


























当今互联网上几乎所有安全内容——HTTPS、JWT 令牌、SSH、代码签名、TLS——都依赖于公钥密码学。两个最常见的系列是:
这两者被认为是安全的,因为没有经典(传统)计算机能够在合理的时间内破解它们。使用当今的硬件破解一个 2048 位的 RSA 密钥需要数十亿年。
量子计算机是一种根本不同的机器。它使用量子位而不是比特(0 或 1),量子位可以同时表示 0、1 或两者(叠加态)。对于某些数学问题,这为量子计算机提供了指数级的速度优势。
1994 年,数学家 Peter Shor 证明了一台足够强大的量子计算机运行Shor 算法可以在多项式时间内分解大数——并解决离散对数问题。简单来说:量子计算机可以在数小时或数天内破解 RSA-2048 或 ECDSA P-256,而不是数十亿年。
能够做到这一点的量子计算机尚未完全存在,但该领域正在快速发展。主要政府和科技公司正在量子硬件上进行大量投资。
你不需要等到量子计算机出现才受到威胁。对手已经在今天收集加密数据,目的是在量子硬件成熟后解密。这被称为"现在收集,以后解密"攻击。
对于必须保密 10-20 年以上的数据——医疗记录、金融合同、政府机密、长期有效的身份验证令牌——威胁已经存在。
NIST(美国国家标准与技术研究院)于 2016 年启动了一项全球竞赛,以评估和标准化后量子密码算法。经过 8 年的公开评估、密码分析和改进,NIST 于 2024 年 8 月发布了三项最终标准:
|
标准 |
算法 |
基于 |
用途 |
|
FIPS 203 |
ML-KEM(模格密钥封装机制) |
CRYSTALS-Kyber |
密钥交换 / 建立共享密钥 |
|
FIPS 204 |
ML-DSA(模格数字签名算法) |
CRYSTALS-Dilithium |
数字签名 |
|
FIPS 205 |
SLH-DSA(无状态基于哈希的数字签名算法) |
SPHINCS+ |
数字签名(基于哈希,更保守) |
这些算法建立在经典计算机和量子计算机都难以解决的数学问题之上:
每个算法都有多种"大小",具有不同的安全性 / 性能权衡。对于 ML-DSA(与本文档最相关的签名算法):
|
变体 |
安全级别 |
用例 |
|
ML-DSA-44 |
128 位(相当于 AES-128) |
标准应用 |
|
ML-DSA-65 |
192 位(相当于 AES-192) |
更高安全性应用 |
|
ML-DSA-87 |
256 位(相当于 AES-256) |
最高安全性(推荐用于新系统) |
|
属性 |
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) |
对于处理敏感数据的新系统:现在就采用 ML-DSA-87(或至少 ML-DSA-65)进行签名。对于现有系统:制定迁移路线图;优先处理具有长期保密要求的数据。
本节将介绍一个使用 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 设置:
ML_DSA_87 的 KMS 密钥xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxkms:Sign 和 kms:GetPublicKey 权限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。
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 看到的字节与您要签名的内容不同——验证将始终失败。
// 通过 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);
}
}
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。