← 文章 / 编程开发
InfoQ 4小时前 · 2026-09-04 17:49:44 · 2 阅读

Spring Boot 中的后量子密码学:一个冲刺周期内即可交付的四种模式

背景

当美国国家标准与技术研究院(NIST)于 2024 年 8 月最终敲定 FIPS 203FIPS 204 规范时,受监管行业中的大多数工程团队都开始提出同一个问题:“我们到底该从哪里入手?”显而易见,答案是“切换到 PQC TLS”,但各云服务提供商仍在部署这项技术,目前大多数团队尚无法立即启用该技术。

与此同时,实际的风险——攻击者当前正在存储加密的服务间流量,以便在量子硬件技术成熟后对其进行解密——已经开始显现。

本文以一个基于 Spring Boot 构建的标准零售银行微服务平台为例。该平台包含一个交易服务,不仅会将支付指令发送至 Core Banking Service,还会将客户的个人身份信息(PII)和 KYC 数据存储在 PostgreSQL 中。

此外,该平台将把贷款协议、开户文件存档在 Amazon S3 中,并将 OAuth2 服务账户令牌集成到 SWIFT 和 ACH 连接器的监管报告管道中。对于中大型银行而言,这种拓扑结构是非常典型的。问题在于,“量子安全”对于上述各个组件而言究竟意味着什么。组件不同,答案不同。

本文使用一个名为 PqcStarterLib 的 Spring Boot PQC 库——该库将 Bouncy Castle PQC 提供程序封装在三个自动配置的 Bean 中,针对上述拓扑结构探讨了四种具体的模式。

这些模式涵盖:银行内部服务之间的有效载荷加密;Jakarta Persistence 将数据写入数据库前对个人身份信息(PII)和客户尽职调查(KYC)进行字段级的加密;使用 Dilithium 对贷款协议和审计记录进行长期文档签名;以及针对 Core Banking 和监管报告管道中服务账户的量子安全 OAuth2 令牌进行签名。每种模式都配有可运行的 Spring Boot 代码,并附有关于其无法直接投入生产环境的诚实说明。

零售银行是“先收割,后解密”(HNDL)策略中尤为诱人的目标,因为相关数据的保质期极长。今天被盗的客户社会安全号码(SSN),到了 2035 年依然有用。一份贷款协议,其 RSA 签名十年后便可能被伪造,这将构成无法追溯补救的法律责任。本文所描述的攻击模式正是围绕这一现实情况来设计的。

威胁浅析

RSA 和 ECDSA 之所以有效,是因为对经典计算机而言,大数因式分解和离散对数问题都是难以解决的难题。而运行肖尔算法的量子计算机可以在多项式时间内解决这两个问题。目前,IBM、谷歌以及多家国家实验室都已经拥有可运行的量子处理器,尽管其中尚无任何一台达到破解 RSA-2048 所需的规模。大多数专家认为,这一临界点将在 2030 年至 2035 年之间出现。

最迫在眉睫的问题是 HNDL。目前,攻击者正在拦截并存储加密的 TLS 流量。建立 TLS 会话的 RSA 密钥交换过程会与密文一同被记录下来。一旦具备相应能力的量子计算机问世,攻击者便会回溯并对其进行解密。对于零售银行而言,受影响的范围包括在各服务之间流转的客户 KYC 数据、交易记录、银行间结算信息,以及任何需要长期保密的、通过网络传输的文件。

另一个不容拖延的方面是长期有效的签名文件。如果今天使用 RSA 签署了一份贷款协议,而该协议在 2036 年仍然需具备法律效力,那么你将面临一个事后无法解决的问题。根据监管管辖区的不同,银行会将贷款协议、开户合同及审计记录存档,保存七年到三十年不等。你无法对已存档的文件进行追溯性重新签名。

短期有效的数据风险较低。一个 15 分钟后过期的客户会话令牌,即使使用 RSA 签名密钥,通常问题也不大,因为在有人破解它之前,它就已经毫无价值了。但用于 Core Banking 系统集成、欺诈检测管道和 SWIFT 连接器的 OAuth2 服务账户令牌,其有效期往往长达数月,这样的情况就大不相同了。这些正是 HNDL 攻击的目标。

