2. 开发环境与密码库选型

密码 API 库是应用与底层密码算法之间的“适配层”。同一段业务逻辑,换一个库、换一个平台,接口形态、依赖方式、默认参数甚至可用算法集合都可能不同。本节围绕 OpenSSL、Bouncy Castle、Web Crypto、JCE 四类主流密码 API,讲清它们各自“是什么、装什么、怎么配、跨平台要注意什么”,并给出可复现的安装与验证示例。

四类库的定位差异

先建立选型坐标系。OpenSSL 是 C 语言实现的底层密码库,同时提供命令行工具与 libcrypto/libssl 编程接口,是 Linux 服务器、Nginx、curl 等大量基础设施的密码底座;Bouncy Castle 是 Java/.NET 生态的纯实现密码库,最大价值在于补齐 JDK 未内置的算法(如国密 SM2/SM3/SM4、Ed25519 早期版本、部分 PGP 能力);Web Crypto 是浏览器与部分运行时内置的 JavaScript 密码 API,以 crypto.subtle 为核心,特点是算法受限但密钥不可导出、运行在受控环境;JCE(Java Cryptography Extension)是 JDK 自带的密码框架,通过 Provider 机制组织算法,SunJCE、SunRsaSign 等是默认 Provider。

理解这四者的关系,关键在“谁提供算法、谁提供接口”。JCE 提供统一接口,Bouncy Castle 作为 Provider 插入 JCE;OpenSSL 自成体系,Java 侧通过 JNI 或 Netty-tcnative 调用;Web Crypto 则是浏览器把密码能力封装成 Promise 风格 API。选型时先问三个问题:目标运行平台是什么、需要哪些算法、密钥是否需要持久化导出。

四类密码 API 库的层次与依赖关系 应用 / 业务代码 Java 服务 · 浏览器前端 · Node 服务 · C/C++ 服务 · 网关 JCE 框架 统一接口 + Provider Cipher / Signature / KeyStore Web Crypto crypto.subtle Promise 风格 · 密钥不可导出 Bouncy Castle 纯实现 Provider SM2/SM3/SM4 · PGP · Ed25519 OpenSSL libcrypto / libssl 命令行 + C API 底层实现与运行环境 操作系统密码模块 · CPU 指令加速(AES-NI / SHA-NI)· 硬件安全模块 HSM · 浏览器沙箱 选型判断要点 运行平台:JVM 优先 JCE + Bouncy Castle;浏览器优先 Web Crypto;C/C++ 与网关优先 OpenSSL 算法需求:国密、PGP、Ed25519 等 JDK 未内置算法需引入 Bouncy Castle 密钥管理:Web Crypto 密钥默认不可导出,适合浏览器端会话密钥;服务端密钥需 KeyStore 或 HSM 依赖形态:OpenSSL 为系统级动态库,Bouncy Castle 为 jar 包,Web Crypto 为运行时内置,JCE 随 JDK 发布

OpenSSL:系统级依赖与版本陷阱

OpenSSL 的安装方式因平台而异。Linux 上通常由发行版包管理器提供,例如 Ubuntu/Debian 执行 sudo apt install libssl-dev,CentOS/RHEL 执行 sudo yum install openssl-devel;macOS 可用 Homebrew 安装 brew install openssl@3,但需注意系统自带的 LibreSSL 与 Homebrew 的 OpenSSL 路径不同;Windows 官方不提供二进制包,常用 vcpkg 或第三方构建。

安装后必须验证版本与算法支持,因为 OpenSSL 1.1.1 与 3.x 在 Provider 机制、默认算法、废弃 API 上有明显差异:

openssl version -a
openssl list -digest-algorithms | grep -i sm3
openssl list -cipher-algorithms | grep -i sm4
openssl ecparam -list_curves | grep -i sm2

若输出中找不到 SM3/SM4/SM2,说明当前构建未启用国密支持,需要重新编译并加入 enable-sm2 enable-sm3 enable-sm4。跨平台时最常见的坑是“编译期头文件版本与运行期动态库版本不一致”:编译链接了 OpenSSL 3.0 的头文件,运行时却加载了 1.1.1 的 libcrypto.so,会出现符号缺失或行为异常。排查方法是 ldd 查看实际加载路径,并确认 LD_LIBRARY_PATH 与 rpath 设置。

Bouncy Castle:Provider 注册与依赖管理

