口令加密算法
# 口令加密算法
用户输入的口令(密码),并不能直接当作密钥(因为其不符合长度等要求),因此要用口令加密算法,对口令进行处理,使其符合密钥的要求。
# 为什么要对口令进行处理
对称加密算法决定了口令必须是固定长度,然后对明文进行分块加密。又因为安全需求,口令长度往往都是 128 位以上(16 个字符),常见的长度有 128/192/256 位
而我们使用压缩包工具,加密压缩文件的时候,密码随便输入多少位都可以,难道加密方式不一样?
实际上用户输入的口令并不能直接作为 AES 的密钥进行加密(除非长度恰好是 128/192/256 位),并且用户输入的口令一般都有规律,安全性远远不如安全随机数产生的随机口令。
因此,用户输入的口令,通常还需要使用 PBE 算法,采用随机数杂凑计算出真正的密钥(有点像加盐),再进行加密。
# PBE
PBE 就是 Password Based Encryption 的缩写,其作用就是把用户输入的口令和一个安全随机的口令采用杂凑后计算出真正的密钥,伪代码如下:
key = generate(userPassword, secureRandomPassword);
以 AES 密钥为例,我们让用户输入一个口令,然后生成一个随机数,通过 PBE 算法计算出真正的 AES 口令,再进行加密,示例:
package chapter12Hash;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;
public class EncryptDemo3PBE {
public static void main(String[] args) throws Exception{
Security.addProvider( new BouncyCastleProvider()); // 把BouncyCastle作为Provider添加到java.security:
String message = "Hello World!"; //原文
String password = "hello12345"; //加密口令
byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
System.out.printf("salt: %032x\n", new BigInteger(1, salt));
// 加密:
byte[] data = message.getBytes("UTF-8");
byte[] encrypted = encrypt(password, salt, data);
System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(password, salt, encrypted);
System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
}
// 加密
public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
SecretKey skey = secretKeyFactory.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);
}
// 解密:
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
使用 PBE 时,我们还需要引入 BouncyCastle,并指定算法是 PBEwithSHA1and128bitAES-CBC-BC
。观察代码,实际上真正的 AES 密钥是调用 Cipher
的 init()
方法时同时传入 SecretKey
和 PBEParameterSpec
实现的。在创建 PBEParameterSpec
的时候,我们还指定了循环次数 1000
,循环次数越多,暴力破解需要的计算量就越大。
如果我们把 salt 和循环次数固定,就得到了一个通用的“口令”加密软件。如果我们把随机生成的 salt 存储在 U 盘,就得到了一个“口令”加 USB Key 的加密软件,简称 Ukey,它的好处在于,即使用户使用了一个非常弱的口令,没有 USB Key 仍然无法解密,因为 USB Key 存储的随机数密钥安全性非常高。
目前,很多银行都提供 Ukey,如果没有 Ukey 是不能做太大金额的交易的,就是出于安全性的考虑
# 小结
PBE 算法通过用户口令和安全的随机 salt 计算出 Key,然后再进行加密;
Key 通过口令和安全的随机 salt 计算得出,大大提高了安全性;
PBE 算法内部使用的仍然是标准对称加密算法(例如 AES 等)。