PqcStarterLib 向 Spring Boot 添加了什么

PqcStarterLib 基于 FIPS 203 和 FIPS 204 的 Bouncy Castle 实现构建。它提供了三个在启动时自动配置的 Spring Bean:

  • PqcEncryptionService 提供混合加密功能:首先使用 Kyber KEM 建立一次性共享密钥,随后采用 AES-256-GCM 对实际有效载荷进行加密。如果服务间消息正文或数据库字段需要保持机密性,请使用此服务。

  • PqcSignatureService 通过 CRYSTALS-Dilithium 提供签名和验证功能。对于需要证明内容未被篡改的贷款协议、KYC 文件、审计记录、构建工件和 OAuth2 令牌,请使用此服务。

  • PqcKeyPairGenerator 作为自动配置的 Spring Bean,用于生成 Kyber 和 Dilithium 密钥对。

集成仅需三行代码:

@Autowired PqcEncryptionService pqc;@Autowired PqcSignatureService  pqcSig;byte[] ciphertext = pqc.encrypt(data, recipientPublicKey).toBytes();byte[] signature  = pqcSig.sign(document, myPrivateKey);boolean ok        = pqcSig.verify(document, signature, myPublicKey);
复制代码

关于依赖关系的说明

Bouncy Castle(bcprov-jdk18on)向下兼容 JDK 11,这涵盖了 LTS 周期内的大多数银行系统。如果你使用的是 JDK 24 及以上版本,SunJCE 提供程序现在已经分别通过 JEP 496JEP 497 原生支持 ML-KEM 和 ML-DSA,不需要依赖任何外部库:

// 仅支持 JDK 24+ 版本,不需要 Bouncy Castle 2KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM-768");3KeyPair kyberPair = kpg.generateKeyPair();45Signature signer = Signature.getInstance("ML-DSA-65");6signer.initSign(dilithiumPrivateKey);7signer.update(message);8byte[] sig = signer.sign();
复制代码

Bouncy Castle 提供了更灵活的参数设置,并且支持 JDK 11 和 JDK 17。原生提供程序没有任何依赖,也是 NIST 标准 Java 工具集当前的发展方向。如果一家银行原本就计划升级到 JDK 24,那么选择原生方案是值得的。

用例 1:服务间有效载荷加密

情况说明

交易服务通过 HTTP 将客户的支付指令发送至 Core Banking Service。虽然 TLS 保护了传输过程,却无法防范 HNDL 攻击。如果攻击者今天记录了 RSA 密钥的交换过程,那么日后便可以利用量子计算机解密整个会话。在典型的银行基础设施中,TLS 连接通常会在 API 网关、服务网格和内部负载均衡器处终止,因此,交易服务与 Core Banking Service 之间的实际微服务跳转,在网络边界内部往往本就是未加密的。

模式

在发送前使用 Kyber+AES-256-GCM 对 HTTP 正文进行加密,加密过程与 TLS 独立。Core Banking Service 接收一个 PqcEncryptedPayload 记录,并使用其 Kyber 私钥对其进行解密。即使 TLS 被完全移除,支付指令仍然受到保护。

// 交易服务:sender@Autowired PqcEncryptionService pqc;PaymentInstruction instruction = buildInstruction(transfer);byte[] payload = objectMapper.writeValueAsBytes(instruction);PqcEncryptedPayload encrypted = pqc.encrypt(   payload,   coreBankingPublicKey   // Kyber-768 公钥);restTemplate.postForObject("/core-banking/process", encrypted, Void.class);// Core Banking Service:receiver@PostMapping("/core-banking/process")public ResponseEntity<?> process(@RequestBody PqcEncryptedPayload body) {   byte[] plaintext = pqc.decrypt(body, coreBankingPrivateKey);   PaymentInstruction instruction =       objectMapper.readValue(plaintext, PaymentInstruction.class);   // 记入分类账……   return ResponseEntity.ok().build();}
复制代码

