加密模块
1. 前言
我们知道安全性的实现往往少不了加解密的参与,Spring Security 加密模块支持对称加密、秘钥生成和密码编码。这些代码被发布在 Spring Security 核心模块当中,与其他模块或代码之间零耦合。
本节主要讨论 Spring Security 中的加密模块。
2. 加密器
加密器的相关类提供了构造对称加密器的工厂方法。通过该类,我们可以创建 ByteEncryptor
用于加密原始字节流内容,我们也可以构建出 TextEncryptor
用于加密文本字符串,这些加密器都是线程安全的。
2.1 BytesEncryptor
BytesEncryptor
是通过 Encryptors.stronger
工厂方法构造出的:
Encryptors.stronger("password", "salt");
stronger
加密方法构造了一个使用GCM模式的 256 位 AES 算法加密器。它使用 PKCS #5 的 PBKDF2(基于密码的密钥派生函数 #2)导出密钥(此方法需要 Java 6)。方法中 password
参数用于生产加密密钥,它将被保存在一个不被共享的安全区域。salt
参数用于避免字典攻击。除此之外,此处还应用了 16 字节的随机初始化向量来保证每个加密消息都是唯一的。
salt
参数的是一个 16 禁止编码字符串,具有随机性,至少有 8 位长,salt
可以通过以下方式生成:
String salt = KeyGenerators.string().generateKey(); // 生成一个随机 8 位长的 16 进制字符串
我们也可以使用标准加密方法,即 CBC 模式的 256 位 AES 算法加密模式。这种模式不需要通过身份认证,也无法保证数据的真实性,相对 Encryptors.stronger
来说安全性会低一些。
2.2 TextEncryptor
使用 Encryptors.text
工厂方法构造标准的文本加密器 TextEncryptor
:
Encryptors.text("password", "salt");
TextEncryptor
使用的是标准 BytesEncryptor
来加密文本数据,加密的结果以 16 进制编码字符串形式返回,这样容易被文件系统或数据库保存。
我们也可以使用 Encryptors.queryableText
工厂方法构建可查询的文本加密器:
Encryptors.queryableText("password", "salt");
这两种加密器间的区别是拥有不同的初始化向量(iv)处理。在可查询文本加密器中,初始化向量是可共享的、固定的,并且不是随机生成的。这意味着相同的文本内容多次被加密的结果是相同的。这样的场景的安全性相对较低,但有时为了加密出的结果是有规律的,比如希望他仍然可以被再次查询到,还是需要用到这样的加密方式。可查询文本加密器的使用场景有如 OAuth 的 apiKey。
3. 秘钥生成器
密钥生成器(KeyGenerators)提供了一些工厂方法,用于构造不同类型的密钥生成器。例如 BytesKeyGenerator
可用于生产 byte[]
类型密钥。StringKeyGenerator
用于生成字符串类型密钥。密钥生成器是线程安全的。
3.1 BytesKeyGenerator
使用 KeyGenerators.secureRandom
工厂方法构造 BytesKeyGenerator
实例:
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
默认配置下,生成的 key 长度为 8 位,我们也可以为 secureRandom
设置参数来修改生成密钥长度:
KeyGenerators.secureRandom(16);
使用 KeyGenerators.shared
工厂方法构造 BytesKeyGenerator
可以使多次生成的密钥内容相同。
KeyGenerators.shared(16);
3.2 StringKeyGenerator
使用 KeyGenerators.string
工厂方法可以构造 8 为长的随机 16 禁止字符串密码:
KeyGenerators.string();
4. 密码编码
在 Spring Security 加密模块中,password
包提供了编码密码的方法。PasswordEncoder
是其中的核心类:
public interface PasswordEncoder {
String encode(String rawPassword);
boolean matches(String rawPassword, String encodedPassword);
}
matches
方法用来判断密码原文在经过一次编码后,与密码密文是否匹配,这个方法用于基于密码认证的场景。
最常见的实现类是 BCryptPasswordEncoder
,它使用了 bcrypt
算法来散列密码。Bcrypt 使用了一个随机 16 位盐值,用于制造冗余,以防止密码被破解。冗余次数可以通过 strength
参数设置,其值为 4~31 之间,值约高,散列次数越多,默认值为 10。
// 构造一个强度为 16 的密码加密器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
Pbkdf2PasswordEncoder
实现了 PBKDF2 算法用来散列密码。该算法为了防止被破解,有意的减慢了执行时间,大概需要 0.5 秒完成密码的验证。
// 创建一个 PBKDF2 算法的密码加密器
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
5. 小结
本节讨论了 Spring Security 安全框架中的加解密实现,主要内容有:
- Spring Security 在核心模块中包含了一个关于加密算法的包,其包含了加密、密钥生成、密码编码三个主要功能;
- Spring Security 的加密主要分基于
byte[]
对象的加密和文本形式的加密; - Spring Security 的加密可分为幂等的和非幂等的,其分别有各自的应用场景;
- Spring Security 对密码的加密采取了有意拖延的方式,防止密码被暴力破解。
下节讨论在 Spring Security 项目中,如何实现单元测试。