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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7314901/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 02:27:56  来源:igfitidea点击:

How to add/remove PKCS7 padding from an AES encrypted string?

phpencryptionaesmcryptpkcs#7

提问by Click Upvote

I'm trying to encrypt/decrypt a string using 128 bit AES encryption (ECB). What I want to know is how I can add/remove the PKCS7 padding to it. It seems that the Mcrypt extension can take care of the encryption/decryption, but the padding has to be added/removed manually.

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

Any ideas?

有任何想法吗?

回答by Pa?lo Ebermann

Let's see. PKCS #7 is described in RFC 5652 (Cryptographic Message Syntax).

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

The padding scheme itself is given in section 6.3. Content-encryption Process. It essentially says: append that many bytes as needed to fill the given block size (but at least one), and each of them should have the padding length as value.

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

Thus, looking at the last decrypted byte we know how many bytes to strip off. (One could also check that they all have the same value.)

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

I could now give you a pair of PHP functions to do this, but my PHP is a bit rusty. So either do this yourself (then feel free to edit my answer to add it in), or have a look at the user-contributed notesto the mcrypt documentation - quite some of them are about padding and provide an implementation of PKCS #7 padding.

我现在可以给你一对 PHP 函数来做到这一点,但我的 PHP 有点生疏。因此,要么自己执行此操作(然后随意编辑我的答案以将其添加),要么查看用户对 mcrypt 文档的贡献注释——其中相当一部分是关于填充并提供 PKCS #7 填充的实现.



So, let's look on the first note therein detail:

因此,让我们详细看一下那里第一个注释

<?php

function encrypt($str, $key)
 {
     $block = mcrypt_get_block_size('des', 'ecb');

This gets the block size of the used algorithm. In your case, you would use aesor rijndael_128instead of des, I suppose (I didn't test it). (Instead, you could simply take 16here for AES, instead of invoking the function.)

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

     $pad = $block - (strlen($str) % $block);

This calculates the padding size. strlen($str)is the length of your data (in bytes), % $blockgives the remainder modulo $block, i.e. the number of data bytes in the last block. $block - ...thus gives the number of bytes needed to fill this last block (this is now a number between 1and $block, inclusive).

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

     $str .= str_repeat(chr($pad), $pad);

str_repeatproduces a string consisting of a repetition of the same string, here a repetition of the character given by$pad, $padtimes, i.e. a string of length $pad, filled with $pad. $str .= ...appends this padding string to the original data.

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

     return mcrypt_encrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);

Here is the encryption itself. Use MCRYPT_RIJNDAEL_128instead of MCRYPT_DES.

这是加密本身。使用MCRYPT_RIJNDAEL_128代替MCRYPT_DES

 }

Now the other direction:

现在另一个方向:

 function decrypt($str, $key)
 {   
     $str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB);

The decryption. (You would of course change the algorithm, as above). $str is now the decrypted string, including the padding.

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

     $block = mcrypt_get_block_size('des', 'ecb');

This is again the block size. (See above.)

这又是块大小。(看上面。)

     $pad = ord($str[($len = strlen($str)) - 1]);

This looks a bit strange. Better write it in multiple steps:

这看起来有点奇怪。最好分多个步骤写:

    $len = strlen($str);
    $pad = ord($str[$len-1]);

$lenis now the length of the padded string, and $str[$len - 1]is the last character of this string. ordconverts this to a number. Thus $padis the number which we previously used as the fill value for the padding, and this is the padding length.

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

     return substr($str, 0, strlen($str) - $pad);

So now we cut off the last $padbytes from the string. (Instead of strlen($str)we could also write $lenhere: substr($str, 0, $len - $pad).).

所以现在我们$pad从字符串中切掉最后一个字节。(strlen($str)我们也可以$len在这里写:substr($str, 0, $len - $pad).)。

 }

?>

Note that instead of using substr($str, $len - $pad), one can also write substr($str, -$pad), as the substrfunction in PHP has a special-handling for negative operands/arguments, to count from the end of the string. (I don't know if this is more or less efficient than getting the length first and and calculating the index manually.)

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

As said before and noted in the comment by rossum, instead of simply stripping off the padding like done here, you should check that it is correct - i.e. look at substr($str, $len - $pad), and check that all its bytes are chr($pad). This serves as a slight check against corruption (although this check is more effective if you use a chaining mode instead of ECB, and is not a replacement for a real MAC).

如前所述并在 rossum 的评论中指出,您应该检查它是否正确,而不是像这里所做的那样简单地剥离填充 - 即查看substr($str, $len - $pad),并检查其所有字节是否为chr($pad). 这可以作为对损坏的轻微检查(尽管如果您使用链接模式而不是 ECB,这种检查会更有效,并且不能替代真正的 MAC)。



(And still, tell your client they should think about changing to a more secure mode than ECB.)

(而且,告诉您的客户他们应该考虑更改为比 ECB 更安全的模式。)

回答by Maarten Bodewes

I've created two methods to perform the padding and unpadding. The functions are documented using phpdocand require PHP 5. As you will notice the unpad function contains a lot of exception handling, generating not less than 4 different messages for each possible error.

我创建了两种方法来执行填充和取消填充。这些函数使用phpdoc并需要 PHP 5进行记录。您会注意到 unpad 函数包含许多异常处理,为每个可能的错误生成不少于 4 条不同的消息。

To get to the block size for PHP mcrypt, you can use mcrypt_get_block_size, which also defines the block size to be in bytes instead of bits.

要获得 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);
}

This does not invalidate the answer of Pa?lo Ebermann in any way, it's basically the same answer in code & phpdoc instead of as description.

这不会以任何方式使 Pa?lo Ebermann 的答案无效,它与代码和 phpdoc 中的答案基本相同,而不是作为描述。



Note that returning a padding error to an attacker might result in a padding oracle attackwhich completely breaks CBC (when CBC is used instead of ECB or a secure authenticated cipher).

请注意,向攻击者返回填充错误可能会导致完全破坏 CBC的填充预言机攻击(当使用 CBC 代替 ECB 或安全认证密码时)。

回答by Hari Das

Just call the following function after you decrypt the data

解密数据后调用以下函数即可

function removePadding($decryptedText){
    $strPad = ord($decryptedText[strlen($decryptedText)-1]);
    $decryptedText= substr($decryptedText, 0, -$strPad);
    return $decryptedText;
}