后量子密码学(post-quantum cryptography):为什么重要以及如何使用

后量子密码学:为什么重要以及如何使用

1. 为什么需要后量子密码学?

当今安全的基础正面临风险

当今互联网上几乎所有安全内容——HTTPS、JWT 令牌、SSH、代码签名、TLS——都依赖于公钥密码学。两个最常见的系列是:
  • RSA——安全性基于将一个非常大的数字分解为其质因数的难度。
  • ECC(椭圆曲线密码学)——安全性基于椭圆曲线上"离散对数"问题的难度。
这两者被认为是安全的,因为没有经典(传统)计算机能够在合理的时间内破解它们。使用当今的硬件破解一个 2048 位的 RSA 密钥需要数十亿年。

量子计算机的出现

量子计算机是一种根本不同的机器。它使用量子位而不是比特(0 或 1),量子位可以同时表示 0、1 或两者(叠加态)。对于某些数学问题,这为量子计算机提供了指数级的速度优势。
1994 年,数学家 Peter Shor 证明了一台足够强大的量子计算机运行Shor 算法可以在多项式时间内分解大数——并解决离散对数问题。简单来说:量子计算机可以在数小时或数天内破解 RSA-2048 或 ECDSA P-256,而不是数十亿年。
能够做到这一点的量子计算机尚未完全存在,但该领域正在快速发展。主要政府和科技公司正在量子硬件上进行大量投资。

"现在收集,以后解密"

你不需要等到量子计算机出现才受到威胁。对手已经在今天收集加密数据,目的是在量子硬件成熟后解密。这被称为"现在收集,以后解密"攻击。
对于必须保密 10-20 年以上的数据——医疗记录、金融合同、政府机密、长期有效的身份验证令牌——威胁已经存在。

为什么现在就要行动?

  • 迁移密码基础设施需要数年时间(协议更新、库升级、密钥轮换、合规审计)。
  • NIST 已经最终确定了新标准(见第 2 节)。
  • AWS 等云提供商已经在 KMS 中支持后量子算法。
  • 等到量子计算机足够强大时再行动就太晚了。

2. NIST 后量子标准化(FIPS)

NIST(美国国家标准与技术研究院)于 2016 年启动了一项全球竞赛,以评估和标准化后量子密码算法。经过 8 年的公开评估、密码分析和改进,NIST 于 2024 年 8 月发布了三项最终标准:
标准
算法
基于
用途
FIPS 203
ML-KEM(模格密钥封装机制)
CRYSTALS-Kyber
密钥交换 / 建立共享密钥
FIPS 204
ML-DSA(模格数字签名算法)
CRYSTALS-Dilithium
数字签名
FIPS 205
SLH-DSA(无状态基于哈希的数字签名算法)
SPHINCS+
数字签名(基于哈希,更保守)

是什么使这些算法具有抗量子性?

这些算法建立在经典计算机和量子计算机都难以解决的数学问题之上:
  • 格问题(ML-KEM、ML-DSA):在高维几何结构中寻找短向量。目前还没有已知的量子算法可以有效地解决这个问题。
  • 哈希函数(SLH-DSA):安全性仅取决于哈希函数的抗碰撞性,量子计算机只能适度削弱(安全位数降低 2 倍)。

参数集

每个算法都有多种"大小",具有不同的安全性 / 性能权衡。对于 ML-DSA(与本文档最相关的签名算法):
变体
安全级别
用例
ML-DSA-44
128 位(相当于 AES-128)
标准应用
ML-DSA-65
192 位(相当于 AES-192)
更高安全性应用
ML-DSA-87
256 位(相当于 AES-256)
最高安全性(推荐用于新系统)

3. 后量子算法 vs RSA/ECC:优势和权衡

比较

属性
RSA-4096
ECDSA P-384
ML-DSA-87
安全级别
~140 位经典
192 位经典
256 位(经典 + 量子)
抗量子性
公钥大小
512 字节
97 字节
~2,592 字节
私钥大小
~2,350 字节
48 字节
~4,896 字节
签名大小
512 字节
96 字节
4,627 字节
签名速度
中等
验证速度
NIST 标准化
是(传统)
是(传统)
是(FIPS 204,2024)

后量子算法的优势

  • 抗量子性:旨在抵御经典计算机和量子计算机的攻击。
  • 官方标准化:FIPS 204/205 与 AES 和 SHA-256 具有相同的 NIST 权威性。
  • 主要平台已支持:AWS KMS、Cloudflare 等已部署 PQC 支持。
  • 面向未来:现在迁移可以避免以后代价高昂的紧急迁移。

后量子算法的劣势

  • 更大的密钥和签名:ML-DSA-87 签名比 ECDSA P-384 大约 48 倍。这对于带宽敏感的协议或存储大量签名的系统很重要。
  • 更新,实战测试较少:RSA 和 ECDSA 经过了数十年的实际密码分析。ML-DSA 只有几年。数学基础研究充分,但实现经验较新。
  • 库支持仍在成熟:并非所有语言和框架都对 PQC 提供一流支持。BouncyCastle(Java)和 OpenSSL 3.5+ 处于领先地位。
  • 非直接替换:协议更改(TLS、JWT、SSH)需要在多个层面进行更新,而不仅仅是交换密钥类型。

建议

对于处理敏感数据的新系统:现在就采用 ML-DSA-87(或至少 ML-DSA-65)进行签名。对于现有系统:制定迁移路线图;优先处理具有长期保密要求的数据。

4. 实际示例:使用 AWS KMS 进行 ML-DSA 签名和验证

本节将介绍一个使用 AWS KMS 进行签名和使用 BouncyCastle(Java)进行本地验证的完整工作示例。该模式反映了真实的生产用途:KMS 安全地保存私钥,而验证者只需要公钥。

架构概述

┌─────────────────────────────────────────────────────────┐
│                     Your Application                    │
│                                                         │
│  ① Get public key   ② Sign with KMS   ③ Verify locally │
│       (once)           (per request)    (on any node)   │
└────────────┬────────────────┬──────────────────────────┘
             │                │
             ▼                ▼
      ┌─────────────────────────┐
      │       AWS KMS           │
      │  (holds private key —   │
      │  never leaves KMS)      │
      └─────────────────────────┘

前提条件

Maven 依赖(BouncyCastle 1.80+,ML-DSA 支持所需):
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.80</version>
</dependency>
为什么是 1.80?org.bouncycastle.pqc.crypto.mldsa 包(最终的 NIST FIPS 204 支持)在 BouncyCastle 1.77 中引入,并在 1.80 中稳定。较旧的版本可能在标准化前的名称 crystals.dilithium 下有该包。
AWS KMS 设置:
  1. 创建一个密钥规格为 ML_DSA_87 的 KMS 密钥
  2. 记下密钥 IDxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  3. 授予您的 IAM 角色 kms:Signkms:GetPublicKey 权限

三步流程

步骤 1——获取公钥

KMS 以 SPKI DER 格式(SubjectPublicKeyInfo,标准 X.509 公钥编码)返回公钥。这个约 2,614 字节的二进制块包含算法 OID 和原始密钥材料。
byte[] publicKeyDer = kms.getPublicKey(
    GetPublicKeyRequest.builder().keyId(KEY_ID).build()
).publicKey().asByteArray();

System.out.printf("[1] 公钥 : %d 字节%n", publicKeyDer.length);
// 输出:[1] 公钥 : 2614 字节
您可以将 publicKeyDer 分发给任何需要验证签名的服务。私钥永远不会离开 KMS。

步骤 2——使用 KMS 签名

byte[] messageBytes = "hello post quantum algorithm ML_DSA_87"
    .getBytes(StandardCharsets.UTF_8);

byte[] signature = kms.sign(SignRequest.builder()
    .keyId(KEY_ID)
    .message(SdkBytes.fromByteArray(messageBytes))  // 原始字节,不是字符串
    .messageType(MessageType.RAW)
    .signingAlgorithm(SigningAlgorithmSpec.ML_DSA_SHAKE_256)
    .build()
).signature().asByteArray();

System.out.printf("[2] 签名  : %d 字节(ML-DSA-87 固定长度)%n", signature.length);
// 输出:[2] 签名  : 4627 字节
重要:使用 SdkBytes.fromByteArray() 以原始字节形式传递消息。传递纯字符串会触发 SDK 内部的 base64 编码,导致 KMS 看到的字节与您要签名的内容不同——验证将始终失败。

步骤 3——使用 BouncyCastle 本地验证

// 通过 OID 解析 SPKI DER——无需 JCA 提供程序名称查找
MLDSAPublicKeyParameters pubKey =
    (MLDSAPublicKeyParameters) PublicKeyFactory.createKey(publicKeyDer);

// 纯 ML-DSA 验证(无预哈希,匹配 KMS 行为)
MLDSASigner signer = new MLDSASigner();
signer.init(false, pubKey);
signer.update(messageBytes, 0, messageBytes.length);
boolean valid = signer.verifySignature(signature);

System.out.printf("[3] 已验证   : %b%n", valid);
// 输出:[3] 已验证   : true
为什么使用低级 BC API?JCA 提供程序中 ML-DSA 的注册名称(KeyFactory.getInstance("ML-DSA", "BCPQC"))对版本敏感,在某些 BouncyCastle 构建中可能会失败。直接使用 PublicKeyFactory.createKey()MLDSASigner 可以避免这个问题——DER 二进制块中密钥的算法 OID 足以让 BouncyCastle 选择正确的实现。
等效的 OpenSSL 验证(用于跨语言测试):
openssl pkeyutl -verify -pubin -inkey pub.der \
  -in message.bin -sigfile signature.bin

完整的工作类

import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
import org.bouncycastle.pqc.crypto.mldsa.MLDSASigner;
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.*;

import java.nio.charset.StandardCharsets;

public class KmsMLDSATest {
    private static final String KEY_ID = "<your-kms-key-id>";
    private static final String MESSAGE = "hello post quantum algorithm ML_DSA_87";

    public static void main(String[] args) throws Exception {
        KmsClient kms = KmsClient.builder()
                .region(Region.US_EAST_1)  // 调整为您的密钥所在区域
                .credentialsProvider(DefaultCredentialsProvider.create())
                .build();

        try {
            byte[] messageBytes = MESSAGE.getBytes(StandardCharsets.UTF_8);

            // 步骤 1:获取 KMS 公钥(SPKI DER 格式)
            byte[] publicKeyDer = kms.getPublicKey(
                GetPublicKeyRequest.builder().keyId(KEY_ID).build()
            ).publicKey().asByteArray();
            System.out.printf("[1] 公钥 : %d 字节%n", publicKeyDer.length);

            // 步骤 2:使用 KMS 签名
            byte[] signature = kms.sign(SignRequest.builder()
                .keyId(KEY_ID)
                .message(SdkBytes.fromByteArray(messageBytes))
                .messageType(MessageType.RAW)
                .signingAlgorithm(SigningAlgorithmSpec.ML_DSA_SHAKE_256)
                .build()
            ).signature().asByteArray();
            System.out.printf("[2] 签名  : %d 字节(ML-DSA-87 固定长度)%n", signature.length);

            // 步骤 3:使用 BouncyCastle 本地验证
            boolean valid = verify(publicKeyDer, messageBytes, signature);
            System.out.printf("[3] 已验证   : %b%n", valid);


    static boolean verify(byte[] publicKeyDer, byte[] message, byte[] signature) throws Exception {
        // 使用 BC 低级 API 解析 SPKI DER,绕过 JCA 提供程序名称查找
        MLDSAPublicKeyParameters pubKey =
            (MLDSAPublicKeyParameters) PublicKeyFactory.createKey(publicKeyDer);

            if (!valid) throw new RuntimeException("签名验证失败");
        } finally {
            kms.close();
        }
        MLDSASigner signer = new MLDSASigner();
        signer.init(false, pubKey);
        signer.update(message, 0, message.length);
        return signer.verifySignature(signature);
    }
    }
}

参考资料

posted @ 2026-05-02 14:46  软件心理学工程师  Views(61)  Comments(0)    收藏  举报