如何从AES加密字符串添加/删除PKCS7填充?

Cli*_*ote 18 php encryption aes mcrypt pkcs#7

我正在尝试使用128位AES加密(ECB)加密/解密字符串.我想知道的是如何添加/删除PKCS7填充.似乎Mcrypt扩展可以处理加密/解密,但必须手动添加/删除填充.

有任何想法吗?

Paŭ*_*ann 55

让我们来看看.RFC 5652(加密消息语法)中描述了PKCS#7.

填充方案本身在6.3节中给出.内容加密过程.它基本上说:根据需要附加许多字节来填充给定的块大小(但至少有一个),并且每个字节都应该将填充长度作为值.

因此,查看最后一个解密的字节,我们知道要剥离多少字节.(也可以检查它们是否都具有相同的值.)

我现在可以给你一对PHP函数来做这个,但我的PHP有点生疏.所以要么自己做(然后随意编辑我的答案以添加它),或者查看用户提供的mcrypt文档说明 - 其中一些是关于填充并提供PKCS#7填充的实现.


那么,让我们看看那里第一个注释:

<?php

function encrypt($str, $key)
 {
     $block = mcrypt_get_block_size('des', 'ecb');
Run Code Online (Sandbox Code Playgroud)

这将获得所使用算法的块大小.在你的情况下,你会使用aesrijndael_128代替des,我想(我没有测试它).(相反,你可以简单地在16这里使用AES,而不是调用函数.)

     $pad = $block - (strlen($str) % $block);
Run Code Online (Sandbox Code Playgroud)

这会计算填充大小.strlen($str)是数据的长度(以字节为单位),% $block给出模数的余数$block,即最后一个块中的数据字节数.$block - ...因此给出了填充最后一个块所需的字节数(现在这是一个介于1和之间的数字$block).

     $str .= str_repeat(chr($pad), $pad);
Run Code Online (Sandbox Code Playgroud)

str_repeat产生一个由重复相同的字符串组成的字符串,这里是一个字符 的重复$pad,$pad次数,即一串长度$pad,填充$pad. $str .= ...将此填充字符串附加到原始数据.

     return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
Run Code Online (Sandbox Code Playgroud)

这是加密本身.用MCRYPT_RIJNDAEL_128而不是MCRYPT_DES.

 }
Run Code Online (Sandbox Code Playgroud)

现在另一个方向:

 function decrypt($str, $key)
 {   
     $str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);
Run Code Online (Sandbox Code Playgroud)

解密.(你当然会改变算法,如上所述).$ str现在是解密的字符串,包括填充.

     $block = mcrypt_get_block_size('des', 'ecb');
Run Code Online (Sandbox Code Playgroud)

这又是块大小.(往上看.)

     $pad = ord($str[($len = strlen($str)) - 1]);
Run Code Online (Sandbox Code Playgroud)

这看起来有点奇怪.最好用多个步骤编写它:

    $len = strlen($str);
    $pad = ord($str[$len-1]);
Run Code Online (Sandbox Code Playgroud)

$len现在是填充字符串的长度,并且$str[$len - 1]是此字符串的最后一个字符.ord将其转换为数字.因此$pad,我们之前用作填充的填充值的数字,这是填充长度.

     return substr($str, 0, strlen($str) - $pad);
Run Code Online (Sandbox Code Playgroud)

所以现在我们切断$pad了字符串中的最后一个字节.(而不是strlen($str)我们也可以写$len在这里:substr($str, 0, $len - $pad).).

 }

?>
Run Code Online (Sandbox Code Playgroud)

请注意,除了使用之外substr($str, $len - $pad),还可以编写substr($str, -$pad),因为substrPHP中的函数对负操作数/参数具有特殊处理,从字符串的末尾开始计数.(我不知道这是否比首先获得长度和手动计算索引更有效率.)

如前所述,并在rossum的评论中注明,你应该检查它是否正确 - 即查看substr($str, $len - $pad)并检查其所有字节是否正确chr($pad).这可以作为对腐败的轻微检查(尽管如果使用链接模式而不是ECB,这种检查更有效,并且不能替代真正的MAC).


(并且仍然告诉您的客户他们应该考虑改为比ECB更安全的模式.)

  • 删除填充时,不应该只删除它.你应该**检查**它是否正确.如果是,则继续,如果不正确,则擦除解密的文本并抛出填充错误. (7认同)
  • **警告**:如果攻击者可以在线测试不正确的填充,那么攻击者可以创建*填充oracle攻击*,可以完全破坏机密性.在IV和密文上使用MAC或HMAC以避免这种情况. (5认同)

Maa*_*wes 8

我创建了两种方法来执行填充和取消填充.这些函数使用phpdocPHP 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);
}
Run Code Online (Sandbox Code Playgroud)

这并没有以任何方式使PaŭloEbermann的答案无效,它在代码和phpdoc中基本上是相同的答案而不是描述.


请注意,向攻击者返回填充错误可能会导致填充oracle攻击,这会完全破坏CBC(当使用CBC而不是ECB或安全身份验证的密码时).