这就是 NIST 在其迁移指南中描述的双层架构模式:TLS 负责应对经典攻击者,Kyber 负责应对量子攻击者。这两种攻击必须分别破解。你不需要更改 TLS 配置、API 网关配置或服务网格。

关注要点

Kyber-768 的公钥大小约为 1184 字节。这在 HTTP 请求正文中没有问题,但请勿将其放入请求头中。Dilithium-3 的签名大小约为 3300 字节,若将其序列化为 JWT 声明或 ISO 20022 消息信封,则需特别注意。

用例 2:PII 和 KYC 字段级加密

情况说明

客户的社会安全号码(SSN)、税务识别号码、出生日期字段以及 KYC 文件参考信息通常存储在 PostgreSQL 中。在静止状态下,这些数据可能经过了 AES 加密,但密钥通常存储在环境变量中,或在系统启动时从配置服务器中获取。一旦发生通过 SQL 注入获取的数据库导出、备份配置错误或内部人员泄密,所有信息都将暴露无遗。在零售银行业,单次社会安全号码泄露即构成违反《通用数据保护条例》(GDPR)、《加州消费者隐私法案》(CCPA)、《格雷姆-里奇-比利雷尔法案》(GLBA)以及各州金融数据保护法的监管违规行为。

模式

在使用 Jakarta Persistence 将实体持久化之前,请使用 PqcEncryptionService 类对每个敏感字段进行加密。该列存储的是经过 Base64 编码的密文二进制数据块。明文值使用 @Transient 做了注解,而且绝不会写入数据库。

// 保存public CustomerProfile saveProfile(CustomerDto dto) {   CustomerProfile profile = new CustomerProfile();   profile.setFullName(dto.getFullName());   byte[] ssnCipher = pqc.encryptToBytes(       dto.getSsn().getBytes(UTF_8),       masterKeyPair.getPublic()   );   profile.setEncryptedSsn(       Base64.getEncoder().encodeToString(ssnCipher)   );   return repo.save(profile);}// 检索public String getSsn(Long customerId) {   CustomerProfile p = repo.findById(customerId).orElseThrow();   byte[] blob = Base64.getDecoder().decode(p.getEncryptedSsn());   return new String(pqc.decrypt(blob, masterKeyPair.getPrivate()), UTF_8);}
复制代码

阻碍字段级加密投入生产应用的关键管理问题

上述代码在开发环境中运行正常,但 masterKeyPair.getPrivate() 返回的是存储在 JVM 堆中的 Kyber 私钥。在银行业务场景中,这种做法会引发三个问题,导致任何安全审计都无法通过:

  • 如果服务器重启时没有持久化的密钥存储,那么所有加密的客户记录都将永远无法读取。

  • 从任何正在运行的实例中获取的堆转储都会泄露数据库中每个社会安全号码(SSN)和税务识别号(Tax ID)的主私钥。

  • 该方案未实施密钥轮换,而 GLBA、PCI-DSS 和 SOC 2 标准均对此有明确要求。

解决方法是将所有 Kyber 密钥操作都通过 AWS KMS 或 HashiCorp Vault 进行。应用程序绝不会持有原始私钥。每次解密操作都是经过日志记录且可审计的 KMS 调用,并按计划进行密钥轮换。虽然这种模式已经得到了广泛的理解,但与加密本身属于不同的工程工作流,而且必须在任何加密字段进入生产环境之前就已就位。如果没有这一机制,你所拥有的将只是一个可运行的概念验证,而非可部署的系统。

用例 3:贷款协议的电子签名与审计追踪

情况说明

零售银行每天都会签署贷款协议、开户合同以及监管审计记录。这些文件会被存档七至三十年。一份今天使用 RSA 技术签署的贷款协议,其签名到 2035 年左右仍然可能会被伪造。如果一家银行在 2034 年发现了这一风险,他们将无法追溯性地重新签署过去十年间已经归档的文件。这一问题既是法律风险,也是在欧盟《数字权利与权利法案》(DORA)和美国货币监理署(OCC)指南等监管框架下的合规问题。

