2011-11-08 149 views
76

我想實現基於密碼的加密算法,但我得到這個異常:鑑於最終塊未正確填充

javax.crypto.BadPaddingException:鑑於最終塊未正確填充

什麼可能是問題? (我是新來的Java。)

這裏是我的代碼:

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

回答

152

如果您嘗試使用錯誤的密鑰解密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位的密鑰大小(對於標準的變體)。

+6

不錯,完整答案 – demongolem

+1

+1很好的答案。 –

+1

我只想確認一下。我是加密新手,這是我的場景,我正在使用AES加密。在我的加密/解密功能中,我正在使用加密密鑰。我在解密中使用了一個錯誤的加密密鑰,並且我得到了這個'javax.crypto.BadPaddingException:給定的最終塊未正確填充。我應該將此視爲錯誤的關鍵嗎? – kenicky

1

取決於你所使用的加密算法,你可能需要添加在加密一個字節數組前,最後加上一些填充字節,這樣字節數組的長度就是塊大小的倍數:

具體來說,你選擇的填充模式是PKCS5,它是這裏描述: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_SYM__PAD.html

(我假定你有問題,當您嘗試加密)

當實例的Cipher對象你可以選擇你的填充模式。支持的值取決於您使用的安全提供程序。

順便問一下,您確定要使用對稱加密機制來加密密碼嗎?不會是單向散列更好?如果你真的需要能夠解密密碼,DES是一個相當薄弱的解決方案,如果你需要使用對稱算法,你可能有興趣使用像AES這樣更強大的東西。

+1

我忘記提及的問題是上解密。 – Altrim

+1

所以你可以發佈試圖加密/解密的代碼嗎? (並檢查你嘗試解密的字節數組是否不大於塊大小) – fpacifici

+1

我對Java和密碼學都很陌生,所以我仍然不知道更好的加密方法。我只想完成這個工作,而不是尋找更好的方法來實現它。 – Altrim

相關問題