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

Posted

技术标签:

【中文标题】如何从 AES 加密字符串中添加/删除 PKCS7 填充?【英文标题】:How to add/remove PKCS7 padding from an AES encrypted string? 【发布时间】:2011-11-11 00:13:23 【问题描述】:

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

有什么想法吗?

【问题讨论】:

请注意:如果可以更改,请使用another mode than ECB (it is insecure)。 @Paul 不,不能改变,它取决于客户的系统。您有机会指导我使用填充物吗? 不要使用 ECB 模式。如果您的客户认为他们需要欧洲央行,那他们就错了。使用 CTR 或 CBC,确保您对密文进行身份验证。 这个答案涵盖了如何向已经加密的数据添加填充:***.com/questions/24404770/… 【参考方案1】:

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

填充方案本身在6.3. Content-encryption Process 部分中给出。它本质上是说:根据需要附加那么多字节来填充给定的块大小(但至少一个),并且每个字节都应该有填充长度作为值。

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

我现在可以为您提供一对 php 函数来执行此操作,但我的 PHP 有点生疏。所以要么自己做这个(然后随时编辑我的答案以将其添加),或者查看 mcrypt 文档的user-contributed notes - 其中相当一部分是关于填充并提供 PKCS #7 填充的实现。


那么,让我们详细看看first note there:

<?php

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

这将获取所使用算法的块大小。在你的情况下,你会使用aesrijndael_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 次,即长度为$pad 的字符串,用$pad 填充。 $str .= ... 将此填充字符串附加到原始数据中。

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

这是加密本身。使用MCRYPT_RIJNDAEL_128 而不是MCRYPT_DES

 

现在换个方向:

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

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

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

这又是块大小。 (见上文。)

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

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

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

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

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

所以现在我们从字符串中截断最后一个$pad 字节。 (除了strlen($str),我们也可以在这里写$lensubstr($str, 0, $len - $pad)。)。

 

?>

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

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


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

【讨论】:

如果您可以提供一些示例代码以用伪代码或 java 执行此操作,那就太好了。我仍然不太明白如何做到这一点,即给定的块大小是多少,最后解码的字节是多少,等等 删除填充时,您不应该只删除它。您应该检查它是否正确。如果继续,如果不正确,则删除解密的文本并抛出填充错误。 警告:如果攻击者可以在线测试不正确的填充,那么攻击者可以创建可以完全破坏机密性的填充预言攻击。在 IV 和密文上使用 MAC 或 HMAC 来避免这种情况。 在您的解密函数中,您正在计算 $block 但未在函数中使用它。为什么? @omarjebari 老实说,我不知道。我将代码从用户提供的 cmets 复制到 PHP 文档的链接页面,然后将我的 cmets 添加到其中。 (链接的评论要么从此消失,要么页面上的 ID 已更改。)【参考方案2】:

我创建了两种方法来执行填充和取消填充。这些函数使用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ŭlo Ebermann 的答案无效,它与代码和 phpdoc 中的答案基本相同,而不是描述。


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

【讨论】:

【参考方案3】:

解密数据后调用如下函数

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

【讨论】:

答案代码不适用于 PHP mcrypt 默认使用的空填充。对于 PKCS#7/PKCS#5 填充,需要检查填充是否有效。考虑使用错误的键,$strPad 很可能是错误的,可能是一个大于数据长度的值。但是不要返回一个糟糕的填充错误,这往往会创建一个填充预言,而是什么都不做。大多数库都支持 PKCS#7 填充,并且会在加密时自动添加填充并在解密时删除填充——无需执行更多操作。

以上是关于如何从 AES 加密字符串中添加/删除 PKCS7 填充?的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中的 iOS AES/CBC/PKCS7Padding 128 位算法中加密的解密字符串的问题

在 Objective C、.net 和 Android 中生成相同的加密字符串 AES/CBC/PKCS7Padding

微信小程序--AES加密解密 ----ECS,Pkcs7,Hex,Utf8

php AES cbc模式 pkcs7 128位加密解密(微信小程序)

使用 AES/CBC/PKCS7Padding 的 JAVA 加密

python aes pkcs7加密