口令加密 我们在前面介绍了对称加密算法,他们的key其实一个byte数组,例如AES256算法,他的key实际上是一个32位的数组
我们普通的加入软件有所不同,我们在使用WinRAR这样的软件的时候,通常是用户输入的加密口令
如果我们要用户自己输入口令,我们就需要用到PBE算法,它是Password Based Encryption的缩写,PEB算法是由用户输入口令, 然后采用随机数杂凑计算,生成密钥然后再进行加密 1. 首先我们看password也就是用户口令,例如hello123 2. 随着salt是随机生成的一个byte数组 3. 最后的加密密钥key,它是由随机的salt和用户的password计算而成的
我们来比较一下标准的AES方法和PEB方法,在标准的AES算法中,用于加密的密钥key,他这个是随机产生的,在PBE算法 通过用户输入的密码password,以及随机产生的16个字节的salt,基于这两部分生成的key,进行加密,为什么要使用salt 呢,因为用户输入的口令通常都很短,引入一个随机的salt,既可以增加口令的长度,还可以让相同的口令生成相同的key, 从而提高了安全性
package com.learn.securl; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.Security; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; import org.bouncycastle.jce.provider.BouncyCastleProvider; public class PBECipher { /** * 首先我们要Castle的jar包引入到项目中 */ static final String CIPHER_NAME = "PBEwithSHA1and128bitAES-CBC-BC"; /** * 加密 */ public static byte[] encrypt(String password, byte[] salt, byte[] input) throws Exception { /** * 我们在加密的时候需要创建一个PBEKeySpec * 还有传入的用户输入的password * 得到一个PBEKeySpec对象 */ PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); /** * 然后我们通过SecretKeyFactory * 通过getInstance方法 * 得到一个SecretKeyFactory对象 */ SecretKeyFactory sKeyFactory = SecretKeyFactory .getInstance(CIPHER_NAME); /** * 然后通过generateSecret传入keySpec * 就可以得到一个SecretKey * 这个SecretKey就是我们将来要加密的密钥 */ SecretKey skey = sKeyFactory.generateSecret(keySpec); /** * 紧接着我们要通过salt生成一个PBEParameterSpec * 我们传入1000 * 表示用户的口令和salt会走1000次的循环 */ PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); /** * 然后我们通过Cipher.getInstance得到一个Cipher对象 */ Cipher cipher = Cipher.getInstance(CIPHER_NAME); /** * init方法会传入ENCRYPT_MODE,SecretKey, * 以及PBEParameterSpec这三个对象然后就可以加密 */ cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps); /** * 得到密文 */ return cipher.doFinal(input); } /** * 解密 * * 在解密的时候我们需要传入用户需要的password,salt,以及密文 */ public static byte[] decrypt(String password, byte[] salt, byte[] input) throws Exception { PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); SecretKeyFactory sKeyFactory = SecretKeyFactory .getInstance(CIPHER_NAME); SecretKey skey = sKeyFactory.generateSecret(keySpec); PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000); Cipher cipher = Cipher.getInstance(CIPHER_NAME); /** * 然后我们把模式设置为DECRYPT_MODE */ cipher.init(Cipher.DECRYPT_MODE, skey, pbeps); return cipher.doFinal(input); } /** * 我们运行这个代码 * 由于我们每次运行的salt是不同的 * 所以每次我们得到密文也是不同的 * 通过用户口令和随机数 * 我们就保证了加密的强度 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // 把BouncyCastle作为Provider添加到java.security: Security.addProvider(new BouncyCastleProvider()); // 原文: String message = "Hello, world! encrypted using PBE!"; // 加密口令 /** * 我们设置一个加密口令password hello123456 */ String password = "hello12345"; // 16 bytes随机Salt: /** * 我们通过SecureRandom.getInstanceStrong().generateSeed生成一个随机salt */ byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16); /** * 我们打印这个salt * salt: d4356ec3e5621a8f1f6a7be953b0c31e * 我们生成的随机的salt * 1个16字节的数组 */ System.out.printf("salt: %032x\n", new BigInteger(1, salt)); // 加密: byte[] data = message.getBytes(StandardCharsets.UTF_8); /** * 然后调用encrypt方法进行加密 */ byte[] encrypted = encrypt(password, salt, data); /** * 加密以后的密文是用Base64表示的 * encrypted: 2MnA9wd1IL4n0W3d3nS5dqZ6dj1fcxZI2GJtKKIDYchw1EkodmJeOLIQpUBendR+ */ System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted)); // 解密: byte[] decrypted = decrypt(password, salt, encrypted); /** * 解密以后得到与原文相同的信息 * Hello, world! encrypted using PBE! */ System.out.println(new String(decrypted, "UTF-8")); } }
在PBE算法中,如果我们把salt固定下来,就得到了一个通用的口令加密软件 如果我们把salt存在一个U盘上,实际上就得到了一个口令+USB Key的软件,这样的好处是即使用户使用了一个非常弱的口令, 没有USB Key仍然无法解密,因为只有同时破解了口令和salt,才能计算出key,而salt一般都是128位的随机数,一般很难被 破解 最后我们总结一下: 1. PBE算法通过用户口令和随机salt计算key然后再加密 2. 用于加密的Key是通过口令和随机salt计算得出的,因此提高了安全性 3. 在PBE算法的内部,仍然是标准的对称加密算法,例如AES