Apache Commons Crypto 常用 CLI 命令与工具详解
以命令行实操演示 Apache Commons Crypto 1.2.0:javac/java -cp 与 jshell 用法、AES 三模式文件加解密工具完整源码与实测输出、OpenSSL 检测及性能对比。
Apache Commons Crypto 常用 CLI 命令与工具详解
目标:不依赖 IDE,只用 javac、java -cp 与 jshell 在服务器上完成 Commons Crypto 的编译、运行、文件加解密与性能观察,所有命令、源码与回显均为实测,已在 Ubuntu 22.04 / CentOS 8(JDK 17 + Maven)实测通过。
一、环境信息
本文所有命令与回显来自以下两个环境,二者构成「JCE 路径」与「OpenSSL 路径」的对照:
| 项目 | 环境1 | 环境2 |
|---|---|---|
| 操作系统 | Ubuntu 22.04.5 LTS | CentOS Linux 8(8.4.2105) |
| JDK | OpenJDK 17.0.20.1 | OpenJDK 17.0.1 |
| Maven | 3.6.3 | 3.5.4 |
| OpenSSL | 3.0.2 | 1.1.1k FIPS |
| 加密实现 | 加密回退 JceCipher、随机数走 OpenSSL | 加密与随机数均走 OpenSSL |
| 组件 | Apache Commons Crypto 1.2.0(commons-crypto-1.2.0.jar,176035 字节) | 同左 |
工作目录统一使用 /opt/cc-lab,jar 放在其中:
mkdir -p /opt/cc-lab && cd /opt/cc-lab && cp /opt/commons-crypto-1.2.0/commons-crypto-1.2.0.jar . && ls -l
回显(实测):
total 176
-rw-r--r-- 1 root root 176035 Oct 13 07:40 commons-crypto-1.2.0.jar
二、javac 与 java -cp 基础
命令行使用 Commons Crypto 的核心只有两条规则:编译与运行都要挂 classpath;Linux 分隔符是冒号。先看最常见的错误——编译时漏挂 jar:
javac Probe1.java
回显(实测,节选):
Probe1.java:1: error: package org.apache.commons.crypto does not exist
import org.apache.commons.crypto.Crypto;
^
补上 -cp 即可(-encoding UTF-8 用于 POSIX locale 下带中文注释的源码):
javac -encoding UTF-8 -cp commons-crypto-1.2.0.jar Probe1.java && echo JAVAC_OK
回显(实测):
JAVAC_OK
运行时同样要挂 jar,且类路径分隔符是冒号(Windows 才是分号):
java -cp .:commons-crypto-1.2.0.jar Probe1
回显(实测,Ubuntu 22.04):
component=Apache Commons Crypto-1.2.0
nativeLoaded=true
AES/GCM/NoPadding -> org.apache.commons.crypto.cipher.JceCipher
AES/CTR/NoPadding -> org.apache.commons.crypto.cipher.JceCipher
AES/CBC/PKCS5Padding -> org.apache.commons.crypto.cipher.JceCipher
random -> org.apache.commons.crypto.random.OpenSslCryptoRandom
同一命令在 CentOS 8 上的回显(实测):
component=Apache Commons Crypto-1.2.0
nativeLoaded=true
AES/GCM/NoPadding -> org.apache.commons.crypto.cipher.OpenSslCipher
AES/CTR/NoPadding -> org.apache.commons.crypto.cipher.OpenSslCipher
AES/CBC/PKCS5Padding -> org.apache.commons.crypto.cipher.OpenSslCipher
random -> org.apache.commons.crypto.random.OpenSslCryptoRandom
两个环境一次看清了工厂的自动选择差异,后续工具输出请对照此基准。
💡 技术拓展:本文示例覆盖三种分组加密模式,选型对比:ECB 无 IV、相同明文块得到相同密文块,已不安全,不建议使用;CBC 需随机 IV、串行处理、PKCS5 填充使密文膨胀到块大小的整数倍;CTR 把分组密码变成流密码、无填充、可并行,但同一 IV 绝不能加密两份数据;GCM 在 CTR 基础上附加认证标签(默认 128 bit),可检测篡改,是当前推荐模式。实测三者对同一 86 字节明文的密文长度:GCM 102 字节(含 16 字节 tag)、CTR 86 字节(无膨胀)、CBC 96 字节(填充膨胀)。
三、文件加解密小工具 FileCrypto
本节给出一个完整可用的命令行文件加解密工具:支持 GCM、CTR、CBC 三种模式,密钥与 IV 以 hex 传入,一次 doFinal 完成加解密。
3.1 完整源码
import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.cipher.CryptoCipherFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Properties;
/**
* 文件加解密小工具(示例程序):
* FileCrypto <enc|dec> <transformation> <keyHex> <ivHex> <infile> <outfile>
*/
public class FileCrypto {
private static byte[] hexToBytes(String hex) {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException("hex length must be even: " + hex.length());
}
byte[] out = new byte[hex.length() / 2];
for (int i = 0; i < out.length; i++) {
out[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return out;
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
if (args.length != 6) {
System.err.println("Usage: FileCrypto <enc|dec> <transformation> <keyHex> <ivHex> <infile> <outfile>");
System.err.println(" transformation e.g. AES/GCM/NoPadding | AES/CTR/NoPadding | AES/CBC/PKCS5Padding");
System.exit(2);
}
String op = args[0];
String transformation = args[1];
byte[] key = hexToBytes(args[2]);
byte[] iv = hexToBytes(args[3]);
if (key.length != 16 && key.length != 24 && key.length != 32) {
throw new IllegalArgumentException("AES key must be 16/24/32 bytes, got " + key.length);
}
byte[] input = Files.readAllBytes(Paths.get(args[4]));
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
String mode = transformation.split("/")[1];
AlgorithmParameterSpec spec;
if ("GCM".equals(mode)) {
spec = new GCMParameterSpec(128, iv); // GCM: tag 长度 128 bit + nonce
} else {
spec = new IvParameterSpec(iv); // CBC/CTR: 普通 IV
}
Properties props = new Properties();
CryptoCipher cipher = CryptoCipherFactory.getCryptoCipher(transformation, props);
System.out.println("cipher impl: " + cipher.getClass().getName());
cipher.init("enc".equals(op) ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec, spec);
ByteBuffer inBuf = ByteBuffer.allocateDirect(input.length);
inBuf.put(input);
inBuf.flip();
ByteBuffer outBuf = ByteBuffer.allocateDirect(input.length + 32); // 预留 tag/padding 空间
cipher.doFinal(inBuf, outBuf);
outBuf.flip();
byte[] output = new byte[outBuf.remaining()];
outBuf.get(output);
cipher.close();
Files.write(Paths.get(args[5]), output);
PrintStream out = System.out;
out.println(op + " ok: " + args[4] + " (" + input.length + " B) -> " + args[5] + " (" + output.length + " B)");
if ("enc".equals(op)) {
out.println("ciphertext(hex, first 32): " + bytesToHex(output).substring(0, Math.min(64, output.length * 2)));
}
}
}
源码要点:模式常量取自 javax.crypto.Cipher(Commons Crypto 的 CryptoCipher 接口本身没有 ENCRYPT_MODE 常量);GCM 必须传 GCMParameterSpec,CBC/CTR 传 IvParameterSpec;输出缓冲比明文多预留 32 字节,容纳 tag 与填充。
3.2 编译与三模式加解密
准备明文与固定密钥、IV(示例固定值,生产环境请通过密钥管理系统下发):
cd /opt/cc-lab && printf "Hello Apache Commons Crypto 1.2.0! This line is encrypted with AES on a Linux server.\n" > plain.txt && wc -c plain.txt
回显(实测):
86 plain.txt
GCM 加解密(密钥为 32 字节 AES-256,IV 为 12 字节 nonce):
KEY=603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4
java -cp .:commons-crypto-1.2.0.jar FileCrypto enc AES/GCM/NoPadding $KEY 000102030405060708090a0b plain.txt plain-gcm.enc
java -cp .:commons-crypto-1.2.0.jar FileCrypto dec AES/GCM/NoPadding $KEY 000102030405060708090a0b plain-gcm.enc plain-gcm.dec
cmp plain.txt plain-gcm.dec && echo GCM_ROUNDTRIP_OK
回显(实测,Ubuntu 22.04):
cipher impl: org.apache.commons.crypto.cipher.JceCipher
enc ok: plain.txt (86 B) -> plain-gcm.enc (102 B)
ciphertext(hex, first 32): 4004dd3feb64da0ec7c32accad7df6862813e8918226ebaaa8c393c0588a278d
cipher impl: org.apache.commons.crypto.cipher.JceCipher
dec ok: plain-gcm.enc (102 B) -> plain-gcm.dec (86 B)
GCM_ROUNDTRIP_OK
CTR 与 CBC(IV 为 16 字节):
java -cp .:commons-crypto-1.2.0.jar FileCrypto enc AES/CTR/NoPadding $KEY f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff plain.txt plain-ctr.enc
java -cp .:commons-crypto-1.2.0.jar FileCrypto enc AES/CBC/PKCS5Padding $KEY 000102030405060708090a0b0c0d0e0f plain.txt plain-cbc.enc
ls -l plain-ctr.enc plain-cbc.enc
回显(实测):
cipher impl: org.apache.commons.crypto.cipher.JceCipher
enc ok: plain.txt (86 B) -> plain-ctr.enc (86 B)
ciphertext(hex, first 32): 43ba119d363757433ff9e370e823aa6f370107ee73226b7f2447e91cbe4b49ba
cipher impl: org.apache.commons.crypto.cipher.JceCipher
enc ok: plain.txt (86 B) -> plain-cbc.enc (96 B)
ciphertext(hex, first 32): aec82a22c76025d499761e30bbdabeb0f798ee0c12ee469305527e749933bd41
-rw-r--r-- 1 root root 96 Oct 13 07:56 plain-cbc.enc
-rw-r--r-- 1 root root 86 Oct 13 07:56 plain-ctr.enc
密文长度印证 2 节拓展表:CTR 无膨胀、CBC 膨胀到 96、GCM 含 tag 为 102。
3.3 跨实现互操作与 openssl 交叉验证
把 Ubuntu 上(JceCipher 路径)加密的 GCM 文件拿到 CentOS 8(OpenSslCipher 路径)解密:
java -cp .:commons-crypto-1.2.0.jar FileCrypto dec AES/GCM/NoPadding $KEY 000102030405060708090a0b u22-gcm.enc u22-gcm.dec
回显(实测,CentOS 8):
cipher impl: org.apache.commons.crypto.cipher.OpenSslCipher
dec ok: u22-gcm.enc (102 B) -> u22-gcm.dec (86 B)
Hello Apache Commons Crypto 1.2.0! This line is encrypted with AES on a Linux server.
更重要的是:两种实现产出的密文逐字节相同(同密钥同 IV 下 GCM 均为 4004dd3f 开头、CTR 均为 43ba119d 开头)。再用系统 openssl 命令行对 CTR 全文做交叉验证:
od -An -tx1 plain-ctr.enc | tr -d " \n"; echo
printf "Hello Apache Commons Crypto 1.2.0! This line is encrypted with AES on a Linux server.\n" | openssl enc -aes-256-ctr -K 603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4 -iv f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff | od -An -tx1 | tr -d " \n"; echo
回显(实测,两行完全一致):
43ba119d363757433ff9e370e823aa6f370107ee73226b7f2447e91cbe4b49ba2be00cc869087e7d61e2b8c617e7b9424c3882ba104542d4dac9eb1ad77d56f7ce24df8fb75c68b29b9e606991efb0d23e9d02f04e0c
43ba119d363757433ff9e370e823aa6f370107ee73226b7f2447e91cbe4b49ba2be00cc869087e7d61e2b8c617e7b9424c3882ba104542d4dac9eb1ad77d56f7ce24df8fb75c68b29b9e606991efb0d23e9d02f04e0c
结论:Commons Crypto 的 JCE 路径、OpenSSL 路径与系统 openssl 三者输出完全互通,可放心混用。
四、jshell 交互实操
jshell 适合快速验证 API 行为。把以下内容保存为 demo.jsh:
import org.apache.commons.crypto.cipher.CryptoCipherFactory
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import javax.crypto.spec.IvParameterSpec
import java.nio.ByteBuffer
import java.util.HexFormat
import java.util.Properties
var cipher = CryptoCipherFactory.getCryptoCipher("AES/CTR/NoPadding", new Properties())
System.out.println("impl : " + cipher.getClass().getName())
// 注意:IV 必须传原始字节;这里用 HexFormat.parseHex 解析 hex 字符串
// (常见错误:直接传 "ffffffff..." 字符串,实际得到 16 个 0x66 而不是 16 个 0xFF)
var key = new SecretKeySpec("0123456789abcdef".getBytes("UTF-8"), "AES")
var iv = new IvParameterSpec(HexFormat.of().parseHex("ffffffffffffffffffffffffffffffff"))
cipher.init(Cipher.ENCRYPT_MODE, key, iv)
var in = ByteBuffer.allocateDirect(16)
in.put("Attack at dawn!!".getBytes("UTF-8"))
in.flip()
var out = ByteBuffer.allocateDirect(32)
cipher.doFinal(in, out)
out.flip()
var ct = new byte[out.remaining()]
out.get(ct)
System.out.println("cipher: " + HexFormat.of().formatHex(ct))
cipher.close()
/exit
运行(脚本内显式 println,因为管道模式下 jshell 默认不回显表达式值):
jshell --class-path commons-crypto-1.2.0.jar demo.jsh
回显(实测,Ubuntu 22.04):
impl : org.apache.commons.crypto.cipher.JceCipher
cipher: 70960b1d58fafc5007cf33de38071dca
与 openssl 命令行结果一致(同密钥同 IV):
printf "Attack at dawn!!" | openssl enc -aes-128-ctr -K 30313233343536373839616263646566 -iv ffffffffffffffffffffffffffffffff | od -An -tx1 | tr -d " \n"; echo
回显(实测):
70960b1d58fafc5007cf33de38071dca
jshell 三个实测踩坑记录:一是不要加 --execution local,实测会报 NoClassDefFoundError: org/apache/commons/crypto/cipher/CryptoCipherFactory(classpath 未传递给执行引擎);二是 -q 静默模式下表达式值不回显,脚本里要用 System.out.println;三是 IvParameterSpec 构造参数是原始字节,直接传 "ffffffffffffffff" 字符串得到的是 16 个 0x66(字符 f 的 ASCII),实测这条路线的密文与 openssl 不一致,就是 IV 字节含义不同导致的。
五、OpenSSL 与 native 环境检测
排查前先弄清系统 OpenSSL 形态与 Commons Crypto 的 native 加载状态。
5.1 系统 OpenSSL 版本与库文件
openssl version && ldconfig -p | grep libcrypto
回显(实测,Ubuntu 22.04):
OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
libcrypto.so.3 (libc6,x86-64) => /lib/x86_64-linux-gnu/libcrypto.so.3
libcrypto.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libcrypto.so
回显(实测,CentOS 8):
OpenSSL 1.1.1k FIPS 25 Mar 2021
libcrypto.so.1.1 (libc6,x86-64) => /lib64/libcrypto.so.1.1
libcrypto.so (libc6,x86-64) => /lib64/libcrypto.so
关键检查点:是否存在未带版本号的 libcrypto.so。缺失时 Commons Crypto 随机数 native 初始化会抛 UnsatisfiedLinkError: Cannot load libcrypto.so 并直接崩溃(工厂不回退 Error)。
5.2 native 加载过程追踪
java -Dcommons.crypto.debug=true -cp .:commons-crypto-1.2.0.jar Probe1 2>&1 | grep -E "Extract|System.load|libraryLoaded"
回显(实测):
Extracting '/org/apache/commons/crypto/native/Linux/x86_64/libcommons-crypto.so' to '/tmp/commons-crypto-ad3c9409-4d1e-44b9-8b9e-ae8f728d484d-libcommons-crypto.so'...
NativeCodeLoader System.load('/tmp/commons-crypto-ad3c9409-4d1e-44b9-8b9e-ae8f728d484d-libcommons-crypto.so')
NativeCodeLoader libraryLoaded = true, libraryLoadingError = null
可见 jar 内 22,552 字节的胶水库被解压到临时目录后加载,文件在 JVM 退出时自动删除。
5.3 加密路径可用性检测
EnvCheck(见 2 节回显)能区分两种实现。若想强制只用 OpenSSL 实现并拿到失败原因,把 commons.crypto.cipher.classes 设为仅 OpenSslCipher:
openssl-only FAILED: CryptoCipher {org.apache.commons.crypto.cipher.OpenSslCipher} is not available or transformation AES/CTR/NoPadding is not supported.
caused by: java.lang.IllegalStateException: java.lang.UnsatisfiedLinkError: EVP_CIPHER_CTX_block_size
Ubuntu 22.04 的 OpenSSL 3.0 缺少该 JNI 符号,因此加密固定回退 JCE;CentOS 8 的 OpenSSL 1.1.1 则完整可用。
💡 技术拓展:三种加密实现路径的吞吐量级差异显著——JCE 走 JVM 内置代码,JDK 17 在 AES-NI 硬件上有内建优化;OpenSSL JNI 直接调用 libcrypto 的汇编优化实现,长数据场景通常最快;JNA 无需编译但每次调用有映射开销,吞吐最低,仅适合低频调用。下节的实测数据量化了前两者的差距。
六、JCE 与 OpenSSL 性能对比
用同一个程序分别强制两条实现路径,测 AES-CTR 与 AES-GCM 的加密吞吐。完整源码(Perf.java):
import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.cipher.CryptoCipherFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Properties;
/**
* JCE 与 OpenSSL(JNI) 两条实现路径的吞吐对比(功能验证参考,非生产基准)。
* 用法: Perf <transformation> <dataMB> [jce|openssl|default]
*/
public class Perf {
private static final SecureRandom RND = new SecureRandom();
public static void main(String[] args) throws Exception {
String transformation = args.length > 0 ? args[0] : "AES/CTR/NoPadding";
int dataMB = args.length > 1 ? Integer.parseInt(args[1]) : 128;
String impl = args.length > 2 ? args[2] : "default";
Properties props = new Properties();
if ("jce".equals(impl)) {
props.setProperty(CryptoCipherFactory.CLASSES_KEY,
CryptoCipherFactory.CipherProvider.JCE.getClassName());
} else if ("openssl".equals(impl)) {
props.setProperty(CryptoCipherFactory.CLASSES_KEY,
CryptoCipherFactory.CipherProvider.OPENSSL.getClassName());
}
int size = dataMB * 1024 * 1024;
byte[] data = new byte[size];
RND.nextBytes(data);
byte[] keyBytes = new byte[16];
RND.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
CryptoCipher cipher = CryptoCipherFactory.getCryptoCipher(transformation, props);
System.out.println("transformation=" + transformation + ", impl=" + cipher.getClass().getName()
+ ", data=" + dataMB + " MB");
int chunk = 64 * 1024;
ByteBuffer in = ByteBuffer.allocateDirect(chunk);
ByteBuffer out = ByteBuffer.allocateDirect(chunk + 16);
// 预热 16 MB(GCM 每次加密必须换新 nonce,否则 JDK 17 SunJCE 抛 InvalidAlgorithmParameterException)
cipher.init(Cipher.ENCRYPT_MODE, key, newSpec(transformation));
for (int off = 0; off < 16 * 1024 * 1024; off += chunk) {
encryptChunk(cipher, data, off, chunk, in, out);
}
long t0 = System.nanoTime();
cipher.init(Cipher.ENCRYPT_MODE, key, newSpec(transformation));
for (int off = 0; off < size; off += chunk) {
encryptChunk(cipher, data, off, chunk, in, out);
}
long t1 = System.nanoTime();
cipher.close();
double seconds = (t1 - t0) / 1_000_000_000.0;
System.out.printf("encrypt %d MB in %.3f s -> %.1f MB/s%n", dataMB, seconds, dataMB / seconds);
}
/** GCM 用 12 字节 nonce 的 GCMParameterSpec,CBC/CTR 用 16 字节 IvParameterSpec;每次调用生成新随机值 */
private static AlgorithmParameterSpec newSpec(String transformation) {
boolean gcm = transformation.contains("GCM");
byte[] iv = new byte[gcm ? 12 : 16];
RND.nextBytes(iv);
return gcm ? new GCMParameterSpec(128, iv) : new IvParameterSpec(iv);
}
private static void encryptChunk(CryptoCipher cipher, byte[] data, int off, int len,
ByteBuffer in, ByteBuffer out) throws Exception {
in.clear();
in.put(data, off, len);
in.flip();
out.clear();
cipher.update(in, out);
long residue = in.remaining();
if (residue != 0) {
throw new IllegalStateException("input not fully consumed: " + residue);
}
out.clear();
}
}
javac -encoding UTF-8 -cp commons-crypto-1.2.0.jar Perf.java
java -Xmx512m -cp .:commons-crypto-1.2.0.jar Perf AES/CTR/NoPadding 128 openssl
回显(实测,CentOS 8):
transformation=AES/CTR/NoPadding, impl=org.apache.commons.crypto.cipher.OpenSslCipher, data=128 MB
encrypt 128 MB in 0.034 s -> 3774.9 MB/s
其余三组实测:
transformation=AES/CTR/NoPadding, impl=org.apache.commons.crypto.cipher.JceCipher, data=128 MB
encrypt 128 MB in 0.090 s -> 1416.0 MB/s
transformation=AES/GCM/NoPadding, impl=org.apache.commons.crypto.cipher.OpenSslCipher, data=128 MB
encrypt 128 MB in 0.046 s -> 2809.3 MB/s
transformation=AES/GCM/NoPadding, impl=org.apache.commons.crypto.cipher.JceCipher, data=128 MB
encrypt 128 MB in 0.700 s -> 182.9 MB/s
Ubuntu 22.04 上(加密只有 JCE 可用)的对照实测:
transformation=AES/CTR/NoPadding, impl=org.apache.commons.crypto.cipher.JceCipher, data=128 MB
encrypt 128 MB in 0.080 s -> 1599.7 MB/s
transformation=AES/GCM/NoPadding, impl=org.apache.commons.crypto.cipher.JceCipher, data=128 MB
encrypt 128 MB in 0.729 s -> 175.5 MB/s
汇总(128MB 数据、64KB 分块、单次预热 16MB):
| 环境 | 模式 | 实现 | 吞吐 |
|---|---|---|---|
| CentOS 8 | AES-CTR | OpenSslCipher | 3774.9 MB/s |
| CentOS 8 | AES-CTR | JceCipher | 1416.0 MB/s |
| CentOS 8 | AES-GCM | OpenSslCipher | 2809.3 MB/s |
| CentOS 8 | AES-GCM | JceCipher | 182.9 MB/s |
| Ubuntu 22.04 | AES-CTR | JceCipher | 1599.7 MB/s |
| Ubuntu 22.04 | AES-GCM | JceCipher | 175.5 MB/s |
逐项说明:CTR 下 OpenSSL 路径约为 JCE 的 2.7 倍;GCM 差距最大(约 15 倍),因为这台 CentOS 8 的旧版 SunJCE 未启用 GCM 向量化优化。以上数据为功能验证参考,非生产基准——绝对值受 CPU、内核、JDK 小版本与内存频率影响,生产选型请以目标硬件上的复测为准。
补充一个失败场景实测:在 Ubuntu 22.04 上强制 openssl 路径会直接抛异常终止:
Exception in thread "main" java.security.GeneralSecurityException: CryptoCipher {org.apache.commons.crypto.cipher.OpenSslCipher} is not available or transformation AES/CTR/NoPadding is not supported.
at org.apache.commons.crypto.cipher.CryptoCipherFactory.getCryptoCipher(CryptoCipherFactory.java:199)
另有一个实测 API 约束:Commons Crypto 的 OpenSSL GCM 实现要求 GCMParameterSpec,传 IvParameterSpec 会抛 InvalidAlgorithmParameterException: Illegal parameters;而 JDK 17 SunJCE 的 GCM 在同一 Cipher 实例上重复 init 复用旧 nonce 会抛 InvalidAlgorithmParameterException: Cannot reuse iv for GCM encryption,所以 Perf 每次加密前都生成新 nonce。
七、参考链接
- Apache Commons Crypto 官方主页:https://commons.apache.org/proper/commons-crypto/
- 官方用户指南:https://commons.apache.org/proper/commons-crypto/userguide.html
- Javadoc 在线文档:https://commons.apache.org/proper/commons-crypto/javadocs/api-release/index.html
- OpenSSL 官方文档:https://www.openssl.org/docs/
- 项目源码仓库:https://github.com/apache/commons-crypto