给定最终块未正确填充


115

我正在尝试实现基于密码的加密算法,但出现此异常:

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

Answers:


197

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

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

如果您提供了错误的密码,然后将其用于从密钥库中获取密钥,或者使用密钥生成功能将其转换为密钥,也会发生这种情况。

当然,如果您的数据在传输中被破坏,填充也可能发生。

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

  • 对于基于密码的加密,应该使用SecretKeyFactory和PBEKeySpec而不是将SecureRandom与KeyGenerator一起使用。原因是SecureRandom在每个Java实现上可能是不同的算法,为您提供了不同的密钥。SecretKeyFactory以定义的方式进行密钥派生(如果选择正确的算法,则认为是安全的)。

  • 不要使用ECB模式。它独立地加密每个块,这意味着相同的纯文本块也始终提供相同的密文块。

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

  • 通常,您不仅不希望保密,而且还希望身份验证,以确保消息不会被篡改。(这还可以防止对密文进行选择的密文攻击,即有助于提高机密性。)因此,在邮件中添加MAC(消息身份验证代码),或使用包含身份验证的密文模式(请参见上一点)。

  • DES的有效密钥大小仅为56位。这个密钥空间很小,可以由专门的攻击者在几个小时内强行使用。如果您通过密码生成密钥,则速度会更快。此外,DES的块大小仅为64位,这在链接模式中增加了更多弱点。请改用AES等现代算法,该算法的块大小为128位,密钥大小为128位(对于标准变体)。


1
我只想确认一下。我是加密的新手,这是我的场景,我使用的是AES加密。在我的加密/解密功能中,我使用的是加密密钥。我在解密中使用了错误的加密密钥,而我得到了这个javax.crypto.BadPaddingException: Given final block not properly padded。我应该将此作为错误的密钥吗?
kenicky 2015年

明确一点,当为密钥存储文件(例如.p12文件)提供错误的密码时,也会发生这种情况,这正是我所经历的。
沃伦·露

2
@WarrenDew“密钥存储文件的密码错误”只是“密钥错误”的特例。
圣保罗Ebermann

@kenicky对不起,我刚才看到您的评论……是的,错误的按键几乎总是会导致这种效果。(当然,还有可能发生数据损坏的情况。)
PaŭloEbermann

@PaŭloEbermann我同意,但是我认为这并不一定立即显而易见,因为它不同于原始帖子中程序员可以控制密钥和解密的情况。不过,我确实发现您的答案足够有用,可以支持它。
沃伦·露

1

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

具体而言,您选择的填充模式是PKCS5,在此处进行了描述:http ://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ //www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ _SYM__PAD.html

(我认为您尝试加密时遇到问题)

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

顺便说一句,您确定要使用对称加密机制来加密密码吗?不会是一种更好的哈希方法吗?如果您确实需要解密密码,那么DES是一个很弱的解决方案,如果您需要使用对称算法,则可能会对使用诸如AES之类的更强功能感兴趣。


1
因此,您可以张贴尝试加密/解密的代码吗?(并检查您尝试解密的字节数组不大于块大小)
fpacifici 2011年

1
我对Java和密码学都很陌生,因此我仍然不知道进行加密的更好方法。我只想完成这一任务,而不是寻找实现它的更好方法。
Altrim

您可以更新链接吗,因为它不起作用@fpacifici,我更新了我的帖子,其中包括测试加密和解密的JUnit测试
Altrim

已更正(抱歉,复制粘贴错误)。无论如何,确实发生了问题,因为您使用的密钥与Paulo解释的密钥不同,因此无法解密。发生这种情况是因为在每个测试方法之前执行了在junit中用@Before注释的方法,从而每次都重新生成密钥。由于密钥是随机初始化的,因此每次都会不同。
fpacifici 2011年

1

我遇到了这个问题,这是由于操作系统,JRE实现的不同平台所致。

new SecureRandom(key.getBytes())

在Windows中将获得相同的值,而在Linux中将获得不同的值。因此在Linux中需要更改为

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

“ SHA1PRNG”是使用的算法,您可以在此处参考以获取有关算法的更多信息。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.