第二部分:如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好
Posted
技术标签:
【中文标题】第二部分:如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好【英文标题】:Part II: How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together 【发布时间】:2010-12-24 07:06:18 【问题描述】:这个问题是我上一个问题的延续,关于How to make Ruby AES-256-CBC and php MCRYPT_RIJNDAEL_128 play well together。我现在已经开始工作了,但我仍在努力转向另一个方向。 PHP 生成的密码似乎包含所提供的所有信息,但我无法让 Ruby 代码无错误地对其进行解密。
这是我用来生成密码的 PHP 代码:
$cleartext = "Who's the clever boy?";
$key = base64_decode("6sEwMG/aKdBk5Fa2rR6vVw==\n");
$iv = base64_decode("vCkaypm5tPmtP3TF7aWrug==");
$cryptogram = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $cleartext, MCRYPT_MODE_CBC, $iv);
$result = base64_encode($cryptogram);
print "\n'$result'\n";
RESULT
'JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM='
那么这是在 Ruby 中解密的尝试:
>> cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
>> cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
>> cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
>> cryptogram = Base64.decode64('JM0OxMINPTnF1vwXdI3XdKI0KlVx210CvpJllFja+GM=')
>> cleartext = cipher.update(cryptogram)
=> "Who's the clever"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
from (irb):100:in `final'
from (irb):100
真正令人沮丧的是,有可能从该加密字符串中获取整个明文。重复上述步骤,但在密码中添加一个无意义的填充:
>> cleartext = cipher.update(cryptogram + 'pad')
=> "Who's the clever boy?\000\000\000\000\000\000\000\000\000\000\000"
>> cleartext << cipher.final
OpenSSL::Cipher::CipherError: bad decrypt
from (irb):119:in `final'
from (irb):119
在我的实际用例中,明文是结构化的(一个 JSON 字符串,因为你问了),所以我感觉很舒服,我可以告诉使用这个方案并检测加密不佳的输入而不执行 cipher.final
。但是,我不能容忍我的代码中出现这种混乱,所以我想了解如何让 ruby 代码优雅地处理最后一个块。
【问题讨论】:
【参考方案1】:问题在于mcrypt
没有填充最后一个块,而 Ruby 的 OpenSSL 绑定使用默认的 OpenSSL 填充方法,即 PKCS 填充。我无法真正改进 OpenSSL 文档中的描述:
PKCS 填充通过添加 n 填充来工作 值 n 的字节构成总数 数据长度 a 块大小的倍数。填充是 总是添加所以如果数据已经 块大小 n 的倍数将 等于块大小。例如,如果 块大小为 8 和 11 个字节 要加密,然后是 5 个填充字节 值 5 将被添加。
在加密之前,您需要在 PHP 中的明文末尾手动添加适当的填充。为此,请在加密之前通过 PHP 端的 pkcs5_pad
函数传递您的 $cleartext
(传递 16
作为块大小)。
function pkcs5_pad ($text, $blocksize)
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
如果您也采用其他方式(在 Ruby 中加密并使用 mcrypt 解密),则必须在解密后去除填充字节。
旁注: 即使明文已经是块大小的倍数(整个填充块),您也必须添加填充,这样当您解密时,您就知道最后一个块的最后一个字节总是是添加的填充量。否则,您将无法区分具有单个填充字节的明文和恰好以值 0x01
结尾的没有填充字节的明文。
【讨论】:
感谢您的想法,咖啡馆。将文本添加到明文只是创建一个不同的字符串来加密;它不会改变结果。我同意这个问题与两个实现如何处理加密流的最后一个块有关。两种算法的输出是相同的,直到最后一个 32 字节块,其中最后 16 个字节完全不同。我已经对这个问题失去了耐心,所以除非有一些撒玛利亚人过来为我解决这个问题,否则我会选择上面的问题。 添加到明文末尾的文本(填充)必须具有非常特定的形式,这是 Ruby 方面所期望的。我将调查它使用什么填充方法并更新答案。 ...PKCS 填充通过添加值 n 的 n 个填充字节来使 加密 数据的总长度成为块大小的倍数... 虽然经过反思,措辞有点令人困惑,但填充最肯定添加到未加密的数据中。 咖啡馆,我欠你的,首先是道歉,然后是你选择的虚拟美味饮料(如果你在西雅图附近的任何地方,这种饮料可以是非虚拟的)。对我来说令人难以置信的是,您发现并填充了源 - 即使它的长度正好是 0 mod 16 - 也是必需的。谢谢。【参考方案2】:似乎 PHP \0
在加密之前填充了明文。您可以将 Ruby 设置为禁用填充。
http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-padding-3D
这会起作用,但是您必须手动去除填充。
1.9.3p125 :008 > cipher = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
=> #<OpenSSL::Cipher::Cipher:0x0000000561ee78>
1.9.3p125 :009 > cipher.decrypt
=> #<OpenSSL::Cipher::Cipher:0x0000000561ee78>
1.9.3p125 :010 > cipher.padding = 0
=> 0
1.9.3p125 :011 > cipher.key = Base64.decode64("6sEwMG/aKdBk5Fa2rR6vVw==\n")
=> "\xEA\xC100o\xDA)\xD0d\xE4V\xB6\xAD\x1E\xAFW"
1.9.3p125 :012 > cipher.iv = Base64.decode64("vCkaypm5tPmtP3TF7aWrug==")
=> "\xBC)\x1A\xCA\x99\xB9\xB4\xF9\xAD?t\xC5\xED\xA5\xAB\xBA"
1.9.3p125 :013 > cryptogram = Base64.decode64('JM0OxMINPTnF1vwXdI3XdI2j8NJ8kr+Du0fnkxorNl0=')
=> "$\xCD\x0E\xC4\xC2\r=9\xC5\xD6\xFC\x17t\x8D\xD7t\x8D\xA3\xF0\xD2|\x92\xBF\x83\xBBG\xE7\x93\x1A+6]"
1.9.3p125 :014 > cleartext = cipher.update(cryptogram)
=> "Who's the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1.9.3p125 :015 > cleartext << cipher.final
=> "Who's the clever girl?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1.9.3p125 :042 > cleartext.strip
=> "Who's the clever girl?"
【讨论】:
以上是关于第二部分:如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好的主要内容,如果未能解决你的问题,请参考以下文章
无法使用来自 AES-256-CBC 的 pgcrypto 解密,但 AES-128-CBC 可以
text 唯一支持的密码是AES-128-CBC和AES-256-CBC,密钥长度正确。