口令加密
我们在前面介绍了对称加密算法,他们的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