在CloudHSM上用3DES-CBC替代3DES-MAC
本文讲述了在 AWS CloudHSM 上实现 3DES-MAC 的替代方案。由于 3DES 算法强度不足的安全原因,CloudHSM FIPS版本不再支持3DES算法。对于非FIPS版本,CloudHSM Client SDK 5 的 JCE、PKCS#11、OpenSSL 等接口不支持直接 3DES-MAC,但是可执行 3DES-CBC。因此解决方案是调用非FIPS集群的 DESede/CBC/NoPadding 加密、使用全零 IV、应用层自行处理 ISO 9797-1 填充,并截取密文最后 8 字节作为3DES-MAC。这样的效果是等价实现。
一、背景
1、3DES-MAC的作用
3DES-MAC(也称为 DESede MAC、TDES-MAC)是一种基于 Triple DES 分组密码的消息认证码(Message Authentication Code) 算法。它的核心作用是:
- 消息完整性验证 — 确保数据在传输过程中没有被篡改
- 消息来源认证 — 确认消息确实来自持有相同密钥的合法发送方
3DES-MAC 广泛应用于金融和支付行业的遗留系统中,包括:
- EMV 芯片卡交易认证
- ISO 8583 银行间交易报文的 MAC 字段
- SWIFT 银行间报文验证
- POS 终端与银行主机之间的通信认证
需要注意的是,MAC(消息认证码)与数字签名不同。MAC 使用对称密钥,通信双方共享同一把密钥,因此不具备不可否认性。而数字签名使用非对称密钥(私钥签名、公钥验证),任何持有公钥的人都可以验证签名,且签名方无法抵赖。
| 特性 | MAC(消息认证码) | 数字签名 |
|---|---|---|
| 密钥类型 | 对称密钥(双方共享) | 非对称密钥(私钥/公钥) |
| 完整性验证 | 支持 | 支持 |
| 来源认证 | 支持 | 支持 |
| 不可否认性 | 不支持 | 支持 |
2、CloudHSM无论是否FIPS版本均不支持3DES-MAC
AWS CloudHSM Client SDK 5 提供了多种 SDK 接口形式,包括 JCE Provider、PKCS#11、OpenSSL Dynamic Engine 和 OpenSSL Provider。经过对所有 SDK 官方文档的确认,没有任何一种 SDK 原生支持 3DES-MAC。
各 SDK 对 MAC 相关算法的支持情况如下:
| SDK | 支持的 MAC 算法 | 是否支持 3DES-MAC |
|---|---|---|
| JCE Provider | HmacSHA1/224/256/384/512、AES-CMAC | 不支持 |
| PKCS#11 | CKM_SHA_1_HMAC、CKM_SHA224_HMAC、CKM_SHA256_HMAC、CKM_SHA384_HMAC、CKM_SHA512_HMAC、CKM_AES_CMAC | 不支持 |
| OpenSSL Dynamic Engine | 仅支持 RSA/ECDSA 签名验证 | 不支持 |
| OpenSSL Provider | 仅支持 RSA/ECDSA 签名验证 | 不支持 |
各 SDK 对 3DES(DESede)的支持仅限于密钥生成和加密/解密:
| SDK | 3DES 密钥生成 | 3DES 加密/解密 | 3DES MAC |
|---|---|---|---|
| JCE Provider | DESede | DESede/CBC/NoPadding、DESede/CBC/PKCS5Padding、DESede/ECB/NoPadding、DESede/ECB/PKCS5Padding | 不支持 |
| PKCS#11 | CKM_DES3_KEY_GEN | CKM_DES3_CBC、CKM_DES3_ECB、CKM_DES3_CBC_PAD | 不支持 |
| OpenSSL | 不涉及 | 不涉及 | 不支持 |
此外,FIPS 模式与非 FIPS 模式的区别仅影响已列出算法的可用性。根据 NIST 指南,FIPS 模式的集群在 2023 年后禁止使用 3DES 相关操作(包括密钥生成和加密解密);非 FIPS 模式的集群仍允许使用 3DES 加密解密,但同样不提供 3DES-MAC 功能。
3、为何3DES-MAC不再安全
3DES-MAC 基于 Triple DES 算法,而 3DES 本身已被业界认定为不再安全的算法,主要原因包括:
| 安全问题 | 说明 |
|---|---|
| 分组长度短 | 3DES 分组大小仅为 64 bit(8 字节),容易受到生日攻击(Birthday Attack) 和 Sweet32 攻击。当加密数据量达到约 32GB 时,可能泄露明文信息 |
| 有效密钥强度有限 | 3DES 名义上密钥长度为 168 bit(三密钥模式),但因 Meet-in-the-Middle 攻击,实际有效密钥强度仅为 112 bit |
| 性能低下 | 3DES 需要执行 3 次 DES 运算,性能比 AES 慢 3 倍以上 |
| NIST 已正式弃用 | NIST 在 SP 800-131A Rev.2 中明确要求在 2023 年后弃用 3DES,CloudHSM 的 FIPS 模式已遵从该要求 |
NIST(美国国家标准与技术研究院)是 FIPS(联邦信息处理标准)的制定机构。FIPS 140-3 是 NIST 发布的密码模块安全认证标准,AWS CloudHSM 通过了 FIPS 140-3 Level 3 认证。NIST 发布的弃用指南直接影响 FIPS 合规产品的算法支持范围。
对于新系统建设,推荐使用以下替代方案:
| 旧算法 | 推荐替代 | 说明 |
|---|---|---|
| 3DES-MAC (CBC-MAC) | AES-CMAC | NIST SP 800-38B 推荐,CloudHSM 原生支持 |
| 3DES-MAC | HMAC-SHA256 | 基于哈希的 MAC,更通用,CloudHSM 原生支持 |
二、实现
1、3DES-MAC原理
3DES-MAC 本质上就是 CBC-MAC(Cipher Block Chaining MAC) 算法,底层使用 3DES 作为分组密码。其计算过程如下:
明文分块: P1 P2 P3 Pn
| | | |
IV(全零) → XOR ┌→ XOR ┌→ XOR ┌→ XOR
| | | | | | |
3DES | 3DES | 3DES | 3DES
| | | | | | |
C1 ──┘ C2 ──┘ C3 ── ... ┘ Cn ← 这就是 MAC 值
(丢弃) (丢弃) (丢弃) (保留)
具体步骤:
- 将明文按 8 字节(3DES 分组大小)分块:P1, P2, P3, …, Pn
- 如果最后一块不足 8 字节,按照协议要求进行填充
- 第一块 P1 与 IV(全零)进行 XOR,然后用 3DES 密钥加密,得到 C1
- 第二块 P2 与 C1 进行 XOR,然后用 3DES 密钥加密,得到 C2
- 依此类推,直到最后一块
- 最后一个密文块 Cn 就是 MAC 值
可以看出,这个过程与 3DES-CBC 加密完全相同,唯一的区别是:
- CBC 加密:输出所有密文块 C1, C2, …, Cn(用于保密)
- CBC-MAC:只保留最后一个密文块 Cn(用于认证)
2、用3DES-CBC替代3DES-MAC
基于上述原理,在 CloudHSM 上用 3DES-CBC 替代 3DES-MAC 的方法是:
- 自己完成明文填充(按协议要求选择填充方式)
- 将 IV 设为 8 字节全零
- 调用 CloudHSM 的
DESede/CBC/NoPadding执行加密 - 从返回的密文中截取最后 8 字节作为 MAC 值
这个方法在数学上与 3DES-MAC(CBC-MAC)的输出完全等价,不是近似模拟。
关键参数:
| 参数 | 值 | 说明 |
|---|---|---|
| 算法 | DESede (3DES) | 192-bit 密钥(实际有效 112-bit) |
| 模式 | CBC | 密码块链接模式 |
| 填充 | NoPadding | 由应用层自己处理填充,不能让 Cipher 自动填充 |
| IV | 8 字节全零 0x0000000000000000 |
CBC-MAC 标准要求,不能使用随机 IV |
| 分组大小 | 8 字节(64 bit) | DES/3DES 的固定分组长度 |
| MAC 输出 | 密文最后 8 字节(或根据协议截取前 4 字节) | 取决于业务协议要求 |
数据流:
应用程序 CloudHSM
| |
| 填充后的明文 + IV(全零) ----------→ |
| | 用 3DES 密钥做 CBC 加密
| | (密钥始终在 HSM 内部,不会暴露)
|←── 返回完整密文 byte[] ─────────────|
| |
| 在应用程序内存中操作:
| cipherText = [C1 | C2 | C3 | ... | Cn]
| ↑
| 取最后 8 字节 = MAC 值
密文是加密的输出结果,不是密钥材料,CloudHSM 会正常返回给调用方。3DES 密钥始终安全地保留在 HSM 内部,满足硬件安全保护要求。
常见的填充方式:
(1) ISO 9797-1 Padding Method 1(补零)
原始数据: [0x11 0x22 0x33] (3 字节)
填充后: [0x11 0x22 0x33 0x00 0x00 0x00 0x00 0x00] (8 字节)
原始数据: [0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88] (刚好 8 字节)
填充后: [0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88] (不填充)
特点:如果数据刚好是 8 的倍数,不追加额外块。
(2) ISO 9797-1 Padding Method 2(先补 0x80 再补零)
原始数据: [0x11 0x22 0x33] (3 字节)
填充后: [0x11 0x22 0x33 0x80 0x00 0x00 0x00 0x00] (8 字节)
原始数据: [0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88] (刚好 8 字节)
填充后: [0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 (追加完整块)
0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00]
特点:始终追加 0x80 标记,即使数据已对齐也要加一个完整填充块。EMV 支付常用此方式。
三、JCE Provider实现
1、创建3DES密钥
在 CloudHSM JCE Provider 中,使用 KeyGenerator 生成 3DES 密钥。密钥生成操作在 HSM 内部完成,密钥不会离开 HSM。
注意:在 FIPS 模式的 CloudHSM 集群中,2023 年后不允许生成 3DES 密钥。此功能仅在非 FIPS 模式的集群中可用。
import javax.crypto.KeyGenerator;
import java.security.Key;
// 指定 Provider 为 CloudHSM,密钥在 HSM 内部生成
KeyGenerator keyGen = KeyGenerator.getInstance("DESede", "CloudHSM");
// 设置密钥长度为 192 bit(3DES 标准密钥长度)
keyGen.init(192);
// 生成密钥(密钥存储在 HSM 内部,返回的 Key 对象是引用)
Key desKey = keyGen.generateKey();
如果需要为密钥设置标签(label)以便后续通过标签查找密钥,可以使用 CloudHSM 的 KeyAttributesMap:
import com.amazonaws.cloudhsm.jce.provider.attributes.KeyAttribute;
import com.amazonaws.cloudhsm.jce.provider.attributes.KeyAttributesMap;
// 创建密钥属性,设置标签
KeyAttributesMap keyAttributes = new KeyAttributesMap();
keyAttributes.put(KeyAttribute.LABEL, "my-3des-mac-key");
keyAttributes.put(KeyAttribute.TOKEN, true); // 持久化密钥
keyAttributes.put(KeyAttribute.EXTRACTABLE, false); // 不可导出
KeyGenerator keyGen = KeyGenerator.getInstance("DESede", "CloudHSM");
keyGen.init(keyAttributes);
Key desKey = keyGen.generateKey();
2、执行3DES-CBC并截取成为MAC
以下是完整的 Java 代码实现,通过 CloudHSM JCE Provider 执行 3DES-CBC 加密并截取最后一个密文块作为 MAC。
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import java.security.Key;
import java.util.Arrays;
public class DesEdeMacSimulator {
// 3DES 分组大小:8 字节
private static final int BLOCK_SIZE = 8;
/**
* 使用 CloudHSM 中的 3DES 密钥计算 CBC-MAC
*
* @param desKey CloudHSM 中的 3DES 密钥(Key 对象,密钥不出 HSM)
* @param message 要计算 MAC 的原始消息
* @param macLen MAC 输出长度(4 或 8 字节,取决于业务协议)
* @return MAC 值
*/
public static byte[] computeCbcMac(Key desKey, byte[] message, int macLen)
throws Exception {
// ======== 第 1 步:填充 ========
// 按照业务协议选择填充方式,此处以 ISO 9797-1 Method 2 为例
byte[] padded = padISO9797Method2(message);
// ======== 第 2 步:构造全零 IV ========
// CBC-MAC 标准要求 IV 必须为全零
byte[] ivBytes = new byte[BLOCK_SIZE];
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// ======== 第 3 步:调用 CloudHSM 做 3DES-CBC 加密 ========
// Provider 指定为 "CloudHSM",加密操作在 HSM 硬件内完成
// 密钥始终留在 HSM 内部,不会暴露到应用程序内存
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding", "CloudHSM");
cipher.init(Cipher.ENCRYPT_MODE, desKey, iv);
byte[] cipherText = cipher.doFinal(padded);
// ======== 第 4 步:从密文中截取最后一个块作为 MAC ========
// cipherText 已经返回到应用程序内存,可以自由操作
byte[] fullMac = new byte[BLOCK_SIZE];
System.arraycopy(
cipherText, // 源数组:完整密文
cipherText.length - BLOCK_SIZE, // 起始位置:最后 8 字节
fullMac, // 目标数组
0, // 目标偏移量
BLOCK_SIZE // 复制长度:8 字节
);
// ======== 第 5 步:根据业务协议截取 MAC 长度 ========
if (macLen == BLOCK_SIZE) {
return fullMac; // 完整 8 字节 MAC
} else {
byte[] truncatedMac = new byte[macLen];
System.arraycopy(fullMac, 0, truncatedMac, 0, macLen);
return truncatedMac; // 截取指定长度(如 4 字节)
}
}
/**
* ISO 9797-1 Padding Method 2
* 先追加 0x80 标记字节,再补 0x00 直到总长度为 8 的倍数
*/
private static byte[] padISO9797Method2(byte[] data) {
int padLen = BLOCK_SIZE - ((data.length + 1) % BLOCK_SIZE);
if (padLen == BLOCK_SIZE) {
padLen = 0;
}
byte[] padded = new byte[data.length + 1 + padLen];
System.arraycopy(data, 0, padded, 0, data.length);
padded[data.length] = (byte) 0x80;
// 剩余位置默认为 0x00
return padded;
}
/**
* ISO 9797-1 Padding Method 1
* 补 0x00 直到总长度为 8 的倍数(已对齐时不填充)
*/
private static byte[] padISO9797Method1(byte[] data) {
if (data.length % BLOCK_SIZE == 0 && data.length > 0) {
return Arrays.copyOf(data, data.length);
}
int padLen = BLOCK_SIZE - (data.length % BLOCK_SIZE);
byte[] padded = new byte[data.length + padLen];
System.arraycopy(data, 0, padded, 0, data.length);
return padded;
}
}
调用示例:
// 从 CloudHSM 获取或生成 3DES 密钥
Key desKey = getOrGenerateDesKey();
// 待计算 MAC 的消息
byte[] message = "Transaction Data".getBytes("UTF-8");
// 计算完整 8 字节 MAC
byte[] mac8 = DesEdeMacSimulator.computeCbcMac(desKey, message, 8);
// 计算 4 字节 MAC(某些金融协议要求截取前 4 字节)
byte[] mac4 = DesEdeMacSimulator.computeCbcMac(desKey, message, 4);
安全注意事项:
| 事项 | 说明 |
|---|---|
| 密钥安全 | 3DES 密钥始终在 HSM 内部,加密操作在 HSM 硬件上执行 |
| IV 必须全零 | 使用随机 IV 会破坏 CBC-MAC 的安全属性,计算结果也无法与标准实现互通 |
| 必须使用 NoPadding | 填充逻辑由应用层控制,不能让 Cipher 自动添加 PKCS5Padding |
| 填充方式一致性 | 发送方和接收方必须使用完全相同的填充方式,否则 MAC 校验会失败 |
| 密钥不可复用 | 用于 CBC-MAC 的 3DES 密钥不应同时用于 CBC 加密,否则会降低安全性 |
| CBC-MAC 长度攻击 | 纯 CBC-MAC 对可变长度消息存在伪造风险,建议固定消息长度或在消息前附加长度字段 |
| 非 FIPS 模式要求 | 此方案要求 CloudHSM 集群运行在非 FIPS 模式下,FIPS 模式在 2023 年后已禁止 3DES 操作 |
四、参考文档
- CloudHSM JCE Provider 支持的算法(Client SDK 5)
- CloudHSM JCE Provider 支持的密钥类型(Client SDK 5)
- CloudHSM PKCS#11 支持的算法(Client SDK 5)
- CloudHSM OpenSSL Dynamic Engine 支持的算法(Client SDK 5)
- CloudHSM OpenSSL Provider 支持的算法
- CloudHSM FIPS 140 合规:2024 算法弃用通知
- CloudHSM JCE Provider 密钥管理基础(Client SDK 5)
- NIST SP 800-131A Rev.2 — 密码算法和密钥长度过渡指南
- NIST SP 800-38B — CMAC 推荐标准
- ISO 9797-1 — 使用分组密码的消息认证码
最后修改于 2026-04-28