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

时间:2021-07-25 18:25:04

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?

3 个解决方案

#1


50  

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

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

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 notes to 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 there in detail:

那么,让我们看一下详细的第一个注释:

<?phpfunction 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 aes or rijndael_128 instead of des, I suppose (I didn't test it). (Instead, you could simply take 16 here for AES, instead of invoking the function.)

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

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

This calculates the padding size. strlen($str) is the length of your data (in bytes), % $block gives 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 1 and $block, inclusive).

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

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

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

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

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

Here is the encryption itself. Use MCRYPT_RIJNDAEL_128 instead 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]);

$len is now the length of the padded string, and $str[$len - 1] is the last character of this string. ord converts this to a number. Thus $pad is 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 $pad bytes from the string. (Instead of strlen($str) we could also write $len here: 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 substr function 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),因为PHP中的substr函数对负操作数/参数有特殊处理,需要计算从字符串的结尾。 (我不知道这是否比首先获得长度和手动计算索引更有效率。)

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更安全的模式。)

#2


7  

I've created two methods to perform the padding and unpadding. The functions are documented using phpdoc and 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ŭloEbermann的答案无效,它在代码和phpdoc中基本上是相同的答案而不是描述。


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

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

#3


0  

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;}

#1


50  

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

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

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 notes to 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 there in detail:

那么,让我们看一下详细的第一个注释:

<?phpfunction 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 aes or rijndael_128 instead of des, I suppose (I didn't test it). (Instead, you could simply take 16 here for AES, instead of invoking the function.)

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

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

This calculates the padding size. strlen($str) is the length of your data (in bytes), % $block gives 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 1 and $block, inclusive).

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

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

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

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

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

Here is the encryption itself. Use MCRYPT_RIJNDAEL_128 instead 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]);

$len is now the length of the padded string, and $str[$len - 1] is the last character of this string. ord converts this to a number. Thus $pad is 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 $pad bytes from the string. (Instead of strlen($str) we could also write $len here: 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 substr function 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),因为PHP中的substr函数对负操作数/参数有特殊处理,需要计算从字符串的结尾。 (我不知道这是否比首先获得长度和手动计算索引更有效率。)

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更安全的模式。)

#2


7  

I've created two methods to perform the padding and unpadding. The functions are documented using phpdoc and 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ŭloEbermann的答案无效,它在代码和phpdoc中基本上是相同的答案而不是描述。


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

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

#3


0  

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;}