加密模块

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 项目中,如何实现单元测试。