如何使用 Dart 解密 AES 256 CBC

问题描述

我想将下面的 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,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 包的 generateKeyPBKDF2 方法需要一个字符串作为盐。在这些方法的实现中,salt是UTF8编码的。
通常,salt 是随机生成的,因此包含与 UTF8 编码不兼容的字节序列。不知道这里使用的盐是如何产生的。但是,它包含(如随机生成的盐)与 UTF8 编码不兼容的字节序列。因此,在这种情况下,应用的 PBKDF2 包不适合密钥派生。在我看来,为盐选择字符串类型是一个糟糕的设计。

另一方面,cryptography 包提供了一个 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;
}

并作为结果返回:

{"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