前面說的AES加密,它的密鑰長度是固定的: 128/192/256位,而不是我們隨便輸入幾位都允許的。這次我們主要demo下前面列表裏面的PBE算法- Password Based Encryption的縮寫。
這是因爲對稱加密算法決定了口令必須是固定長度,然後對明文進行分塊加密。又因安全性需求,口令長度基本都是128位以上,即至少16個字符。
但是我們平時使用的加密軟件,輸入6位、8位都可以,難道加密方式不一樣?
實際上用戶輸入的口令並不能直接作爲AES的密鑰進行加密(除非長度恰好是128/192/256位),並且用戶輸入的口令一般都有規律,安全性遠遠不如安全隨機數產生的隨機口令。因此,用戶輸入的口令,通常還需要使用PBE算法,採用隨機數雜湊計算出真正的密鑰,再進行加密。
PBE它的作用如下:
pbeKey = generatePBE(userPassword, secureRandomPassword);
PBE的作用就是把客戶輸入的口令和一個安全隨機的口令採用雜湊後計算出一個最後真正的密鑰。以AES密鑰爲例,讓客戶輸入一個口令,然後生成一個隨機數,通過PBE算法計算出真正的AES口令,再進行加密。
1)PBE算法通過用戶口令和安全的隨機salt計算出Key,然後再進行加密。
2) Key通過口令和安全的隨機salt計算得出,大大提高了安全性。
3)PBE算法內部使用的仍然是標準對稱加密算法(例如AES)。
使用PBE算法時,我們需要引入BouncyCastle,並指定算法是 PBEwithSHA1and128bitAES-CBC-BC
。 實際上真正的AES密鑰是調用 Cipher
的 init()
方法時同時傳入 SecretKey
和 PBEParameterSpec
實現的。 在創建 PBEParameterSpec
的時候,我們還指定了循環次數1000
,循環次數越多,暴力破解需要的計算量就越大。
如果我們把 salt && 循環次數固定,就得到了一個通用的“口令”加密軟件。如果我們把隨機生成的 salt 存儲在U盾或者 盤,就得到了一個“口令”加USBKey的加密軟件,好處在於,用戶使用了一個非常弱的口令,沒有USBKey仍然無法解密,因爲USBKey存儲的隨機數密鑰安全性非常高。
代碼DEMO如下:
/** * desc- 對稱加密 * 加密算法 PBE * auth xupengfei * * @param password * @param salt * @param input * @return * @throws GeneralSecurityException */ public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException { PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); SecretKey skey = skeyFactory.generateSecret(keySpec); PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps); return cipher.doFinal(input); }
/** * desc- 對稱解密 * 加密算法 PBE * auth xupengfei * * @param password * @param salt * @param input * @return * @throws GeneralSecurityException */ public static byte[] decrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException { PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); SecretKey skey = skeyFactory.generateSecret(keySpec); PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC"); cipher.init(Cipher.DECRYPT_MODE, skey, pbeps); return cipher.doFinal(input); }
@Test public void testPBE() throws Exception { // 把BouncyCastle作爲 提供者 Provider添加到 java.security: Security.addProvider(new BouncyCastleProvider()); // 明文 String message = "Hello,I am mynah886!"; // 加密口令 String password = "mynah886"; // 16 bytes 隨機 Salt byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16); log.debug("salt: {}", String.format("%032x", new BigInteger(1, salt ) ) ); // 加密 byte[] data = message.getBytes("UTF-8"); byte[] encrypted = encrypt(password, salt, data); log.debug("encrypted: " + Base64.getEncoder().encodeToString(encrypted)); // 解密 byte[] decrypted = decrypt(password, salt, encrypted); log.debug("decrypted: " + new String(decrypted, "UTF-8")); }
結果如下: