1、PBE(Password Based Encryption,基于口令加密)是一种基于口令的加密算法,其特点是使用口令代替了密钥,而口令由用户自己掌管,采用随机数(这里称之为 盐)杂凑多重加密等方法保证数据的安全性。
2、PBE 算法并没有 真正构建新的加密/解密算法,而是对已知的对称加密算法(eg:DES算法)做了包装;使用PBE算法对数据做加密/解密操作时,其实是使用了 DES 或者 AES 等其他对称加密算法做了相应的操作。
既然,PBE算法是使用了较为常用的对称加密算法,那就无法回避密钥的问题;口令并不能替代密钥,密钥是经过加密算法计算得出的,但口令本身不可能很长,单纯的口令很容易通过穷举攻击方式破译,这就引入了 “盐”。盐能够阻止字典攻击或预先计算的攻击,本身是一个随机的信息,相同的随机信息极不可能使用2次。将盐附加在 口令上,通过消息摘要算法经过迭代计算获取得到 构建密钥/初始化向量的基本材料,使得破译加密信息的难度加大。
PBE 算法是对称加密算法的综合性算法,常见有:PBEWithMD5AndDES,该算法使用了 MD5 和 DES 算法构建 PBE算法。
3、实现
java6 和 Bouncy Castle 都提供了 系统算法的 相关实现.
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import com.sun.org.apache.xml.internal.security.utils.Base64;
/**
* 对称加密 - 口令算法
*/
public class PBECoder {
/**指定消息摘要算法 组合*/
public static final String ALGORITHM = "PBEWITHMD5andDES";
/**迭代次数*/
public static final int ITERATION_COUNT = 100;
/**
* 初始化 盐
* 盐的 长度必须为 8 字节 (密钥长度 必须是 8 的 倍数)
* @return
*/
public static byte[] initSalt(){
SecureRandom secureRandom = new SecureRandom();
return secureRandom.generateSeed(8);
}
/**
* 转换密钥
*/
public static Key toKey(String password) throws Exception{
// 材料转换
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
// 生产密钥
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM);
// 初始化
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
return secretKey;
}
/**
* 加密数据
* @param data 即将要加密的数据
* @param password 密码
* @param salt 盐
* @return
* @throws Exception
*/
public static String encrypt(byte[] data, String password, byte[] salt) throws Exception{
// 还原密钥
Key key = toKey(password);
// 实例化 PBE 参数
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
// 创建实例
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key, pbeParameterSpec);
byte[] encry = cipher.doFinal(data);
return Base64.encode(encry);
}
/**
* 解密数据
* @param data 即将要解密的数据
* @param password 密码
* @param salt 盐
* @return
*/
public static String decrypt(String data, String password, byte[] salt) throws Exception{
// 还原即将解密的数据
byte[] encry = Base64.decode(data);
// 还原 key
Key key = toKey(password);
// 实例化 PBE 参数
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
// 创建实例
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化
cipher.init(Cipher.DECRYPT_MODE, key, pbeParameterSpec);
byte[] decry = cipher.doFinal(encry);
return new String(decry);
}
public static void main(String[] args) throws Exception {
// 加解密密钥
String password = "pbe_test";
String data = "PBE 测试";
//byte[] salt = initSalt(); // 设置为 随机的 8位数
byte[] salt = "20170814".getBytes(); // 约定为 yyyyMMdd时间
String encryStr = encrypt(data.getBytes("utf-8"), password, salt);
System.out.println(data + "-> 加密后:" + encryStr);
String decryStr = decrypt(encryStr, password, salt);
System.out.println(data + "-> 解密后:" + decryStr);
/** console结果:
* PBE 测试-> 加密后:5nv5Gfr9eJwIqhGn2qYiCg==
PBE 测试-> 解密后:PBE 测试
*/
}
}
4、小结
关于 “盐”的说明:
“盐”本身是一种可以由消息 传递双方按照 一定规律约定的信息,例如时间,也可以是某个不可变物理的硬件的编号;甲乙双方可以通过约定消息传递时间,并将其作为 “盐”的基本信息,根据预定算法(如 MD5算法)对其处理,最终获取真正的 “盐”,这样一来,“盐”就无需传递,提供了安全性。
换一种思路考虑,使用者需要将 U 盘插入计算机,同时输入 口令方能登录,那么 U 盘就是 “盐”信息的提供者,即使 U盘丢失,加密的信息也未必能被窃取。从另一个角度来说,“盐”与 口令就像 2把 不可分割的钥匙。