# 加密解密技术 / Encryption and Decryption

重要提示

要使用加密和解密特性,您需要在 JVM 中安装完整的 JCE (默认情况下不包括它)。 可以在官网搜索 JCE (opens new window) 然后选择你当前版本的 jdk/jre ;

比如我这里是 jdk8,选择

Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files for JDK/JRE 8 Download (opens new window) 下载

下载之后的 jce_policy-8.zip 包中有两个文件:local_policy.jarUS_export_policy.jar 和一份 README.txt 说明,简单说需要把这两个文件覆盖以下两个路径中的文件

  • D:\Program Files\Java\jre8\lib\security
  • D:\Program Files\Java\jdk1.8.0_45\jre\lib\security

也就是你 jdk 和 jre 安装目录下

在配置文件中如果有加密属性存在的话(以 {cipher} 作为前缀),在发送给客户端之前会进行解密; 这样的好处在于,当配置文件在文件系统后端时(如 git 仓库),密码不会以明文方式被暴露,比如如下 yml

spring:
  datasource:
    username: dbuser
    password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
1
2
3
4

config server 服务器需要公开 /encrypt/decrypt 断点,在 org.springframework.cloud.config.server.encryption.EncryptionController 类中实现的

我们可以使用这两个断点进行加密和解密;记得关闭 security 身份认证,否则会出现使用 curl 请求什么也不返回的问题;但是在实际应用中需要对该端点进行保护,保证是安全的

在这之前需要在 bootstrap.yml 中配置以下属性;

encrypt:
  key: 123456
1
2

使用 curl 对 字符串 mysecret 加密;如果没有 JCE 的两个文件,此操作会一直阻塞

$ curl localhost:11000/encrypt -d mysecret
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    72  100    64    0     8      5      0  0:00:12  0:00:12 --:--:--     8d10b24a37054040ac811e86b6231c6d03ef6c3349315dce449b0ace09f78dd50

1
2
3
4
5

对于加密和解密这里展示的暂时还不能进行实际使用,因为本人在测试过程中,照上面这样配置,每次加密都是返回不同的字符串

对于在 curl 中如果加密的值有特殊字符的话可以使用 --data-urlencode 代替 -d(这里暂未测试)

