如何用 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