如何用 Dart 解密 AES 256 CBC
How to decrypt AES 256 CBC with Dart
我想将下面的 PHP 脚本转换为 dart 我尝试了很多案例,但没有任何帮助。
我试过下面的代码;但是这里encrypter.decrypt方法抛出异常
import 'package:encrypt/encrypt.dart' as EncryptPack;
import 'package:crypto/crypto.dart' as CryptoPack;
import 'dart:convert' as ConvertPack;
void main(List<String> arguments) {
var decrypt = extractPayload('$encryptedResopnse');
print(decrypt);
}
String extractPayload(String encryptedResopnse) {
if (encryptedResopnse == null) {
return '';
}
var separated = encryptedResopnse.split(':');
var secret = 'abcd123';
var data = ConvertPack.base64Decode(separated[0].trim());
var iv = CryptoPack.sha256.convert(data).toString().substring(0, 16);
var salt = CryptoPack.sha256.convert(data).toString().substring(16, 32);
var cipherText = CryptoPack.sha256.convert(data).toString().substring(64);
print('cipherText : ${cipherText}');
var ivObj = EncryptPack.IV.fromBase64(iv);
var generator = PBKDF2(hashAlgorithm: CryptoPack.sha1);
var hash = generator.generateBase64Key(secret, salt, 2048, 32);
print('hash : $hash');
var keyObj = EncryptPack.Key.fromBase64(hash);
final encrypter = EncryptPack.Encrypter(
EncryptPack.AES(keyObj, mode: EncryptPack.AESMode.cbc)); // Apply CBC mode
print(cipherText);
var firstBase64Decoding = cipherText; // First Base64 decoding
print(firstBase64Decoding);
final decrypted = encrypter.decrypt(
EncryptPack.Encrypted.fromBase64(firstBase64Decoding),
iv: ivObj);
return decrypted;
}
用于演示内容;
初始化 aes_secret
$aes_secret = '123456ac';
演示内容;
$encryptedResopnse = "dBluiiVaHxhRcWJPaEip9kCGXDwufk3mFp8Xe9ioh9UKu6xL+CHUZrKvuf3xI7P1vFpvyyJ2Vz2Q3ieLEuRHk7NOinZU82FNdE3SOc9D2JTUFkif5ye3rVfQ7O39DpBnV41CduEP0OsASA8cr/RChqhulVHsaw6oUP0mg79M3Jlnpbab0EqlWRQx3k85rcajmov4cYLmsja++p2Lyw/BgOTKDf/yw3NWiK73Ot4P3C6urUiFNUCQTaOHCas1Sa8Wl0udQo1viyApuCE9+Ll1SGnUu26uNy5RR55IFLVnAHuIOBDePjdAw3DapAtLFnSd+FrVjYcUuevMMliSy3PHiZU66qdyx8YSn13tYH6KGFxC/kvPsi5dLGorQ1TdNR5fxZGRPNQXEEIwWYSiF8LA0AJzVqpRoXs9PkEseCUnH1Sj5sBQgXQc0RA8vHWf3n2X/cABLEWaRHHlBlZjqjJXl0uKSgAWC3JoelABGSuSCvL3GJhn9SuSV6+jCOftb6UCmw7LzalKB7UNIQPJ1vMtKl3+38RKDwp7a4xpdlln+IPp+R2aGuobuhk9ySSJYN3GCn7MoC/uaCAR0aEYsIHP1BQ+UgOPOsQFZEVdKMrFLJsJ3HtQ1fQxqpPQ13TClWCOyZu+w+1q4W+8CBJuI4l4Em+91 ";
class AesEncryption {
private static $encryptionMethod = 'aes-256-cbc';
private static $blockSize = 16;
private static $keySize = 32; // in bytes - so 256 bit for aes-256
private static $iterations = 2048;
public static function sign($data, $key) {
return hash_hmac('sha256', $data, $key);
}
/**
* @param string $encryptedContent
* @param string $secret
* @return string
*/
public static function decrypt(string $encryptedContent, string $secret) {
if (!$encryptedContent) {
return "";
}
// Separate payload from potential hmac
$separated = explode(":", trim($encryptedContent));
// Extract HMAC if signed
$hmac = (isset($separated[1])) ? $separated[1] : null;
// Convert data-string to array
$data = base64_decode($separated[0]);
// Then we remove the iv and salt to fetch the original text
$iv = substr($data, 0, self::$blockSize);
//echo($iv);
$salt = substr($data, self::$blockSize, self::$blockSize);
// We finally extract the ciphertext
$cipherText = substr($data, self::$blockSize * 2);
// Generate Key
$key = hash_pbkdf2('sha1', $secret, $salt, self::$iterations, self::$keySize, true);
// Check https://www.php.net/manual/en/function.openssl-decrypt.php
return openssl_decrypt($cipherText, self::$encryptionMethod, $key, OPENSSL_RAW_DATA, $iv);
}
}
generateBase64Key
or generateKey
methods of the PBKDF2
包需要盐的字符串。在这些方法的实现中,盐是UTF8编码的。
通常,salt 是随机生成的,因此包含与 UTF8 编码不兼容的字节序列。目前尚不清楚这里使用的盐是如何产生的。但是,它包含(像随机生成的盐)与 UTF8 编码不兼容的字节序列。因此,在这种情况下,应用的 PBKDF2
包不适用于密钥派生。在我看来,选择 salt 的字符串类型是一个糟糕的设计。
cryptography
package, on the other hand, provides a Pbkdf2
将盐处理为 Uint8List
的实现,因此是合适的。这个库也支持AES/CBC,所以用这个库来解密也是有意义的。
以下实现解密发布的密文:
var encryptedResopnse = 'dBluiiVaHxhRcWJPaEip9kCGXDwufk3mFp8Xe9ioh9UKu6xL+CHUZrKvuf3xI7P1vFpvyyJ2Vz2Q3ieLEuRHk7NOinZU82FNdE3SOc9D2JTUFkif5ye3rVfQ7O39DpBnV41CduEP0OsASA8cr/RChqhulVHsaw6oUP0mg79M3Jlnpbab0EqlWRQx3k85rcajmov4cYLmsja++p2Lyw/BgOTKDf/yw3NWiK73Ot4P3C6urUiFNUCQTaOHCas1Sa8Wl0udQo1viyApuCE9+Ll1SGnUu26uNy5RR55IFLVnAHuIOBDePjdAw3DapAtLFnSd+FrVjYcUuevMMliSy3PHiZU66qdyx8YSn13tYH6KGFxC/kvPsi5dLGorQ1TdNR5fxZGRPNQXEEIwWYSiF8LA0AJzVqpRoXs9PkEseCUnH1Sj5sBQgXQc0RA8vHWf3n2X/cABLEWaRHHlBlZjqjJXl0uKSgAWC3JoelABGSuSCvL3GJhn9SuSV6+jCOftb6UCmw7LzalKB7UNIQPJ1vMtKl3+38RKDwp7a4xpdlln+IPp+R2aGuobuhk9ySSJYN3GCn7MoC/uaCAR0aEYsIHP1BQ+UgOPOsQFZEVdKMrFLJsJ3HtQ1fQxqpPQ13TClWCOyZu+w+1q4W+8CBJuI4l4Em+91aMT4xm7FWB/RhmUN8hfsHk7EATW8CkRGF4zFGKKdeN9zzGM0ViZYv30PARg8W2SJRKoZkaMOgZXtE/8D9fWzrmNDdHBCbMt0yrGycBbn8/b3JLQkcqxzY6VnWrBR1VJ66OB1mH5i6ejDrkxLx5VvkdKf3fcoKEZ/FptZK4zUwXgHJIF/YLChsYUj2mX9Ox18ZZi9vBG9L5vONc0GuQ31FzjwG77yGrJrS4mVi76uaifu7Thd6TiYXuu7OaFBl9+lPMvfHf+wWRqLQbgDoVtOXvND5e4LncWPHZbEjHGwO9I/MnVjMnH6nSbKER63Na8XBUIwsSlJwrswa3fLNInJA1/qGBb9nrVNzKLRfvku1UPvavDP1WxsTEzg0gH8Ui6KzBoBOd9IK/7ZtmSmSug5Ig8GAZ0R/kR7DnSs4ekxKxmcCJ95YVyf9fx0Vlw2oB9iOoUaHM3OITeldfMtoM=';
var secret = 'abcd123';
var decrypt = extractPayload(encryptedResopnse, secret);
print(decrypt);
和
import 'dart:convert' as ConvertPack;
import 'package:cryptography/cryptography.dart' as CryptographyPack;
...
String extractPayload(String encryptedResopnse, String secret) {
if (encryptedResopnse == null) {
return '';
}
// Separate data
// Note: Authentication not considered (separated: size = 1; see ciphertext and PHP code (hmac derived but unused))
var separated = encryptedResopnse.split(':');
var data = ConvertPack.base64Decode(separated[0].trim());
var iv = data.sublist(0, 16);
var salt = data.sublist(16, 32);
var cipherText = data.sublist(32);
// Derive key
var generator = CryptographyPack.Pbkdf2(
macAlgorithm: CryptographyPack.Hmac(CryptographyPack.sha1),
iterations: 2048,
bits: 256,
);
var hash = generator.deriveBitsSync(
ConvertPack.utf8.encode(secret),
nonce: CryptographyPack.Nonce(salt)
);
// Decrypt
var decrypted = CryptographyPack.aesCbc.decryptSync(
cipherText,
secretKey: CryptographyPack.SecretKey(hash),
nonce: CryptographyPack.Nonce(iv));
var plaintext = ConvertPack.utf8.decode(decrypted);
return plaintext;
}
和 returns 结果:
{"nofollow":false,"id":"2226521","title":"When You Say Nothing At All","album":"Ronan","albumID":"237798","artist":"Ronan Keating","artistID":"52715","track":"6","year":"1999","duration":"258.00","coverArt":"323816","ArtistArt":1002340723,"allowoffline":1,"genre":"Pop","AlbumArt":"323816","keywords":["When You Say Nothing At All","Ronan Keating","Ronan"],"languageid":2,"bitrates":"24,256","hexcolor":"#b43931","cleardetails":1,"bitrate":64,"size":"2108905","releasedate":"1999-01-01","explicit":"0","extras":"eyJyZXF1ZXN0dHlwZWlkIjo...","saveprogress":0,"lyrics":"true","is_podcast":0,"is_original":1,"location":"https:\\/\\/some audio file\u2019s URL","debugurl":"http:\\/\\/some URL","debugurldata":"http:\\/\\/some URL","hash":"b1229af8b0078e0b9ec9e203e3b32b7c","plays":"593963","likes":"13705"}
必须在 dependencies 部分的 pubspec.yaml 中引用 cryptography
包,here:
cryptography: ^1.4.1
我想将下面的 PHP 脚本转换为 dart 我尝试了很多案例,但没有任何帮助。
我试过下面的代码;但是这里encrypter.decrypt方法抛出异常
import 'package:encrypt/encrypt.dart' as EncryptPack;
import 'package:crypto/crypto.dart' as CryptoPack;
import 'dart:convert' as ConvertPack;
void main(List<String> arguments) {
var decrypt = extractPayload('$encryptedResopnse');
print(decrypt);
}
String extractPayload(String encryptedResopnse) {
if (encryptedResopnse == null) {
return '';
}
var separated = encryptedResopnse.split(':');
var secret = 'abcd123';
var data = ConvertPack.base64Decode(separated[0].trim());
var iv = CryptoPack.sha256.convert(data).toString().substring(0, 16);
var salt = CryptoPack.sha256.convert(data).toString().substring(16, 32);
var cipherText = CryptoPack.sha256.convert(data).toString().substring(64);
print('cipherText : ${cipherText}');
var ivObj = EncryptPack.IV.fromBase64(iv);
var generator = PBKDF2(hashAlgorithm: CryptoPack.sha1);
var hash = generator.generateBase64Key(secret, salt, 2048, 32);
print('hash : $hash');
var keyObj = EncryptPack.Key.fromBase64(hash);
final encrypter = EncryptPack.Encrypter(
EncryptPack.AES(keyObj, mode: EncryptPack.AESMode.cbc)); // Apply CBC mode
print(cipherText);
var firstBase64Decoding = cipherText; // First Base64 decoding
print(firstBase64Decoding);
final decrypted = encrypter.decrypt(
EncryptPack.Encrypted.fromBase64(firstBase64Decoding),
iv: ivObj);
return decrypted;
}
用于演示内容;
初始化 aes_secret
$aes_secret = '123456ac';
演示内容;
$encryptedResopnse = "dBluiiVaHxhRcWJPaEip9kCGXDwufk3mFp8Xe9ioh9UKu6xL+CHUZrKvuf3xI7P1vFpvyyJ2Vz2Q3ieLEuRHk7NOinZU82FNdE3SOc9D2JTUFkif5ye3rVfQ7O39DpBnV41CduEP0OsASA8cr/RChqhulVHsaw6oUP0mg79M3Jlnpbab0EqlWRQx3k85rcajmov4cYLmsja++p2Lyw/BgOTKDf/yw3NWiK73Ot4P3C6urUiFNUCQTaOHCas1Sa8Wl0udQo1viyApuCE9+Ll1SGnUu26uNy5RR55IFLVnAHuIOBDePjdAw3DapAtLFnSd+FrVjYcUuevMMliSy3PHiZU66qdyx8YSn13tYH6KGFxC/kvPsi5dLGorQ1TdNR5fxZGRPNQXEEIwWYSiF8LA0AJzVqpRoXs9PkEseCUnH1Sj5sBQgXQc0RA8vHWf3n2X/cABLEWaRHHlBlZjqjJXl0uKSgAWC3JoelABGSuSCvL3GJhn9SuSV6+jCOftb6UCmw7LzalKB7UNIQPJ1vMtKl3+38RKDwp7a4xpdlln+IPp+R2aGuobuhk9ySSJYN3GCn7MoC/uaCAR0aEYsIHP1BQ+UgOPOsQFZEVdKMrFLJsJ3HtQ1fQxqpPQ13TClWCOyZu+w+1q4W+8CBJuI4l4Em+91 ";
class AesEncryption {
private static $encryptionMethod = 'aes-256-cbc';
private static $blockSize = 16;
private static $keySize = 32; // in bytes - so 256 bit for aes-256
private static $iterations = 2048;
public static function sign($data, $key) {
return hash_hmac('sha256', $data, $key);
}
/**
* @param string $encryptedContent
* @param string $secret
* @return string
*/
public static function decrypt(string $encryptedContent, string $secret) {
if (!$encryptedContent) {
return "";
}
// Separate payload from potential hmac
$separated = explode(":", trim($encryptedContent));
// Extract HMAC if signed
$hmac = (isset($separated[1])) ? $separated[1] : null;
// Convert data-string to array
$data = base64_decode($separated[0]);
// Then we remove the iv and salt to fetch the original text
$iv = substr($data, 0, self::$blockSize);
//echo($iv);
$salt = substr($data, self::$blockSize, self::$blockSize);
// We finally extract the ciphertext
$cipherText = substr($data, self::$blockSize * 2);
// Generate Key
$key = hash_pbkdf2('sha1', $secret, $salt, self::$iterations, self::$keySize, true);
// Check https://www.php.net/manual/en/function.openssl-decrypt.php
return openssl_decrypt($cipherText, self::$encryptionMethod, $key, OPENSSL_RAW_DATA, $iv);
}
}
generateBase64Key
or generateKey
methods of the PBKDF2
包需要盐的字符串。在这些方法的实现中,盐是UTF8编码的。
通常,salt 是随机生成的,因此包含与 UTF8 编码不兼容的字节序列。目前尚不清楚这里使用的盐是如何产生的。但是,它包含(像随机生成的盐)与 UTF8 编码不兼容的字节序列。因此,在这种情况下,应用的 PBKDF2
包不适用于密钥派生。在我看来,选择 salt 的字符串类型是一个糟糕的设计。
cryptography
package, on the other hand, provides a Pbkdf2
将盐处理为 Uint8List
的实现,因此是合适的。这个库也支持AES/CBC,所以用这个库来解密也是有意义的。
以下实现解密发布的密文:
var encryptedResopnse = 'dBluiiVaHxhRcWJPaEip9kCGXDwufk3mFp8Xe9ioh9UKu6xL+CHUZrKvuf3xI7P1vFpvyyJ2Vz2Q3ieLEuRHk7NOinZU82FNdE3SOc9D2JTUFkif5ye3rVfQ7O39DpBnV41CduEP0OsASA8cr/RChqhulVHsaw6oUP0mg79M3Jlnpbab0EqlWRQx3k85rcajmov4cYLmsja++p2Lyw/BgOTKDf/yw3NWiK73Ot4P3C6urUiFNUCQTaOHCas1Sa8Wl0udQo1viyApuCE9+Ll1SGnUu26uNy5RR55IFLVnAHuIOBDePjdAw3DapAtLFnSd+FrVjYcUuevMMliSy3PHiZU66qdyx8YSn13tYH6KGFxC/kvPsi5dLGorQ1TdNR5fxZGRPNQXEEIwWYSiF8LA0AJzVqpRoXs9PkEseCUnH1Sj5sBQgXQc0RA8vHWf3n2X/cABLEWaRHHlBlZjqjJXl0uKSgAWC3JoelABGSuSCvL3GJhn9SuSV6+jCOftb6UCmw7LzalKB7UNIQPJ1vMtKl3+38RKDwp7a4xpdlln+IPp+R2aGuobuhk9ySSJYN3GCn7MoC/uaCAR0aEYsIHP1BQ+UgOPOsQFZEVdKMrFLJsJ3HtQ1fQxqpPQ13TClWCOyZu+w+1q4W+8CBJuI4l4Em+91aMT4xm7FWB/RhmUN8hfsHk7EATW8CkRGF4zFGKKdeN9zzGM0ViZYv30PARg8W2SJRKoZkaMOgZXtE/8D9fWzrmNDdHBCbMt0yrGycBbn8/b3JLQkcqxzY6VnWrBR1VJ66OB1mH5i6ejDrkxLx5VvkdKf3fcoKEZ/FptZK4zUwXgHJIF/YLChsYUj2mX9Ox18ZZi9vBG9L5vONc0GuQ31FzjwG77yGrJrS4mVi76uaifu7Thd6TiYXuu7OaFBl9+lPMvfHf+wWRqLQbgDoVtOXvND5e4LncWPHZbEjHGwO9I/MnVjMnH6nSbKER63Na8XBUIwsSlJwrswa3fLNInJA1/qGBb9nrVNzKLRfvku1UPvavDP1WxsTEzg0gH8Ui6KzBoBOd9IK/7ZtmSmSug5Ig8GAZ0R/kR7DnSs4ekxKxmcCJ95YVyf9fx0Vlw2oB9iOoUaHM3OITeldfMtoM=';
var secret = 'abcd123';
var decrypt = extractPayload(encryptedResopnse, secret);
print(decrypt);
和
import 'dart:convert' as ConvertPack;
import 'package:cryptography/cryptography.dart' as CryptographyPack;
...
String extractPayload(String encryptedResopnse, String secret) {
if (encryptedResopnse == null) {
return '';
}
// Separate data
// Note: Authentication not considered (separated: size = 1; see ciphertext and PHP code (hmac derived but unused))
var separated = encryptedResopnse.split(':');
var data = ConvertPack.base64Decode(separated[0].trim());
var iv = data.sublist(0, 16);
var salt = data.sublist(16, 32);
var cipherText = data.sublist(32);
// Derive key
var generator = CryptographyPack.Pbkdf2(
macAlgorithm: CryptographyPack.Hmac(CryptographyPack.sha1),
iterations: 2048,
bits: 256,
);
var hash = generator.deriveBitsSync(
ConvertPack.utf8.encode(secret),
nonce: CryptographyPack.Nonce(salt)
);
// Decrypt
var decrypted = CryptographyPack.aesCbc.decryptSync(
cipherText,
secretKey: CryptographyPack.SecretKey(hash),
nonce: CryptographyPack.Nonce(iv));
var plaintext = ConvertPack.utf8.decode(decrypted);
return plaintext;
}
和 returns 结果:
{"nofollow":false,"id":"2226521","title":"When You Say Nothing At All","album":"Ronan","albumID":"237798","artist":"Ronan Keating","artistID":"52715","track":"6","year":"1999","duration":"258.00","coverArt":"323816","ArtistArt":1002340723,"allowoffline":1,"genre":"Pop","AlbumArt":"323816","keywords":["When You Say Nothing At All","Ronan Keating","Ronan"],"languageid":2,"bitrates":"24,256","hexcolor":"#b43931","cleardetails":1,"bitrate":64,"size":"2108905","releasedate":"1999-01-01","explicit":"0","extras":"eyJyZXF1ZXN0dHlwZWlkIjo...","saveprogress":0,"lyrics":"true","is_podcast":0,"is_original":1,"location":"https:\\/\\/some audio file\u2019s URL","debugurl":"http:\\/\\/some URL","debugurldata":"http:\\/\\/some URL","hash":"b1229af8b0078e0b9ec9e203e3b32b7c","plays":"593963","likes":"13705"}
必须在 dependencies 部分的 pubspec.yaml 中引用 cryptography
包,here:
cryptography: ^1.4.1