加密解密还支持 /*/{name}/{profiles} ,但是这种形式暂时还没有看懂怎么使用的

本章官网地址 (opens new window)

# 密钥管理 / Key Management

配置服务器可以使用对称(共享)密钥,也可以使用非对称密钥(RSA密钥对)。

就安全性而言,非对称选择更为优越,但是使用对称密钥通常更为方便,因为它是要在 bootstrap.properties 中配置一个值就可以使用。

若要配置对称密钥,可以使用 encrypt.key ,但是不能使用 encrypt.key 配置对称密钥

需要在 bootstrap.yml 中配置密钥属性

# 对称密钥

encrypt:
  key: 123456
1
2

注意:目前不知道怎么应用

# 非对称密钥

配置费对称密钥可以使用 keystore (例如,由 JDK 附带的 keytool 实用程序创建)。keystore 的配置属性是 encrypt.keyStore.*, 其中 * 如下表所述

Property Description
encrypt.keyStore.location 包含资源位置
encrypt.keyStore.password 保存解锁密钥存储库的密码
encrypt.keyStore.alias 标识要使用存储中的哪个键
encrypt.keyStore.type 要创建的密钥存储库的类型。默认为 jks

加密是使用公钥进行的,解密需要使用私钥。因此,原则上,如果只想加密(并且准备使用私钥在本地解密值),则只能在服务器中配置公钥。实际上,您可能不希望在本地进行解密,因为它将关键管理过程分散到所有客户端,而不是集中在服务器上。

# 使用 keytool 创建用于测试的密钥

keytool -genkeypair -alias mytestkey -keyalg RSA \
  -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" \
  -keypass changeme -keystore server.jks -storepass letmein
1
2
3
  • genkeypair

  • alias : 可以是任意字符,只要不提示错误即可。因一个密钥库中可以存放多个密钥。

  • keyalg: 指定密钥的算法。可以选择的密钥算法有:RSA、DSA、EC。

  • dname :

    指定证书拥有者信息 例如: "CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"

  • keypass:密钥密码(私钥的密码)

  • keystore: 密钥库名称。可给出绝对路径。默认在当前目录创建证书库。

  • storepass:密钥库名称密码(私钥的密码)

$ keytool -genkeypair -alias mytestkey -keyalg RSA \
  -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" \
  -keypass changeme -keystore server.jks -storepass letmein

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore server.jks -destkeystore server.jks -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。
1
2
3
4
5
6

虽然有一个警告,但是还是生成了;

在 config server bootstrap.yml 中添加以下配置

encrypt:
  keyStore:
    location: classpath:/server.jks  # 把刚才生成的 jks 文件放在 resource 下,这样写才能被找到
    password: letmein # 对应 storepass
    alias: mytestkey
    secret: changeme # 对应 keypass
1
2
3
4
5
6

# 加密值,并配置在 yml 中 实践

首先加密一个值

# 加密结果存储在文件中,因为我尝试好几次都说解密失败,可能是在控制台复制出现了问题
# 结果是一个 Base64 的字符串
curl localhost:11000/encrypt -d mysecret > aa.txt
1
2
3

除了加密属性值中的 {cipher} 前缀外,配置服务器还在(Base64 编码的)密码文本开始之前查找零个或多个 {name:value} 前缀。 密钥被传递给 TextEncryptorLocator,它可以执行为密码定位 TextEncryptor 所需的任何逻辑。 如果您配置了密钥存储库 (encryption.keystore.location),默认定位器将查找密钥前缀提供别名的密钥,并使用类似于下面这样的密文:

spring:
  cloud:
    config:
      server:
        overrides:
          foo: bar
          pwd: '{cipher}{key:mytestkey}AQAn3u819EQ/5aRWAAi5ZyOOeaQSfW5uCCkYHaUbGNKbsg1x9UgLQTsPBLU72hUhOLzGG9/qrHvGQ4w3+dPRZLlHryeAmrtj/u9Fw4Np6D4/eU9LWrsl8ak87jiACXEgvDsEzrlhWTk6P7uhPYWhzeL8Ymweus30AZfHEHwLREgKZmIn24L9e9IFr0BX5G1b5kl1jeaHrwOYEXvdv8Q+lqFvf4EliQAwLzqVZlXLCK/uQtUbK0gZQsNo04/T2WIsWuv6GSAs96jjPpdrQgHuEUr8NEe2Uoa+L2MizqUlzVEQC3TKplXTH0lybKfMngZxDO6Vj78NJmoiz+RcPXc6oYOvbXEVj96VLW96S4WmlNcaORYwrCYT5/mxZI4vj9PiqD8='
1
2
3
4
5
6
7

kye:mytestkey 是我们刚生成的密钥存储库中的别名(alias);稍微跟踪了一下代码,在项目启动时,会使用 别名找到密码进行解密; 如果 key 或者密码文本有问题导致不能解密,将在启动界面报错

在客户端打印 pwd 属性,会发现加密文本传递到客户端之后密码的确先被解密了

When the keys are being used only to encrypt a few bytes of configuration data (that is, they are not being used elsewhere), key rotation is hardly ever necessary on cryptographic grounds. However, you might occasionally need to change the keys (for example, in the event of a security breach). In that case, all the clients would need to change their source config files (for example, in git) and use a new {key:…​} prefix in all the ciphers. Note that the clients need to first check that the key alias is available in the Config Server keystore.

上面这段话没有看明白是什么意思;个人理解是在更改密钥库密码之后,加密文本需要重新加密,客户端需要重新获取加密文本

# 加密服务属性 - 客户端自己解密属性

通过 bootstrap.[yml|properties] 中的 spring.cloud.config.server.encrypt.enabled=false 属性来控制是否将在发送到客户端之前启用对环境属性的解密。

spring:
  cloud:
    config:
      server:
        encrypt:
          enabled: false  # 默认为 true,解密后传输到客户端
1
2
3
4
5
6

经过尝试之后,该属性没有任何效果。始终都会解密后发给客户端