Flutter / Dart AES-256-CBC 从 PHP 中的加密中解密

Jab*_*alr 5 php aes dart flutter

任何人都可以帮助我找出使用 AES-256-CBC 在 PHP 中加密的数据的解密算法。我尝试了很多不同的方法,但我认为在尝试复制在 Dart 中重新创建 Key/IV 的方法时我搞砸了,并且不断收到异常,例如:

RangeError (end): Invalid value: Not in inclusive range 0..16:
Run Code Online (Sandbox Code Playgroud)

进行加密的PHP代码(由于加密字符串由第三方提供,因此无法更改)如下:

function encrypt( $string, $encrypt=true) {
    $secret_key = 'SuperSecretKey';
    $secret_iv = 'SuperSecretBLOCK';
    $output = false;
    $encrypt_method = "AES-256-CBC";
    $key = hash( 'sha256', $secret_key );
    $iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );
    if($encrypt) {
        $output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
    } else {
        $output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
    }
    return $output;
}
Run Code Online (Sandbox Code Playgroud)

例如,如果调用 PHP 中的加密例程来加密字符串“This is a Test!”,则结果将是:

ZHArWURDY2FkelBtSGY5c1AzdTNBZz09
Run Code Online (Sandbox Code Playgroud)

我试图在 Dart 中解密的正是这个结果,但没有任何运气!

到目前为止,这是我所得到的导致上面提到的异常的原因:

import 'package:encrypt/encrypt.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert' show utf8;

String extractPayload(String payload) {
      String strPwd = 'SuperSecretKey';
      String strIv = 'SuperSecretBLOCK';
      var iv = sha256.convert(utf8.encode(strIv));
      var key = sha256.convert(utf8.encode(strPwd));
      IV ivObj = IV.fromUtf8(iv.toString());
      Key keyObj = Key.fromUtf8(key.toString());
      final encrypter = Encrypter(AES(keyObj));
      final decrypted = encrypter.decrypt(Encrypted.from64(payload), iv: ivObj);
      print(decrypted);
      return decrypted;
}
Run Code Online (Sandbox Code Playgroud)

欢迎任何建议,谢谢

Top*_*aco 8

PHP 代码有许多不必要的弱点,使移植变得复杂:

  • 散列时,结果以十六进制编码字符串的形式返回,不幸的是,这是该hash函数的默认值。将结果作为二进制数据返回更有意义(但为此,必须将第三个参数显式设置为TRUE)。在这种情况下,以十六进制字符串形式返回有两个缺点:
  1. 由于每个字节由两个字符表示,因此SHA256的32字节哈希由64个字符表示,即64个字节。这用作 AES-256 密钥(因为在实现过程中可能错误地假设了二进制数据),导致密钥无效,因为 AES-256 密钥的大小正好是 32 字节。PHP通过截断太长的键(太短的键用 0 值填充)来解决这个问题,即只使用前 32 个字节作为键。对于跨平台实现,在密钥无效的情况下显示错误消息比应用任意和不透明的规则秘密生成有效密钥更有用。
  2. 对于十六进制字符串,根据平台不同,数字 (af) 可以使用大写或小写字母。如果是跨平台实现的情况,则会生成不同的密钥。这里这并不重要,因为 PHP 和 Dart 使用小写字母。
  • openssl_encrypt默认情况下 Base64 对密文进行编码,openssl_decrypt默认情况下需要 Base64 编码的密文。可以使用 flag 禁用此功能OPENSSL_RAW_DATA,但当前代码中并非如此。同时在加密时对密文进行了显式Base64编码,因此是双重Base64编码的。类似地,在解密密文时,它是显式 Base64 解码的,因此它也是双重 Base64 解码的。这种冗余是没有意义的,只会不必要地增加密文并降低性能。

此外,Dart 代码假定了错误的默认值:加密包使用 SIC(或 CTR)作为默认模式。由于 CBC 是在 PHP 代码中指定的,因此必须在 Dart 代码中显式指定此模式。

以下 Dart 实现解密密文ZHArWURDY2FkelBtSGY5c1AzdTNBZz09

import 'package:encrypt/encrypt.dart' as EncryptPack;
import 'package:crypto/crypto.dart' as CryptoPack;
import 'dart:convert' as ConvertPack;

String extractPayload(String payload) {
    String strPwd = "SuperSecretKey";
    String strIv = 'SuperSecretBLOCK';
    var iv = CryptoPack.sha256.convert(ConvertPack.utf8.encode(strIv)).toString().substring(0, 16);         // Consider the first 16 bytes of all 64 bytes
    var key = CryptoPack.sha256.convert(ConvertPack.utf8.encode(strPwd)).toString().substring(0, 32);       // Consider the first 32 bytes of all 64 bytes
    EncryptPack.IV ivObj = EncryptPack.IV.fromUtf8(iv);
    EncryptPack.Key keyObj = EncryptPack.Key.fromUtf8(key);
    final encrypter = EncryptPack.Encrypter(EncryptPack.AES(keyObj, mode: EncryptPack.AESMode.cbc));        // Apply CBC mode
    String firstBase64Decoding = new String.fromCharCodes(ConvertPack.base64.decode(payload));              // First Base64 decoding
    final decrypted = encrypter.decrypt(EncryptPack.Encrypted.fromBase64(firstBase64Decoding), iv: ivObj);  // Second Base64 decoding (during decryption)
    return decrypted;
}
Run Code Online (Sandbox Code Playgroud)

以下测试返回明文This is a Test!

String plaintext = extractPayload("ZHArWURDY2FkelBtSGY5c1AzdTNBZz09");
print(plaintext); // This is a Test!
Run Code Online (Sandbox Code Playgroud)