第二部分:如何使Ruby AES-256-CBC和PHP MCRYPT_RIJNDAEL_128一起发挥得很好

这个问题是我最后一个问题的延续,关于如何使Ruby AES-256-CBC和PHP MCRYPT_RIJNDAEL_128很好地协同工作 。 我现在已经开始工作,但我仍然在努力朝着另一个方向努力。 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 。 但是,我不能容忍我的代码中的这种kludge,所以我想了解如何使ruby代码优雅地处理最后一个块。

问题是mcrypt没有填充最后一个块,而Ruby的OpenSSL绑定使用默认的OpenSSL填充方法,即PKCS填充。 我无法真正改进OpenSSL文档中的描述:

PKCS填充通过添加n个值为n的填充字节来工作,以使数据的总长度为块大小的倍数。 总是添加填充,因此如果数据已经是块大小的倍数,则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结束。

在加密之前,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') => # 1.9.3p125 :009 > cipher.decrypt => # 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?"