环境变量(Environment Variables)是操作系统提供的一种机制,用于存储和传递配置信息或敏感数据(如密钥、密码等)。每个进程都可以访问一组环境变量,这些变量在操作系统级别定义,可以被应用程序读取和使用。
在 Spring 应用中,将私钥存入环境变量是一种常见的做法,可以避免将敏感信息硬编码在代码或配置文件中,从而提高安全性。
1. 什么是环境变量?
环境变量是操作系统级别的键值对(Key-Value),用于存储配置信息。例如:
JAVA_HOME
:指向 Java 安装路径。PATH
:定义可执行文件的搜索路径。RSA_PRIVATE_KEY
:存储 RSA 私钥。
环境变量可以在以下场景中使用:
- 操作系统启动时加载。
- 应用程序运行时读取。
- 通过命令行或脚本设置。
2. 如何设置环境变量?
2.1 在 Linux/MacOS 中设置
-
临时设置(仅在当前终端会话有效):
export RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASC..."
-
永久设置(对所有终端会话有效):
- 编辑
~/.bashrc
或~/.zshrc
文件:echo 'export RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASC..."' >> ~/.bashrc source ~/.bashrc
- 编辑
2.2 在 Windows 中设置
-
临时设置(仅在当前命令行窗口有效):
set RSA_PRIVATE_KEY=MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
-
永久设置(对所有命令行窗口有效):
- 打开“系统属性” → “高级” → “环境变量”。
- 在“系统变量”或“用户变量”中添加
RSA_PRIVATE_KEY
。
2.3 在 Docker 中设置
在 docker-compose.yml
或 Docker 命令中设置:
environment:
- RSA_PRIVATE_KEY=MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
3. 在 Spring 中读取环境变量
Spring 提供了多种方式读取环境变量,以下是常用方法:
3.1 使用 @Value
注解
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class RsaKeyConfig {
@Value("${RSA_PRIVATE_KEY}") // 从环境变量注入
private String privateKey;
public String getPrivateKey() {
return privateKey;
}
}
3.2 使用 Environment
对象
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class RsaKeyConfig {
private final Environment env;
public RsaKeyConfig(Environment env) {
this.env = env;
}
public String getPrivateKey() {
return env.getProperty("RSA_PRIVATE_KEY");
}
}
3.3 在 application.properties
中引用环境变量
# application.properties
rsa.private-key=${RSA_PRIVATE_KEY}
然后在代码中读取:
@Value("${rsa.private-key}")
private String privateKey;
4. 环境变量的安全性
优点
- 避免硬编码:私钥不直接写在代码中,降低泄露风险。
- 动态配置:不同环境(开发、测试、生产)可以使用不同的私钥。
- 集中管理:通过操作系统或容器平台统一管理环境变量。
缺点
- 访问控制:需确保只有授权用户和进程可以访问环境变量。
- 泄露风险:如果服务器被入侵,环境变量可能被窃取。
最佳实践
- 限制访问权限:
- 确保只有应用进程可以读取环境变量。
- 使用操作系统权限控制(如 Linux 的
chmod
和chown
)。
- 加密存储:
- 在容器平台(如 Kubernetes)中使用 Secrets 存储环境变量。
- 日志安全:
- 避免在日志中打印环境变量。
5. 完整代码示例
5.1 设置环境变量
# Linux/MacOS
export RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASC..."
# Windows
set RSA_PRIVATE_KEY=MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
5.2 Spring 代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
@Configuration
public class RsaKeyConfig {
@Value("${RSA_PRIVATE_KEY}") // 从环境变量注入
private String privateKeyStr;
private PrivateKey privateKey;
@PostConstruct
public void init() throws Exception {
// 将 Base64 编码的私钥字符串转换为 PrivateKey 对象
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
this.privateKey = keyFactory.generatePrivate(keySpec);
}
public PrivateKey getPrivateKey() {
return privateKey;
}
}
5.3 使用私钥
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
@Service
public class AuthService {
@Autowired
private RsaKeyConfig rsaKeyConfig;
public String decrypt(String encryptedData) throws Exception {
PrivateKey privateKey = rsaKeyConfig.getPrivateKey();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
return new String(decryptedBytes);
}
}
6. 总结
- 环境变量是操作系统级别的键值对,用于存储配置信息或敏感数据。
- 优点:避免硬编码,支持动态配置,集中管理。
- 最佳实践:限制访问权限,加密存储,避免日志泄露。
- 适用场景:开发、测试和生产环境均可使用,适合中小型项目或容器化部署。
- 、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
- 在 Spring 应用中,RSA 私钥的安全性至关重要。如果私钥泄露,攻击者可以解密敏感数据(如用户密码、加密的通信内容等),因此必须遵循严格的私钥管理规范。以下是完整的处理方案和代码实现:
一、私钥存储的常见错误
1. 硬编码在代码中
- 风险:代码提交到 Git 仓库后,私钥可能被泄露。
// ❌ 错误示例:私钥硬编码 String privateKey = "MIIEvQIBADANBgkqhkiGw...";
2. 明文存储在配置文件中
- 风险:配置文件可能被未授权访问。
# ❌ 错误示例:application.properties rsa.private-key=MIIEvQIBADANBgkqhkiGw...
3. 未加密的服务器本地存储
- 风险:服务器被入侵后,私钥可能被窃取。
二、安全的私钥管理方案
方案 1:通过环境变量注入
实现步骤
-
将私钥存入环境变量:
# Linux/MacOS export RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiGw..." # Windows (PowerShell) $env:RSA_PRIVATE_KEY="MIIEvQIBADANBgkqhkiGw..."
-
在 Spring 中读取环境变量:
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; @Configuration public class RsaKeyConfig { @Value("${RSA_PRIVATE_KEY}") // 从环境变量注入 private String privateKeyStr; private PrivateKey privateKey; @PostConstruct public void init() throws Exception { // 将 Base64 编码的私钥字符串转换为 PrivateKey 对象 byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); this.privateKey = keyFactory.generatePrivate(keySpec); } public PrivateKey getPrivateKey() { return privateKey; } }
优点
- 私钥不写入代码或配置文件,依赖部署环境的安全性。
- 适合开发和生产环境。
缺点
- 需确保服务器环境变量不被未授权访问。
方案 2:加密配置文件(Jasypt)
实现步骤
-
添加 Jasypt 依赖:
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>
-
加密私钥:
# 使用 Jasypt 加密私钥 java -cp jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI \ input="MIIEvQIBADANBgkqhkiGw..." \ password=your-secret-key \ algorithm=PBEWithMD5AndDES
-
配置加密后的私钥:
# application.properties rsa.private-key=ENC(加密后的字符串)
-
启动时提供解密密钥:
java -jar your-app.jar -Djasypt.encryptor.password=your-secret-key
-
在代码中解密:
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class RsaKeyConfig { @Value("${rsa.private-key}") private String encryptedPrivateKey; public PrivateKey getPrivateKey() { // 实际场景中,Jasypt 会自动解密配置项 byte[] keyBytes = Base64.getDecoder().decode(encryptedPrivateKey); // 转换为 PrivateKey 对象(同方案1) } }
优点
- 配置文件中的私钥是加密的,即使泄露也无法直接使用。
- 适合需要版本控制配置文件的场景。
方案 3:密钥管理服务(KMS)
实现步骤(以 AWS KMS 为例)
-
将私钥存储在 AWS KMS:
- 通过 AWS 控制台或 SDK 创建一个加密密钥(CMK)。
-
在 Spring 中动态获取私钥:
import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; import com.amazonaws.services.kms.model.DecryptRequest; import java.nio.ByteBuffer; @Component public class AwsKmsService { private final AWSKMS kmsClient = AWSKMSClientBuilder.defaultClient(); public String decryptPrivateKey(byte[] encryptedKey) { DecryptRequest decryptRequest = new DecryptRequest() .withCiphertextBlob(ByteBuffer.wrap(encryptedKey)); ByteBuffer plaintext = kmsClient.decrypt(decryptRequest).getPlaintext(); return new String(plaintext.array(), StandardCharsets.UTF_8); } }
-
使用解密后的私钥:
@Service public class AuthService { @Autowired private AwsKmsService kmsService; public PrivateKey loadPrivateKey() throws Exception { byte[] encryptedKey = // 从数据库或安全存储中加载加密的私钥 String privateKeyStr = kmsService.decryptPrivateKey(encryptedKey); // 转换为 PrivateKey 对象(同方案1) } }
优点
- 私钥由 AWS 托管,支持自动轮换和访问控制。
- 适合云原生应用。
方案 4:硬件安全模块(HSM)
实现步骤
-
将私钥存储在 HSM:
- 使用 HSM 设备(如 SafeNet Luna)生成并存储密钥。
-
通过 HSM 接口访问私钥:
import java.security.KeyStore; import java.security.PrivateKey; public class HsmService { public PrivateKey getPrivateKey() throws Exception { KeyStore keyStore = KeyStore.getInstance("Luna"); keyStore.load(null, null); // 使用 HSM 提供的凭据 return (PrivateKey) keyStore.getKey("my-rsa-key", null); } }
优点
- 硬件级安全,私钥永不离开 HSM。
- 适合金融、政府等高安全场景。
三、最佳实践
- 最小化权限:
- 限制服务器和环境变量的访问权限,仅允许应用进程读取私钥。
- 密钥轮换:
- 定期更换密钥对(如每 3 个月一次),旧密钥仅用于解密历史数据。
- 审计与监控:
- 记录私钥的使用日志,监控异常访问行为。
- 代码审查:
- 使用 Git Hooks 或扫描工具(如 TruffleHog)防止私钥提交到仓库。
四、总结
- 推荐方案:
- 开发环境:环境变量或 Jasypt 加密配置文件。
- 生产环境:AWS KMS、HashiCorp Vault 或 HSM。
- 核心原则:
- 私钥不落地(不硬编码、不明文存储)。
- 依赖硬件或云服务提供商的密钥管理能力。