我正在尝试使用128位AES加密(ECB)加密/解密字符串。我想知道的是如何添加/删除PKCS7填充。似乎Mcrypt扩展可以处理加密/解密,但必须手动添加/删除填充。
有什么想法吗?
答案 0 :(得分:54)
我们来看看。 RFC 5652(加密消息语法)中描述了PKCS#7。
填充方案本身在6.3. Content-encryption Process部分中给出。它基本上说:根据需要附加许多字节来填充给定的块大小(但至少有一个),并且每个字节都应该以填充长度作为值。
因此,查看最后一个解密字节,我们知道要剥离多少字节。 (也可以检查它们是否都具有相同的值。)
我现在可以给你一对PHP函数来做到这一点,但我的PHP有点生疏。所以要么自己做(然后随意编辑我的答案以添加它),或者查看m user-contributed notes到mcrypt文档 - 其中一些是关于填充并提供PKCS#7的实现填充。
所以,让我们详细了解first note there:
<?php
function encrypt($str, $key)
{
$block = mcrypt_get_block_size('des', 'ecb');
这将获得所使用算法的块大小。在您的情况下,我会使用aes
或rijndael_128
代替des
,我想(我没有对其进行测试)。 (相反,您可以简单地将16
用于AES,而不是调用该函数。)
$pad = $block - (strlen($str) % $block);
这会计算填充大小。 strlen($str)
是数据的长度(以字节为单位),% $block
给出余数模$block
,即最后一个块中的数据字节数。 $block - ...
因此给出了填充最后一个块所需的字节数(现在是1
和$block
之间的数字,包括在内。
$str .= str_repeat(chr($pad), $pad);
str_repeat
生成一个由重复相同字符串组成的字符串,此处重复character given by $pad
,$pad
次,即一个长度为{{的字符串1}},填充$pad
。
$pad
将此填充字符串附加到原始数据。
$str .= ...
这是加密本身。使用MCRYPT_RIJNDAEL_128
代替 return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
。
MCRYPT_DES
现在是另一个方向:
}
解密。 (你当然会改变算法,如上所述)。 $ str现在是解密的字符串,包括填充。
function decrypt($str, $key)
{
$str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
这又是块大小。 (见上文。)
$block = mcrypt_get_block_size('des', 'ecb');
这看起来有点奇怪。最好分多步编写:
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len-1]);
现在是填充字符串的长度,$len
是此字符串的最后一个字符。 ord
将此转换为数字。因此$str[$len - 1]
是我们以前用作填充的填充值的数字,这是填充长度。
$pad
所以现在我们从字符串中删除了最后 return substr($str, 0, strlen($str) - $pad);
个字节。 (而不是$pad
我们也可以在这里写strlen($str)
:$len
。)。
substr($str, 0, $len - $pad)
请注意,不是使用 }
?>
,而是可以编写substr($str, $len - $pad)
,因为PHP中的substr($str, -$pad)
函数对负操作数/参数有特殊处理,从最后开始计算的字符串。 (我不知道这是否比首先获得长度和手动计算索引更有效率。)
正如前面所说并在rossum的评论中指出的那样,你应该检查它是否正确,而不是简单地剥离填充,即查看substr
,并检查其所有字节是否为{ {1}}。这可以作为对腐败的轻微检查(尽管如果您使用链接模式而不是ECB,此检查更有效,并且不能替代真正的MAC)。
(并且仍然告诉您的客户他们应该考虑更改为比ECB更安全的模式。)
答案 1 :(得分:8)
我创建了两种方法来执行填充和取消填充。这些函数使用phpdoc
进行记录,并且需要PHP 5.正如您将注意到,unpad函数包含大量异常处理,为每个可能的错误生成不少于4个不同的消息。
要获得PHP mcrypt的块大小,可以使用mcrypt_get_block_size
,它还将块大小定义为以字节为单位而不是位。
/**
* Right-pads the data string with 1 to n bytes according to PKCS#7,
* where n is the block size.
* The size of the result is x times n, where x is at least 1.
*
* The version of PKCS#7 padding used is the one defined in RFC 5652 chapter 6.3.
* This padding is identical to PKCS#5 padding for 8 byte block ciphers such as DES.
*
* @param string $plaintext the plaintext encoded as a string containing bytes
* @param integer $blocksize the block size of the cipher in bytes
* @return string the padded plaintext
*/
function pkcs7pad($plaintext, $blocksize)
{
$padsize = $blocksize - (strlen($plaintext) % $blocksize);
return $plaintext . str_repeat(chr($padsize), $padsize);
}
/**
* Validates and unpads the padded plaintext according to PKCS#7.
* The resulting plaintext will be 1 to n bytes smaller depending on the amount of padding,
* where n is the block size.
*
* The user is required to make sure that plaintext and padding oracles do not apply,
* for instance by providing integrity and authenticity to the IV and ciphertext using a HMAC.
*
* Note that errors during uppadding may occur if the integrity of the ciphertext
* is not validated or if the key is incorrect. A wrong key, IV or ciphertext may all
* lead to errors within this method.
*
* The version of PKCS#7 padding used is the one defined in RFC 5652 chapter 6.3.
* This padding is identical to PKCS#5 padding for 8 byte block ciphers such as DES.
*
* @param string padded the padded plaintext encoded as a string containing bytes
* @param integer $blocksize the block size of the cipher in bytes
* @return string the unpadded plaintext
* @throws Exception if the unpadding failed
*/
function pkcs7unpad($padded, $blocksize)
{
$l = strlen($padded);
if ($l % $blocksize != 0)
{
throw new Exception("Padded plaintext cannot be divided by the block size");
}
$padsize = ord($padded[$l - 1]);
if ($padsize === 0)
{
throw new Exception("Zero padding found instead of PKCS#7 padding");
}
if ($padsize > $blocksize)
{
throw new Exception("Incorrect amount of PKCS#7 padding for blocksize");
}
// check the correctness of the padding bytes by counting the occurance
$padding = substr($padded, -1 * $padsize);
if (substr_count($padding, chr($padsize)) != $padsize)
{
throw new Exception("Invalid PKCS#7 padding encountered");
}
return substr($padded, 0, $l - $padsize);
}
这并没有以任何方式使PaŭloEbermann的答案无效,它在代码和答案中基本上是相同的答案。 phpdoc而不是描述。
请注意,向攻击者返回填充错误可能会导致 padding oracle attack 完全破坏CBC(当使用CBC而不是ECB或安全身份验证密码时)。
答案 2 :(得分:0)
解密数据后,只需调用以下函数
即可function removePadding($decryptedText){
$strPad = ord($decryptedText[strlen($decryptedText)-1]);
$decryptedText= substr($decryptedText, 0, -$strPad);
return $decryptedText;
}