前面说的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")); }
结果如下: