按产品分类浏览文章 关于本站

在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 值
           (丢弃)    (丢弃)    (丢弃)            (保留)

具体步骤:

  1. 将明文按 8 字节(3DES 分组大小)分块:P1, P2, P3, …, Pn
  2. 如果最后一块不足 8 字节,按照协议要求进行填充
  3. 第一块 P1 与 IV(全零)进行 XOR,然后用 3DES 密钥加密,得到 C1
  4. 第二块 P2 与 C1 进行 XOR,然后用 3DES 密钥加密,得到 C2
  5. 依此类推,直到最后一块
  6. 最后一个密文块 Cn 就是 MAC 值

可以看出,这个过程与 3DES-CBC 加密完全相同,唯一的区别是:

  • CBC 加密:输出所有密文块 C1, C2, …, Cn(用于保密)
  • CBC-MAC:只保留最后一个密文块 Cn(用于认证)

2、用3DES-CBC替代3DES-MAC

基于上述原理,在 CloudHSM 上用 3DES-CBC 替代 3DES-MAC 的方法是:

  1. 自己完成明文填充(按协议要求选择填充方式)
  2. 将 IV 设为 8 字节全零
  3. 调用 CloudHSM 的 DESede/CBC/NoPadding 执行加密
  4. 从返回的密文中截取最后 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 操作

四、参考文档


最后修改于 2026-04-28