给定最终块未正确填充

Posted

技术标签:

【中文标题】给定最终块未正确填充【英文标题】:Given final block not properly padded 【发布时间】:2011-12-24 09:25:55 【问题描述】:

我正在尝试实现基于密码的加密算法,但我得到了这个异常:

javax.crypto.BadPaddingException:

可能是什么问题?

这是我的代码:

public class PasswordCrypter 

    private Key key;

    public PasswordCrypter(String password)  
        try
            KeyGenerator generator;
            generator = KeyGenerator.getInstance("DES");
            SecureRandom sec = new SecureRandom(password.getBytes());
            generator.init(sec);
            key = generator.generateKey();
         catch (Exception e) 
            e.printStackTrace();
        
    


    public byte[] encrypt(byte[] array) throws CrypterException 
        try
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key);

            return cipher.doFinal(array);
         catch (Exception e)  
            e.printStackTrace();
        
        return null;
    

    public byte[] decrypt(byte[] array) throws CrypterException
        try
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);

            return cipher.doFinal(array);
         catch(Exception e )
            e.printStackTrace();
        
        return null;
    

(JUnit 测试)

public class PasswordCrypterTest 

    private static final byte[] MESSAGE = "Alpacas are awesome!".getBytes();
    private PasswordCrypter[] passwordCrypters;
    private byte[][] encryptedMessages;

    @Before
    public void setUp() 
        passwordCrypters = new PasswordCrypter[] 
            new PasswordCrypter("passwd"),
            new PasswordCrypter("passwd"),
            new PasswordCrypter("otherPasswd")
        ;

        encryptedMessages = new byte[passwordCrypters.length][];
        for (int i = 0; i < passwordCrypters.length; i++) 
            encryptedMessages[i] = passwordCrypters[i].encrypt(MESSAGE);
        
    

    @Test
    public void testEncrypt() 
        for (byte[] encryptedMessage : encryptedMessages) 
            assertFalse(Arrays.equals(MESSAGE, encryptedMessage));
        

        assertFalse(Arrays.equals(encryptedMessages[0], encryptedMessages[2]));
        assertFalse(Arrays.equals(encryptedMessages[1], encryptedMessages[2]));
    

    @Test
    public void testDecrypt() 
        for (int i = 0; i < passwordCrypters.length; i++) 
            assertArrayEquals(MESSAGE, passwordCrypters[i].decrypt(encryptedMessages[i]));
        

        assertArrayEquals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[1]));
        assertArrayEquals(MESSAGE, passwordCrypters[1].decrypt(encryptedMessages[0]));

        try 
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[2])));
         catch (CrypterException e) 
            // Anything goes as long as the above statement is not true.
        

        try 
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[2].decrypt(encryptedMessages[1])));
         catch (CrypterException e) 
            // Anything goes as long as the above statement is not true.
        
    

【问题讨论】:

【参考方案1】:

如果您尝试使用错误的密钥解密 PKCS5 填充数据,然后将其取消填充(由 Cipher 类自动完成),您很可能会收到 BadPaddingException(可能略小于 255/256,大约 99.61%),因为填充具有特殊的结构,在 unpad 期间会被验证,并且很少有键会产生有效的填充。

因此,如果您遇到此异常,请将其捕获并将其视为“错误密钥”。

当您提供错误的密码时也会发生这种情况,然后使用该密码从密钥库中获取密钥,或者使用密钥生成函数将其转换为密钥。

当然,如果您的数据在传输过程中损坏,也会发生错误填充。

也就是说,有一些关于您的方案的安全说明:

对于基于密码的加密,您应该使用 SecretKeyFactory 和 PBEKeySpec 而不是使用带有 KeyGenerator 的 SecureRandom。原因是 SecureRandom 可能是每个 Java 实现上的不同算法,为您提供不同的密钥。 SecretKeyFactory 以定义的方式执行密钥派生(如果您选择正确的算法,这种方式被认为是安全的)。

不要使用 ECB 模式。它独立加密每个块,这意味着相同的纯文本块也总是给出相同的密文块。

最好使用安全的mode of operation,例如 CBC(密码块链接)或 CTR(计数器)。或者,使用还包括身份验证的模式,例如 GCM(Galois-Counter 模式)或 CCM(带 CBC-MAC 的计数器),请参阅下一点。

您通常不仅需要保密,还需要身份验证,以确保消息不被篡改。 (这也可以防止对您的密码进行选择密文攻击,即有助于保密。)因此,在您的消息中添加一个 MAC(消息身份验证代码),或使用包含身份验证的密码模式(请参阅前一点)。

