1

我想我应该先处理我的第一个陈述。当您使用 Open SSL 库时,PHP 进行 AES-128-CBC 加密的方式是否存在不一致?

我问这个是因为如果您查看RFC3602的 AES,那么您会在第 4 节中找到一些测试向量。我尝试的第一个是:

案例 #1:使用 AES-CBC 和 128 位密钥加密 16 个字节(1 个块)

密钥:0x06a9214036b8a15b512e03d534120006

四:0x3dafba429d9eb430b422da802c9fac41

Paintext:“单块味精”

密文:0xe353779c1079aeb82708942dbe77181a

使用带有以下代码的开放 SSL 库:

echo bin2hex(openssl_encrypt($str, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $iv));

你实际上回来了:

e353779c1079aeb82708942dbe77181ab97c825e1c785146542d396941bce55d

现在这一切都很好,并且可以很好地解密,前 32 个字节是预期的密文“e353779c1079aeb82708942dbe77181a”。但是,从 Open SSL 库返回的密文末尾似乎还有另外 32 个字节。为什么是这样?这个问题接下来变得相关。

作为我正在做的事情的一部分,我正在尝试从头开始实施 AES CBC 模式加密。我目前用于执行此操作的代码是(基于先前引用的 RFC 和“块密码操作模式”维基百科页面上的 CBC 图):

public function cbcEncrypt($str, $iv, $key) {
    $bl = 16;
    
    $bs = str_split($str,$bl);
    $pv = null;
    $r = '';
    
    foreach($bs as $b) {
        if($pv === null) $pv = $iv;
        if(strlen($b)<$bl) $b = $this->pkcs7Pad($b, $bl);
        $pv = openssl_encrypt($b^$pv, 'aes-128-ecb', $key, OPENSSL_RAW_DATA);
        $r .= $pv;
    }
    
    return $r;
}

目前我很确定这段代码无论如何都不会在比当前测试的 16 字节字符串长的字符串上工作,但是,我将这段代码与 Open SSL 实现并排运行以进行比较。CBC 实现的返回值是:

e353779c1079aeb82708942dbe77181a8b1ccc6f8cd525ffe22d6327d891a063

通过以下方式调用时:

$crypto = new CryptoTools();
$enc = $crypto->cbcEncrypt('Single block msg',hex2bin("3dafba429d9eb430b422da802c9fac41"),hex2bin("06a9214036b8a15b512e03d534120006"));

同样,前 32 个字节是正确的,但再次添加了后 32 个字节,但与 CBC 的 Open SSL 实现完全不同。关于为什么的任何线索?

此外,ECB 模式的 Open SSL 实现返回一个 32 字节字符串,这意味着 CBC 加密的字符串当前长度大于 16 字节,$pv 的新值将是 32 字节长度,然后它将与第二个字节进行异或纯文本块实际上不应该 $pv 总是 16 字节的长度?通常只是接受你将 $pv 从 32 字节截断到 16 字节吗?

以防万一,上面代码中的 pkcs7Pad 方法如下所示:

public function pkcs7Pad($str, $b = 8) {
    if(trim($str) == '') return false;
    if((int)$b < 1)  return false;
    $p = $b - (strlen($str) % $b);
    return $str . str_repeat(chr($p),$p);
}

任何帮助将不胜感激。不用说这方面的文档不多,因为人们通常不会愚蠢到尝试重新发明轮子,当然是在 PHP 中......

4

2 回答 2

4

这与填充有关。测试向量根本不使用任何填充,而 openssl_encrypt 函数和您的函数应用 PKCS#7 填充。

指定了 PKCS#7 填充,以便在所有情况下都添加填充。当明文长度是块大小的倍数时,会添加一个完整的填充块,这就是为什么明文为 16 字节时密文为 32 字节长的原因。

无法立即看到您的函数有任何问题(尽管我不熟悉 PHP),但您可以$str在拆分之前尝试填充,而不是检查每个块是否需要填充。

于 2013-06-28T11:06:43.467 回答
1

啊哈!知道了!感谢ntoskrnl 的回答这个 Stackoverflow 问题,我设法想出了一个解决方案!如果你这样做:

echo bin2hex(base64_decode(openssl_encrypt($str, 'aes-128-cbc', $key, OPENSSL_ZERO_PADDING, $iv)));

您将匹配 RFC 提供的测试用例!你只需要记住,因为你现在没有做 OPENSSL_RAW_DATA 你会得到一个 base64 编码的字符串,所以需要在将其转换为十六进制以匹配测试用例之前对其进行 base64 解码!

TLDR:这是一个填充问题,只是不是一个明显的问题!

于 2013-06-28T12:33:57.067 回答