使用 PHP AES CCM 模式解密的意外结果

Unexpected result decrypting using PHP AES CCM mode

我正在尝试使用 AES-256-CCM 重现当前在 Java 中与 Bouncy Castle 提供商一起执行的加密操作。使用 openssl 在 PHP 中尝试相同的操作时,我找不到一组产生相同输出的参数。

由于 AEAD 模式最近被添加到 PHP (7.1),因此关于其工作原理的文档很少。

Java 中 "working" 加密的最小示例如下所示:

    public static void main(String args[]) {
    try {
        java.security.Security.addProvider(new BouncyCastleProvider());
        byte[] key = Base64.decodeBase64("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
        byte[] iv = Base64.decodeBase64("rcFcdcgZ3Q/A+uHW".getBytes());

        SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
        Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
        aesCipher.init(1, aesKey, new IvParameterSpec(iv));

        byte[] encrypted = aesCipher.doFinal("test".getBytes());
        System.out.println(Hex.encodeHex(encrypted));

        // Output: 411d89ff74205c106d8d85a8
    }
    catch (Throwable e) {
        e.printStackTrace();
    }
}

当我尝试使用不同的两种不同的库和语言重新生成它时,我已将 key 和 iv 设置为已知值。

当尝试使用 PHP 和 openssl 重新生成时,我正在尝试使用以下代码

$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = base64_decode('rcFcdcgZ3Q/A+uHW');
$data = 'test';
$tag = null;

$encrypted = openssl_encrypt($data,'aes-256-ccm', $key,OPENSSL_RAW_DATA, $iv, $tag,"",8);
echo(bin2hex($encrypted . $tag));
// d1a7403799b8c37240f36edb

显然结果不匹配。为了寻找不正确的答案,我在 javascript 中使用 SJCL 创建了相同的操作。例如:

var data = "test";
var key = sjcl.codec.base64.toBits("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
var iv = sjcl.codec.base64.toBits("rcFcdcgZ3Q/A+uHW");
var  p = { 
        adata: "",
        iter: 0,
        mode: "ccm",
        ts: 64,
        ks: 256,
        iv: iv,
        salt: ""
        };
var encrypted = sjcl.encrypt(key, data, p, {}); 
console.log(encrypted);
// Output: {"iv":"rcFcdcgZ3Q/A+uHW","v":1,"iter":0,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"","ct":"QR2J/3QgXBBtjYWo"}

// QR2J/3QgXBBtjYWo === 411d89ff74205c106d8d85a8

Bouncy Castle 和 SJCL 库产生相同的输出,但我不知道有什么不同。

我已尝试按照 Encrypt in Javascript with SJCL and decrypt in PHP 中的建议使用 PBKDF2 预处理密钥,但没有成功。我已尝试对密钥进行 SHA256 运算但没有成功。

为什么 php/openssl 中的输出与 Bouncy Castle 和 SJCL 不同?

当我偶然发现类似的问题时,我发现问题出在 IV 上,更准确地说:它的长度。就您使用长度小于 12 的 IV 而言,它会产生相同的哈希值。你可以用自己的代码试试:

java.security.Security.addProvider(new BouncyCastleProvider());
byte[] key = Base64.getDecoder().decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
byte[] iv = "12345678901".getBytes();

SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
aesCipher.init(1, aesKey, new IvParameterSpec(iv));

byte[] encrypted = aesCipher.doFinal("test".getBytes());
System.out.println(Hex.encodeHex(encrypted));
// Output: e037af9889af21e78252ab58

与PHP相同:

$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = "12345678901";
$tag = null;

$encrypted = openssl_encrypt("test", "aes-256-ccm", $key, OPENSSL_RAW_DATA, $iv, $tag, null, 8);
print bin2hex($encrypted . $tag);
# e037af9889af21e78252ab58

如果您扩展 IV,您会发现结果会有所不同。 注意!请记住,如果您将 AES 密钥缩短(至 128 字节),则 Java 将自动切换为 aes-128,但在 PHP 中您必须手动更改算法。