PHP与Flutter(Dart) AES-128加密不匹配问题排查及疑问
问题分析与解决方案
一、PHP端加密结果过短的原因
AES-128-CTR属于流加密模式,它的输出密文长度和明文长度完全一致。你的明文是"1234"(4字节),加密后密文也是4字节,经过Base64编码后自然就是短字符串xpL7dg==(4字节的Base64编码正好是8个字符,末尾的==是标准填充)。
你误以为会得到16位字符串,是混淆了CBC、ECB这类块加密模式的特性——块模式下明文长度不是块大小(16字节)的整数倍时会自动填充,密文长度会是块大小的整数倍;但CTR模式不需要填充,密文长度和明文严格对应。
二、两端加密结果不匹配的核心问题
你两端的加密核心参数完全不统一,具体差异如下:
- 加密模式不一致:PHP用的是
AES-128-CTR,Flutter用的是AESMode.cbc,两种模式的加密逻辑完全不同,结果必然无法互通。 - 密钥处理逻辑不同:
- PHP直接使用原始字符串作为AES密钥(注意AES-128要求密钥必须是16字节,若你的原始密钥长度不符,OpenSSL会自动补0/截断,这是潜在问题)。
- Flutter对原始密钥做SHA256哈希后取前16字节作为密钥,和PHP的密钥完全不是同一个值。
- IV处理逻辑不同:
- PHP直接使用原始字符串作为IV(CTR模式下IV长度要求等于块大小16字节,你的IV正好符合要求)。
- Flutter对原始IV做SHA256哈希后取前16字节作为IV,和PHP的IV完全不同。
- 隐藏错误:Flutter代码中把SHA256哈希的字符串转成UTF8作为密钥/IV,这会改变哈希的原始字节值,属于错误处理方式。
三、修正方案(统一参数实现互通)
要实现两端互通,必须让**加密模式、密钥、IV、填充方式(块模式下)**完全一致。以下提供两种可选方案:
方案1:统一使用AES-128-CTR模式
PHP端修正代码
$ciphering = "AES-128-CTR"; $options = 0; // 对齐Flutter的密钥处理逻辑:SHA256哈希后取前16字节原始值 $original_key = "XXXXXXXXXXXX"; $encryption_key = substr(hash('sha256', $original_key, true), 0, 16); // IV同样对齐Flutter的处理逻辑 $original_iv = '1234567891011121'; $decryption_iv = substr(hash('sha256', $original_iv, true), 0, 16); $encryption = openssl_encrypt("1234", $ciphering, $encryption_key, $options, $decryption_iv); echo $encryption;
Flutter端修正代码
import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:encrypt/encrypt.dart'; void main() { final mykey = "XXXXXXXXXXXX"; final myiv = "1234567891011121"; // 密钥处理:取SHA256哈希后的原始字节前16位 final keyUtf8 = utf8.encode(mykey); final keyBytes = sha256.convert(keyUtf8).bytes.sublist(0, 16); // IV处理:取SHA256哈希后的原始字节前16位 final ivUtf8 = utf8.encode(myiv); final ivBytes = sha256.convert(ivUtf8).bytes.sublist(0, 16); final _iv = IV(ivBytes); // 改用CTR模式 final encrypter = Encrypter(AES(Key(keyBytes), mode: AESMode.ctr)); final encryptedPass = encrypter.encrypt("1234", iv: _iv); print(encryptedPass.base64); }
方案2:统一使用AES-128-CBC模式
PHP端修正代码
$ciphering = "AES-128-CBC"; $options = OPENSSL_RAW_DATA; // 输出原始密文后自行Base64编码,对齐Flutter逻辑 $original_key = "XXXXXXXXXXXX"; $encryption_key = substr(hash('sha256', $original_key, true), 0, 16); $original_iv = '1234567891011121'; $decryption_iv = substr(hash('sha256', $original_iv, true), 0, 16); $encrypted_raw = openssl_encrypt("1234", $ciphering, $encryption_key, $options, $decryption_iv); $encryption = base64_encode($encrypted_raw); echo $encryption;
Flutter端修正代码
import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:encrypt/encrypt.dart'; void main() { final mykey = "XXXXXXXXXXXX"; final myiv = "1234567891011121"; // 修正密钥和IV的处理:取哈希后的原始字节,而非字符串转UTF8 final keyUtf8 = utf8.encode(mykey); final keyBytes = sha256.convert(keyUtf8).bytes.sublist(0, 16); final ivUtf8 = utf8.encode(myiv); final ivBytes = sha256.convert(ivUtf8).bytes.sublist(0, 16); final _iv = IV(ivBytes); final encrypter = Encrypter(AES(Key(keyBytes), mode: AESMode.cbc)); final encryptedPass = encrypter.encrypt("1234", iv: _iv); print(encryptedPass.base64); }
关键注意事项
- AES-128的密钥必须是16字节,AES-256是32字节,一定要确保密钥长度符合要求,否则两端加密库的补全/截断逻辑会不一致。
- CTR模式不需要填充,CBC模式默认使用PKCS7填充,两端要保持填充方式统一。
- 处理哈希后的密钥/IV时,必须使用原始字节数组,不要把哈希字符串转成UTF8(哈希字符串是十六进制字符,转UTF8会改变原始字节值)。
内容的提问来源于stack exchange,提问作者chris herd
相关产品推荐
相关产品推荐