模式

在创建时,使用 Dilithium 对所有长期有效的文档进行签名。Dilithium 是一种基于晶格的算法,其安全性假设不受肖尔算法的影响。今天生成的签名在 2045 年仍将具有密码学上的有效性。

@Entitypublic class CustomerProfile {   private String fullName;   private String accountNumber;   @Column(name = "ssn_encrypted")   private String encryptedSsn;   @Column(name = "tax_id_encrypted")   private String encryptedTaxId;   @Transient   // 永远不持久化   private String ssn;   @Transient   // 永远不持久化   private String taxId;}// 文档创建时public SignedDocument signLoanAgreement(       byte[] agreementPdf,       PrivateKey signerKey,       String officerId) {   byte[] signature = pqcSig.sign(agreementPdf, signerKey);   return SignedDocument.builder()       .document(agreementPdf)       .signature(Base64.getEncoder().encodeToString(signature))       .algorithm("DILITHIUM3")       .signedAt(Instant.now())       .signerId(officerId)       .documentType("LOAN_AGREEMENT")       .build();}// 验证:同一段代码在 2026 年、2031 年或 2045 年均可正常运行public VerificationResult verify(SignedDocument doc) {   byte[] sig = Base64.getDecoder().decode(doc.getSignature());   PublicKey pub = keyRegistry.getPublicKey(doc.getSignerId());   boolean valid = pqcSig.verify(doc.getDocument(), sig, pub);   return VerificationResult.builder()       .valid(valid)       .signerId(doc.getSignerId())       .signedAt(doc.getSignedAt())       .quantumSafe(true)       .build();}
复制代码

同样的模式也适用于持续集成和持续交付(CI/CD)的构建产物签名。部署到银行基础设施中的每个 JAR 文件都应由构建管道使用 Dilithium 进行签名。在执行任何 kubectl apply 命令之前,部署门都会验证该签名。如果签名不匹配,部署将因 SecurityException 异常而中止。该解决方案能够捕获构建产物注册表与生产环境之间的供应链注入攻击——由于此类攻击发生在注册表边界内部,所以标准 TLS 无法检测到。

在本文介绍的四种模式中,文档和工件签名是最接近生产就绪的。该模式并不依赖于 KMS 部署,签名密钥可以通过现有的密钥基础设施进行管理,而且其紧迫性论据足够具体,足以获得法律和合规团队的支持。

用例 4:用于 Core Banking Services 的量子安全 OAuth2 令牌

在零售银行业,授权层的实施比在典型的微服务架构中更需要加紧推进。原因如下。

短效客户会话令牌(RS256,有效期 15 分钟)确实优先级比较低。在有人破解之前,该令牌就已经毫无价值。但在银行业务中,有两种令牌类型的情况则截然不同:用于 Core Banking 系统集成、欺诈检测引擎、SWIFT 网关连接器以及 ACH 报告管道的 OAuth2 服务账户令牌。通常,这些令牌的有效期长达数月甚至数年,并且存储在 CI/CD 密钥库和基础设施自动化工具中。任何承载合规监管相关声明的令牌,在合规层面都会具备长期的重要性。

这些正是 HNDL 攻击所收集的凭证。如果今天有攻击者窃取并存储了你 SWIFT 连接器的服务账户凭证,并在 2031 年对其进行解密,那么攻击者将来就能利用该凭证以经过身份验证的方式访问你的 Core Banking API,并进行重放攻击。

模式

将 RS256 替换为 DILITHIUM3,用于服务账户令牌签名。JWT 结构完全相同,仅 alg 头和签名调用方式有所不同。

// 身份验证服务器:服务账户令牌签发public String issueServiceToken(ServiceAccount account) {   String header = base64url(       "{\"alg\":\"DILITHIUM3\",\"typ\":\"JWT\"}"   );   String payload = base64url(String.format(       "{\"sub\":\"%s\",\"scope\":\"%s\",\"iat\":%d,\"exp\":%d}",       account.getClientId(),       account.getScopes(),       now(),       now() + 86400   ));   String signingInput = header + "." + payload;   byte[] sig = pqcSig.sign(       signingInput.getBytes(UTF_8),       authServerPrivateKey   );   return signingInput + "." + base64url(sig);}// API 网关:令牌验证public Claims validateServiceToken(String jwt) {   String[] parts = jwt.split("\\.");   String signingInput = parts[0] + "." + parts[1];   byte[] signature = base64urlDecode(parts[2]);   boolean valid = pqcSig.verify(       signingInput.getBytes(UTF_8),       signature,       authServerPublicKey   );   if (!valid) throw new InvalidTokenException(       "Dilithium token verification failed"   );   return parsePayload(parts[1]);}
复制代码

需要做好哪些准备

Dilithium-3 的签名大小约为 3300 字节,而 RS256 仅为 256 字节。携带 Dilithium-3 签名的 JWT 明显更大。如果你的 API 网关实施了请求大小限制,或者你的 SWIFT 连接器对消息信封大小有严格要求,则请在部署该修复程序之前检查这些限制。在开源生态系统中,OAuth2 流程与 Spring Security 的全面集成仍然在进行当中。因此,这个用例只能列为近期的工作计划,而非当前即可发布的方案。

如何安排工作顺序

大多数团队不应该试图一次性完成所有这些工作。在银行业背景下,图 1 描述了风险排序:

图 1. Spring Boot 微服务基于风险优先级的 PQC 迁移流程(图片由作者制作)

请立即进行以下更改:

  • 为 Kyber 密钥管理部署 AWS KMS 或 HashiCorp Vault。如果不进行这项更改,其他任何方案均无法确保生产环境的安全。

  • 针对传输最敏感数据的两到三个服务间的数据流(即涉及客户个人身份信息、KYC 记录或支付指令的任何数据流),添加 Kyber+AES-256-GCM 有效载荷加密。

  • 将贷款协议和监管文件的签署迁移至 Dilithium。这一项最为紧迫,因为无法对已归档的文件进行追溯性修正,而且该操作无需先部署 KMS。

在未来六到十八个月内实施以下变更:

  • 在内部服务网格的其余部分全面推行有效载荷加密。

  • Jakarta Persistence/Hibernate ORM 字段加密辅助函数,支持从 KMS 生成每条记录的专属密钥。

  • 在内部银行基础设施中,对 CI/CD 构建产物采用 Dilithium 签名。

  • 一旦 JEP 527 在即将发布的 JDK 27 版本中落地,而且你的云服务提供商支持新的密码套件,请在 TLS 1.3 中启用 PQC。

在关键管理机制稳固之后,为服务账户的 Dilithium OAuth2 令牌签名添加 Spring Security 集成。接下来,在 API 网关层为 Core Banking 系统和监管管道端点添加支持后量子计算(PQC)的令牌验证机制。

需要避免的是将短效的客户会话令牌作为起点,因为它们是最常见的授权模式。同时,它们也是本清单中风险最低的一项。应从有效期最长且监管风险最大的项开始入手。

小结

零售银行业 Spring Boot 应用集群的 PQC 迁移不必一次性完成。NIST 标准已经最终确定,JDK 24 提供了原生支持,而 Bouncy Castle 则能满足仍在使用 JDK 11 或 JDK 17 的团队的需求。目前,最紧迫的工作——内部服务流量的有效载荷加密、长期有效文档的 Dilithium 签名以及个人身份信息(PII)的字段级加密——只需要通过库集成和一次专注的冲刺,即可全部实现。

难点不在于密码学本身,而在于其底层的密钥管理。如果没有部署 KMS 或 HashiCorp Vault,虽然字段级加密的概念验证可以正常运行,但却无法通过银行的安全审查。先把这一基础打好,其余工作自然会循序渐进地推进。

原文链接:https://www.infoq.com/articles/pqc-in-spring-boot/

原始来源: InfoQ

评论 (0)