Bouncy Castle 在 Java 侧以 jar 包形式引入。Maven 依赖通常包含两个坐标:bcprov-jdk18on 提供基础算法,bcpkix-jdk18on 提供证书与 PKCS 相关能力。版本号需与 JDK 匹配,jdk18on 后缀表示面向 JDK 1.8 及以上。

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk18on</artifactId>
  <version>1.78.1</version>
</dependency>

引入后有两种注册方式。静态注册是在 java.security 文件中添加 security.provider.N=org.bouncycastle.jce.provider.BouncyCastleProvider;动态注册是在代码中调用 Security.addProvider(new BouncyCastleProvider())。动态注册更灵活,但要注意注册顺序:JCE 按 Provider 顺序查找算法,若 SunJCE 已提供同名算法,Bouncy Castle 可能不会被命中。需要强制使用 Bouncy Castle 时,应在 Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC") 中显式指定 Provider 名称。

依赖管理上要警惕“同名类冲突”。Bouncy Castle 的 bcprov 与某些旧版 bcprov-ext、或与其他库内嵌的 BC 类同时存在时,可能出现 NoSuchMethodError。用 mvn dependency:tree 检查是否存在多个 BC 版本,必要时用 exclusion 排除传递依赖。

Web Crypto:浏览器内置但边界明确

Web Crypto 无需安装,通过 window.crypto.subtle 访问。它的核心约束是:只能在安全上下文(HTTPS 或 localhost)使用;密钥对象默认不可导出,generateKey 生成的密钥若设置 extractable: false,则无法通过 exportKey 取出原始字节;算法集合由浏览器实现决定,AES-GCM、RSA-OAEP、ECDSA、ECDH、HMAC、SHA 系列支持较好,但国密算法通常不在标准支持范围内。

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  false,               // 不可导出
  ["encrypt", "decrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  key,
  new TextEncoder().encode("hello")
);

跨平台注意点集中在三处:一是 Node.js 的 webcrypto 与浏览器实现存在细微差异,部分算法参数校验更严格;二是密钥不可导出意味着无法把浏览器生成的密钥直接传给服务端,需要走 ECDH 协商或服务端下发包装密钥;三是 crypto.subtle 在非安全上下文下为 undefined,本地调试用 localhost 可绕过,但部署到 HTTP 域名会直接失败。

JCE:Provider 机制与算法可用性

JCE 随 JDK 发布,无需额外安装,但“有 JCE”不等于“有你要的算法”。JDK 8 与 JDK 17 在默认算法、密钥长度限制、Provider 列表上都有变化。查看当前环境可用 Provider 与算法的命令如下:

java -version
# 在代码中打印
for (Provider p : Security.getProviders()) {
    System.out.println(p.getName() + " " + p.getVersion());
}
Cipher.getInstance("AES/GCM/NoPadding"); // 默认走 SunJCE

JDK 8 早期版本对 AES-256 存在管辖权限制,需要安装 JCE Unlimited Strength 策略文件;JDK 8u161 及以后默认放开。跨平台时若同一份代码在开发机(JDK 17)正常、在服务器(JDK 8)报 InvalidKeyException: Illegal key size,基本可定位到策略文件或 JDK 版本差异。此外,Android 的 JCE 实现与标准 JDK 不完全一致,部分算法名称、Provider 行为有差异,移动端需单独验证。

依赖管理与跨平台检查清单

把上述要点收敛成一份可执行的检查清单,便于在项目初始化或环境迁移时逐项确认:

检查项OpenSSLBouncy CastleWeb CryptoJCE
安装方式包管理器 / 源码编译Maven / Gradle 依赖运行时内置随 JDK 发布
版本验证openssl version -amvn dependency:tree浏览器版本与特性检测java -version
算法确认openssl list显式指定 Provider 名subtle 特性探测Security.getProviders()
典型跨平台问题头文件与动态库版本不一致多版本 jar 冲突非安全上下文不可用JDK 版本与策略文件差异
密钥可导出性可导出可导出默认不可导出可导出
选型不是“选最强的”,而是“选与运行环境、算法需求、密钥管理策略最匹配的”。JVM 服务端常见组合是 JCE + Bouncy Castle;浏览器端用 Web Crypto 处理会话级加密;网关与 C/C++ 服务用 OpenSSL;需要国密时,Java 侧靠 Bouncy Castle,C 侧靠启用国密的 OpenSSL 构建。
跨平台部署前,务必在目标环境实际执行一次算法可用性探测,而不是只在开发机验证。开发机与生产机的 JDK 小版本、OpenSSL 构建选项、浏览器内核版本差异,都可能导致同一段代码在两端表现不同。