【问题标题】:AES PKCS7 paddingAES PKCS7 填充
【发布时间】:2018-11-01 19:58:20
【问题描述】:

我刚开始学习用于 AES 加密/解密的 Bouncy Castle。我正在使用带有 256 位密钥的 AES/CBC/PKCS7PADDING

BC 可以成功加密和解密文本,但是在解密后我注意到总是有一些空填充(0x00),因此我的哈希比较失败。例如,假设原始输入字符串为“1234567890”,则解密后的字节数组始终为:

{0x49,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x48,0x00,0x00,0x00,0x00,0x00,0x00}

为什么填充不是0x06,0x06,0x06,0x06,0x06,0x06?有什么方法可以确定加密后的填充长度(可能为0),以便在加密前得到完全相同的字符串?

【问题讨论】:

  • 您的代码在这里会很有帮助。很可能您的缓冲区是固定大小的,并且您没有将其截断为正确的长度,但它可能是一些没有代码就无法分辨的事情。

标签: encryption cryptography aes bouncycastle pkcs#7


【解决方案1】:

当你指定PKCS7时,BC会在加密前为数据添加填充,解密时再次删除。 PKCS7AES 将始终添加至少 1 个字节的填充,并将添加足够的数据以使输入成为 AES 块大小的倍数。解密时,填充被验证正确,并且在 PKCS7 的情况下,还用作指示最后一个解密数据块中有多少是填充的,有多少是真实数据。 p>

如果您尝试在解密步骤中未指定 PKCS7 的情况下对加密和填充数据进行解密,则填充仍会在解密数据中。

编辑:

为了说明我的观点.. 下面是一些使用 AES/CBC/PKCS7 加密“1234567890”的 Java 代码,然后在使用和不使用 PKCS7 填充的情况下再次解密:

public class BCTest {
    public static void doTest() throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        byte[] clearData = "1234567890".getBytes();
        SecretKey secretKey = new SecretKeySpec("0123456789ABCDEF".getBytes(), "AES");
        AlgorithmParameterSpec IVspec = new IvParameterSpec("0123456789ABCDEF".getBytes());

        // encrypt with PKCS7 padding
        Cipher encrypterWithPad = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
        encrypterWithPad.init(Cipher.ENCRYPT_MODE, secretKey, IVspec);
        byte[] encryptedData = encrypterWithPad.doFinal(clearData);
        System.out.println("Encryped data (" + encryptedData.length + " bytes): \t" + toHexString(encryptedData));

        // decrypt with PKCS7 pad
        Cipher decrypterWithPad = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
        decrypterWithPad.init(Cipher.DECRYPT_MODE, secretKey, IVspec);
        byte[] buffer1 = new byte[encryptedData.length]; 
        int decryptLen1 = decrypterWithPad.doFinal(encryptedData, 0, encryptedData.length, buffer1); 
        System.out.println("Decrypted with Pad (" + decryptLen1 + " bytes):  \t" + toHexString(buffer1));

        // decrypt without PKCS7 pad
        Cipher decrypterWithoutPad = Cipher.getInstance("AES/CBC/NOPADDING", "BC");
        decrypterWithoutPad.init(Cipher.DECRYPT_MODE, secretKey, IVspec);
        byte[] buffer2 = new byte[encryptedData.length]; 
        int decryptLen2 = decrypterWithoutPad.doFinal(encryptedData, 0, encryptedData.length, buffer2); 
        System.out.println("Decrypted without Pad (" + decryptLen2 + " bytes):\t" + toHexString(buffer2));
    }

    private static String toHexString(byte[] bytes) {
        return javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
    }

    public static void main(String[] args) throws Exception {
        BCTest.doTest(); 
    }
}

输出:

Encryped data (16 bytes):           602CAE14358D0AC5C96E2D46D17E58E3
Decrypted with Pad (10 bytes):      31323334353637383930000000000000
Decrypted without Pad (16 bytes):   31323334353637383930060606060606

使用填充选项解密时,输出已去除填充 - 密码指示 10 个字节的解密数据 - 缓冲区的其余部分填充为 0。在没有填充选项的情况下解密会导致填充现在成为解密数据的一部分。