DES 的有效密钥大小仅为 56 位。这个密钥空间非常小,可以在几个小时内被专门的攻击者暴力破解。如果您通过密码生成密钥,这将变得更快。 此外,DES 的块大小仅为 64 位,这增加了链接模式的一些弱点。 改用像 AES 这样的现代算法,它的块大小为 128 位,密钥大小为 128 位(对于最常见的变体,也存在 196 和 256 的变体)。

【讨论】:

我只是想确认一下。我是加密新手,这是我的场景,我正在使用 AES 加密。在我的加密/解密函数中,我使用了一个加密密钥。我在解密时使用了错误的加密密钥,我得到了这个javax.crypto.BadPaddingException: Given final block not properly padded。我应该把它当作一个错误的键吗? 需要明确的是,当为密钥存储文件(例如 .p12 文件)提供错误密码时也会发生这种情况,这就是我刚刚发生的情况。 @WarrenDew “密钥库文件的密码错误”只是“错误密钥”的一个特例。 @kenicky 抱歉,我刚才看到了你的评论……是的,错误的键几乎总是会导致这种效果。 (当然,损坏的数据是另一种可能性。) @PaŭloEbermann 我同意,但我认为这不一定是显而易见的,因为它与原始帖子中程序员控制密钥和解密的情况不同。不过,我确实发现您的回答足够有用,可以投票。【参考方案2】:

当您为签名密钥输入错误的密码时,这也可能是一个问题。

【讨论】:

这真的是评论,而不是答案。多一点代表,you will be able to post comments. 这不是答案 剧透警报:这就是答案。我在其中一个密码中打错了。【参考方案3】:

根据您使用的加密算法,您可能需要在加密字节数组之前在末尾添加一些填充字节,以便字节数组的长度是块大小的倍数:

具体而言,您选择的填充模式是 PKCS5,如下所述: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_SYM__PAD.html

(我假设您在尝试加密时遇到了问题)

您可以在实例化 Cipher 对象时选择填充模式。支持的值取决于您使用的安全提供程序。

顺便问一下,您确定要使用对称加密机制来加密密码吗?单向哈希不是更好吗?如果你真的需要能够解密密码,DES 是一个相当弱的解决方案,如果你需要保持对称算法,你可能有兴趣使用更强大的东西,比如 AES。

【讨论】:

那么您能否发布尝试加密/解密的代码? (并检查您尝试解密的字节数组是否大于块大小) 我对 Java 和密码学都很陌生,所以我仍然不知道更好的加密方法。我只是想完成这个,而不是寻找更好的方法来实现它。 你能更新链接吗,因为它不起作用@fpacifici,我更新了我的帖子我包括了测试加密和解密的 JUnit 测试 已更正(抱歉复制粘贴错误)。无论如何,确实发生了您的问题,因为您使用的密钥与 Paulo 解释的用于加密的密钥不同。发生这种情况是因为在 junit 中使用 @Before 注释的方法在每个测试方法之前执行,因此每次都重新生成密钥。由于密钥是随机初始化的,因此每次都会有所不同。【参考方案4】:

由于操作系统的原因,我遇到了这个问题,对于不同平台的 JRE 实现很简单。

new SecureRandom(key.getBytes())

在 Windows 中将获得相同的值,而在 Linux 中则不同。所以在Linux下需要改成

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(128, secureRandom);

“SHA1PRNG”是使用的算法,你可以参考here了解更多关于算法的信息。

【讨论】:

【参考方案5】:

javax.crypto.BadPaddingException: 鉴于最终块未正确填充。

如果在解密过程中使用了错误的密钥,则可能会出现此类问题。

对于我自己来说,当我使用错误的密钥进行解密时会发生这种情况。 它始终区分大小写。因此,请确保您在加密时使用了相同的密钥...... :)

【讨论】:

以上是关于给定最终块未正确填充的主要内容,如果未能解决你的问题,请参考以下文章

加密/解密 - iphone 到 java - BadPaddingException:给定最终块未正确填充

java.io.IOException:无法解密安全内容条目:javax.crypto.BadPaddingException:给定最终块未正确填充

javax.crypto.BadPaddingException:给定最终块没有正确填充Exception

在 Java 中解密文本时出现一些错误

核心数据填充的表无法正确显示

如何正确填充类型为ArrayList?的ArrayList