第3章 加密算法的选型与密钥管理:AES、RSA、密钥存储策略、环境变量注入
3.1 本章知识地图
在编译时加密场景中,算法选择直接决定安全强度与性能开销;密钥管理则决定整个加密体系是否可信。本章聚焦两大对称/非对称算法(AES / RSA),对比其适用场景,并落地 Gradle 环境下的密钥存储与注入方案。
| 知识单元 | 核心要点 | Gradle 侧适配 |
|---|---|---|
| AES 对称加密 | 加解密速度快,密钥需安全分发 | 适合加密静态资源、配置文件 |
| RSA 非对称加密 | 公钥加密、私钥解密,管理成本高 | 适合加密小数据(如 AES 密钥) |
| 密钥存储策略 | .properties / JSON / gradle.properties / 环境变量 | 推荐统一注入方式,避免硬编码 |
| 环境变量注入 | 通过系统环境或 gradle.properties 变量传递密钥 | Task 中读取 System.getenv() 或 project.findProperty |
3.2 AES 与 RSA 的原理选型对比
3.2.1 AES(高级加密标准)
- 算法类型:对称分组密码,密钥长度 128 / 192 / 256 位。
- Gradle 场景:文件级别加密,适合大块数据(如 SQLite 数据库、配置文件)。
- 安全性依赖:密钥派生函数(如 PBKDF2)和随机 IV(初始化向量)。
- 性能:硬件加速下可达数 Gbps,远快于 RSA。
3.2.2 RSA(Rivest‑Shamir‑Adleman)
- 算法类型:非对称公钥密码,模数 2048 / 4096 位。
- Gradle 场景:加密 AES 密钥或小体积凭证(< 200 字节)。
- 安全性依赖:妥善保管私钥,公钥可公开。
- 缺陷:加密速度慢(约 100 KB/s 量级),不适合大块数据。
选型原则:优先使用 AES‑256 加密业务数据,用 RSA‑2048 加密 AES 密钥(混合加密)。编译时环境变量中注入 RSA 私钥(或 AES 密钥),避免将密钥硬编码到版本库。
3.3 密钥存储策略
3.3.1 不应使用的做法
- 在 build.gradle.kts / gradle.properties 中直接写入明文密钥(易被提交到 Git)。
- 在源码 jar 中嵌入密钥文件(反编译即可提取)。
3.3.2 推荐策略
| 存储位置 | 安全性 | 使用方式 | Gradle 获取示例 |
|---|---|---|---|
| 环境变量(CI/CD) | 高(不落入文件系统) | System.getenv("ENCRYPT_KEY") |
|
| gradle.properties(加密后) | 中(需配合 Gradle 加密属性插件) | project.findProperty("secretKey") |
|
| 外部密钥文件(.enc) | 中(需 .gitignore 处理) | Gradle 构建时读取并解密 | |
关键原则:永远不要将密钥明文提交到版本控制系统。CI 环境中通过 Pipeline 变量注入环境变量;本地开发使用 local.properties(已 .gitignore)或 OS 密钥链。
3.4 环境变量注入完整流程
以下步骤演示如何在 Gradle 构建中安全注入 AES‑256 密钥,并对资源文件进行编译时加密。
- 定义密钥环境变量(本地开发):
export AES256_KEY="YouR-SecrEt-K3y1234567890AB==" - 编写 Gradle Task 读取密钥(build.gradle.kts):
import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec val aesKey by tasks.registering { doLast { val keyStr = System.getenv("AES256_KEY") ?: project.findProperty("aes256Key") as? String ?: error("未找到 AES‑256 密钥") println("密钥已加载(长度=${keyStr.length})") } } - 加密资源文件(示例):
val encryptConfig by tasks.registering { dependsOn(aesKey) inputs.file("src/main/resources/config.properties") outputs.file(layout.buildDirectory.file("encrypted-config.properties")) doLast { val keyBytes = (System.getenv("AES256_KEY") ?: error("无密钥")).toByteArray() val key = SecretKeySpec(keyBytes.take(32).toByteArray(), "AES") val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, key) val plaintext = file("src/main/resources/config.properties").readBytes() val ciphertext = cipher.doFinal(plaintext) // 将 IV 与密文拼接(无填充模式) val iv = cipher.iv val encrypted = iv + ciphertext outputs.files.singleFile.writeBytes(encrypted) println("配置已加密 → ${outputs.files.singleFile}") } } - 在 CI 中设置环境变量(GitHub Actions 示例):
jobs: build: steps: - name: 注入密钥 run: echo "AES256_KEY=${{ secrets.AES256_KEY }}" >> $GITHUB_ENV - name: 构建加密资源 run: ./gradlew :encryptConfig
调试技巧:开发阶段可在 gradle.properties 临时添加 aes256Key=DevKey123...,但务必在提交前移除。使用 --info 参数运行任务可观察密钥加载情况(日志中不应打印完整密钥)。
3.5 密钥生命周期管理
- 生成:使用 OpenSSL 或 Java
KeyGenerator生成随机 AES 密钥。 - 轮换:每 90 天更换密钥,通过 CI 变量版本号区分(如
AES_KEY_V1 / V2)。 - 销毁:废弃密钥立即从所有环境变量、配置文件、Secrets 平台中删除。
3.6 常见错误与稳定性优化
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
| Environment variable not found 异常 | CI 与本地变量名不一致 | 统一在 gradle.properties 声明默认占位,强制覆盖 |
| BadPaddingException / AEADBadTagException | 密钥长度不符或 IV 不匹配 | 确保密钥为 32 字节(256 位),AES/GCM 使用随机 IV 存储 |
| 密钥硬编码被提交到 Git | 开发者疏忽 | 启用 Git pre‑commit hook 扫描 gradle.properties 敏感字段 |
| RSA 加密数据过长 | 使用 RSA 加密 > 245 字节数据 | 改用 AES 加密大数据,RSA 仅加密 AES 密钥 |
3.7 密钥注入架构示意
图中展示了三种密钥来源(环境变量、CI Secrets、gradle.properties)统一由 KeyResolver 任务合并,校验长度后传给 Encrypt Task 执行 AES/RSA 加密,最终输出加密产物。CI 环境下仅使用环境变量与 Secrets,避免敏感信息本地持久化。
3.8 本章小结
- AES‑256 是编译时加密首选算法,配合 GCM 模式可提供认证加密。
- RSA 仅用于加密小体积的 AES 密钥,构成混合加密体系。
- 密钥必须通过环境变量或 CI Secrets 注入,禁止硬编码。
- 使用 Gradle Task 将密钥读取与加密逻辑解耦,便于测试和替换算法。