编辑2:

现在看到原始代码,证实了我的预感。 GetOutputSize 方法不返回解密字符串的输出大小,而只返回输出缓冲区中所需的最大空间。该方法在 BC 代码中有以下文档:

/**
* return the size of the output buffer required for an update plus a
* doFinal with an input of len bytes.
*
* @param len the length of the input.
* @return the space required to accommodate a call to update and doFinal
* with len bytes of input.
*/

DoFinal 返回放入缓冲区的解密数据的实际长度。

所以在

byte[] plainTextBuffer = new byte[cipher.GetOutputSize(data.Length - IV_LENGTH)];
int length = cipher.DoFinal(data, iv.Length, data.Length - iv.Length, plainTextBuffer, 0);

plainTextBuffer 会比实际解密数据稍大 - 数据的实际长度将在 length 中。

【讨论】:

  • 但是-正如您所说-始终添加一个字节的填充,并且由于 PKCS#7 填充中的字节与填充量具有相同的值,因此您永远不能以带有值 @ 的字节结束987654328@.
  • 总是添加一个字节的填充 - 然后在解密中删除。您通常根本看不到填充。我的猜测是,您从缓冲区打印的内容比解密实际返回的内容要多。您使用什么语言? BC 报告的解密数据的长度是多少?
  • @Maarten Bodewes - 你能详细说明反对意见吗?如果您忽略密码返回的长度,那么在 java 中解密 ala this: byte[] plainData = new byte[encryptedData.length]; int length = cipher.doFinal(encryptedData, 0); 将给出问题中列出的输出。
  • 如果没有注释中的代码,我认为您不希望零字节是新创建的数组的初始内容。实际上,如果您只是更新您的答案以包含上述代码并围绕它写下您的答案,我会很乐意投票。
  • 这看起来是一个非常可能的答案,感谢您的编辑。希望当问题包含代码时,我们可以完全验证它。将反对票反转为赞成票。
【解决方案2】:

我正在使用来自 bouncycastle 的 c#。在我看来,这可能是 bouncycastle 的错误,或者至少 bouncycastle c# 实现不完全遵循 pkcs7 规范。

我的解决方案是去掉 DoFinal 返回长度中不包含的尾随字节。仍然不太清楚为什么会有 0x00 的填充,正如所说的根本不应该存在。

下面是代码。我使用 AES/CBC/PKCS7PADDING 进行加密和解密。

加密--->

        ICipherParameters keyParams = ParameterUtilities.CreateKeyParameter("AES", keyByte);
        ICipherParameters aesIVKeyParam = new ParametersWithIV(keyParams, StringToByteArray(IV_STRING));
        byte[] iv = ((ParametersWithIV) aesIVKeyParam).GetIV();

        IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7PADDING");
        cipher.Init(true, aesIVKeyParam);

        byte[] cipherText = new byte[iv.Length + cipher.GetOutputSize(data.Length)];
        Array.Copy(iv, 0, cipherText, 0, iv.Length);
        int length = cipher.DoFinal(data, 0, data.Length, cipherText, iv.Length);

解密--->

        ICipherParameters keyParams = ParameterUtilities.CreateKeyParameter("AES", keyByte);
        byte[] iv = new byte[IV_LENGTH];
        Array.Copy(data, 0, iv, 0, IV_LENGTH);
        ICipherParameters aesIVKeyParam = new ParametersWithIV(keyParams, iv);

        IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7PADDING");
        cipher.Init(false, aesIVKeyParam);

        byte[] plainTextBuffer = new byte[cipher.GetOutputSize(data.Length - IV_LENGTH)];
        int length = cipher.DoFinal(data, iv.Length, data.Length - iv.Length, plainTextBuffer, 0);

【讨论】:

  • 你应该用这个更新你的原始问题..而不是添加它作为答案..
猜你喜欢
  • 2020-12-28
  • 2011-05-29
  • 2017-08-29
  • 1970-01-01
  • 2021-04-17
  • 2014-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多