Java RSA数字签名实战:从原理到API安全与软件验签应用

📅 2026/6/26 22:34:15
Java RSA数字签名实战:从原理到API安全与软件验签应用
1. 项目概述为什么我们需要数字签名在数字世界里如何证明“这份文件确实是我发的而且中途没被篡改过”这个问题在电子合同、软件分发、API接口安全等场景下至关重要。想象一下你从官网下载了一个重要的软件安装包你怎么能确信这个包就是官方发布的原版而不是被黑客植入木马的“李鬼”这时候数字签名就登场了。数字签名简单说就是利用非对称加密技术给数据打上一个独一无二的、无法伪造的“电子指纹”。发送方用自己的私钥对数据的摘要信息进行加密生成签名接收方用发送方的公钥对签名进行解密并比对数据的摘要如果一致就证明数据来源可信且内容完整。RSA算法作为最经典、应用最广泛的非对称加密算法之一是实现数字签名的绝佳选择。今天我们就来彻底搞懂在Java中如何使用RSA进行数字签名。这不是一个简单的API调用教程我会带你从密钥对生成、签名生成到签名验证一步步拆解背后的原理和实操中的每一个坑。文中的代码模块都配有详细注释你可以直接复制到项目中使用但更重要的是我希望你能明白每一步“为什么”要这么做。毕竟在安全领域知其然更要知其所以然一个配置失误就可能导致整个安全防线形同虚设。2. 核心原理与设计思路拆解2.1 数字签名的核心流程签名与验签数字签名过程可以清晰地分为两个阶段签名和验签。理解这个流程是理解所有代码的基础。签名过程发送方执行计算摘要对原始数据比如一个文件、一段JSON字符串使用哈希算法如SHA256进行计算得到一个固定长度的、唯一的“数字指纹”即消息摘要。哈希算法的特性保证了哪怕原始数据只改动一个标点摘要也会完全不同。私钥加密发送方使用自己的RSA私钥对这个“数字指纹”进行加密。这个加密后的结果就是“数字签名”。发送将原始数据和数字签名一起发送给接收方。请注意私钥始终由发送方秘密保管绝不外泄。验签过程接收方执行计算摘要接收方收到原始数据后使用与发送方相同的哈希算法独立计算一次消息摘要。公钥解密接收方使用发送方事先公开的RSA公钥对收到的“数字签名”进行解密操作。如果签名确实是发送方用其私钥生成的那么用对应的公钥就能成功解密得到发送方当初计算的“数字指纹”。比对将解密得到的“数字指纹”与自己计算出的“数字指纹”进行比对。如果两者完全一致则证明第一数据在传输过程中未被篡改摘要一致第二数据确实来自声称的发送方因为只有他的私钥能生成可用其公钥解密的签名。这个设计的精妙之处在于它完美结合了哈希算法的“防篡改”和RSA非对称加密的“身份认证”特性。公钥可以公开分发用于验证而私钥的安全性是整个体系的基石。2.2 为什么选择RSA算法选型背后的考量你可能听说过ECC椭圆曲线加密等更现代的算法。为什么我们这里还是以RSA为例这背后有几个实际的考量广泛兼容性与历史积淀RSA算法诞生早几乎所有的加密库、硬件设备、协议标准如PKCS#1、X.509证书都对其提供了原生且稳定的支持。在对接老旧系统或需要最大范围兼容性的场景下RSA往往是首选。原理相对直观RSA基于大数分解难题其数学原理欧拉定理、模幂运算对于开发者而言相对更容易理解和教学。作为学习数字签名的入门RSA是绝佳的样板。密钥管理成熟围绕RSA的密钥生成、存储、格式转换PEM、DER以及证书体系有非常成熟和通用的解决方案如Java的KeyStoreOpenSSL工具链。性能与安全性的平衡对于签名/验签操作在密钥长度足够目前推荐2048位及以上的情况下RSA的安全性经过了几十年的实战检验。虽然签名生成私钥运算较慢但验签公钥运算速度很快这非常符合大多数应用场景一次签名多次验签的需求。当然RSA也有其缺点比如密钥长度较长相对于同等安全强度的ECC导致数据包略大。在移动端或对性能有极致要求的场景ECC是更优的选择。但无论如何掌握RSA是构建密码学知识体系的坚实一步。注意本文示例将使用RSA密钥和SHA256withRSA签名算法。这是目前业界公认安全且通用的组合。绝对不要使用已被证明不安全的哈希算法如MD5、SHA1或过短的RSA密钥如1024位。3. 环境准备与核心工具类构建3.1 密钥对的生成与管理一切始于密钥对。在Java中我们可以使用KeyPairGenerator类来生成RSA密钥对。这里的关键是理解并正确设置参数。import java.security.*; import java.util.Base64; /** * RSA密钥对生成器 */ public class RSAKeyPairGenerator { /** * 生成RSA密钥对 * param keySize 密钥长度推荐2048或4096 * return 生成的密钥对 * throws NoSuchAlgorithmException */ public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException { // 1. 获取RSA算法的密钥对生成器实例 KeyPairGenerator keyPairGen KeyPairGenerator.getInstance(RSA); // 2. 初始化生成器指定密钥长度。使用默认的随机源通常是安全的 keyPairGen.initialize(keySize); // 3. 生成密钥对 return keyPairGen.generateKeyPair(); } /** * 将密钥转换为Base64编码的字符串便于存储和传输 * param key 公钥或私钥 * return Base64编码的字符串 */ public static String keyToBase64(Key key) { return Base64.getEncoder().encodeToString(key.getEncoded()); } public static void main(String[] args) throws Exception { // 生成一个2048位的密钥对 KeyPair keyPair generateKeyPair(2048); PrivateKey privateKey keyPair.getPrivate(); PublicKey publicKey keyPair.getPublic(); System.out.println( 私钥 (Private Key) ); System.out.println(keyToBase64(privateKey)); System.out.println(\n 公钥 (Public Key) ); System.out.println(keyToBase64(publicKey)); // 注意在实际生产中私钥必须被安全地存储例如使用加密的KeyStore文件。 // 绝对不要将私钥硬编码在代码中或提交到版本控制系统 } }实操心得密钥长度keySize参数至关重要。1024位已被认为不安全2048位是当前的最低安全标准对于需要长期安全的数据建议使用4096位。但请注意密钥长度翻倍生成和运算时间会显著增加。密钥存储控制台打印的Base64字符串只是演示。私钥的生命周期管理是安全的核心。在生产环境中你应该使用Java的KeyStoreJKS或PKCS12格式文件并用强密码保护。考虑使用硬件安全模块HSM或云服务商的密钥管理服务KMS来存储私钥实现最高级别的安全。公钥可以放心分发例如放在配置文件、数据库或通过API提供给客户端。3.2 从字符串还原密钥对象我们经常需要将存储的Base64字符串重新加载为Java的PrivateKey或PublicKey对象。这里需要理解密钥的标准编码格式如PKCS#8。import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; /** * 密钥工具类用于从Base64字符串加载密钥 */ public class KeyLoader { /** * 从Base64字符串加载RSA私钥 * param base64PrivateKey Base64编码的私钥字符串PKCS#8格式 * return PrivateKey对象 */ public static PrivateKey loadPrivateKey(String base64PrivateKey) throws GeneralSecurityException { byte[] keyBytes Base64.getDecoder().decode(base64PrivateKey); // PKCS#8是Java中私钥的标准编码格式 PKCS8EncodedKeySpec keySpec new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory KeyFactory.getInstance(RSA); return keyFactory.generatePrivate(keySpec); } /** * 从Base64字符串加载RSA公钥 * param base64PublicKey Base64编码的公钥字符串X.509格式 * return PublicKey对象 */ public static PublicKey loadPublicKey(String base64PublicKey) throws GeneralSecurityException { byte[] keyBytes Base64.getDecoder().decode(base64PublicKey); // X.509是公钥的标准编码格式 X509EncodedKeySpec keySpec new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory KeyFactory.getInstance(RSA); return keyFactory.generatePublic(keySpec); } }注意事项格式匹配loadPrivateKey方法期望的Base64字符串必须是PKCS#8格式的私钥编码。如果你从OpenSSL等工具生成的PEM文件以-----BEGIN PRIVATE KEY-----开头中获取需要先去掉头尾标识和换行符再解码Base64内容。公钥格式同样loadPublicKey方法期望X.509格式的公钥对应PEM文件以-----BEGIN PUBLIC KEY-----开头。如果是从证书中提取的公钥格式也是兼容的。4. 核心实现签名生成与验证详解有了密钥我们就可以进入核心的签名和验签环节。Java的Signature类封装了这些操作。4.1 签名生成用私钥为数据“盖章”import java.security.*; /** * 数字签名生成器 */ public class Signer { /** * 使用SHA256withRSA算法对数据进行签名 * param data 待签名的原始数据 * param privateKey 签名者的私钥 * return Base64编码的数字签名 */ public static String sign(byte[] data, PrivateKey privateKey) throws GeneralSecurityException { // 1. 获取Signature实例指定算法为 SHA256withRSA // 这个字符串是标准名称表示用SHA256计算摘要再用RSA私钥加密 Signature signature Signature.getInstance(SHA256withRSA); // 2. 初始化签名对象进入签名模式需要私钥 signature.initSign(privateKey); // 3. 传入要签名的原始数据 signature.update(data); // 4. 执行签名操作生成签名字节数组 byte[] digitalSignature signature.sign(); // 5. 将签名转换为Base64字符串便于传输和存储 return Base64.getEncoder().encodeToString(digitalSignature); } /** * 重载方法方便对字符串进行签名 * param message 待签名的字符串 * param privateKey 签名者的私钥 * return Base64编码的数字签名 */ public static String sign(String message, PrivateKey privateKey) throws GeneralSecurityException { return sign(message.getBytes(java.nio.charset.StandardCharsets.UTF_8), privateKey); } }关键点解析算法字符串SHA256withRSA是一个完整的算法描述。Java还支持SHA512withRSA、SHA384withRSA等。选择SHA256是目前安全与性能的平衡点。update方法可以多次调用用于处理大数据流。如果你已经拥有全部数据的字节数组一次调用即可。签名输出sign()方法返回的字节数组就是数字签名。其长度与RSA密钥长度有关例如2048位密钥的签名长度是256字节。4.2 签名验证用公钥验明正身验证是签名的逆过程但使用的是公钥。import java.security.*; /** * 数字签名验证器 */ public class Verifier { /** * 验证数字签名 * param data 接收到的原始数据 * param base64Signature 接收到的Base64编码的数字签名 * param publicKey 签名者的公钥 * return true 验证成功false 验证失败 */ public static boolean verify(byte[] data, String base64Signature, PublicKey publicKey) throws GeneralSecurityException { // 1. 获取Signature实例算法必须与签名时一致 Signature signature Signature.getInstance(SHA256withRSA); // 2. 初始化签名对象进入验证模式需要公钥 signature.initVerify(publicKey); // 3. 传入接收到的原始数据 signature.update(data); // 4. 将Base64签名解码为字节数组 byte[] signatureBytes Base64.getDecoder().decode(base64Signature); // 5. 执行验证操作 return signature.verify(signatureBytes); } /** * 重载方法方便验证字符串数据的签名 * param message 接收到的原始字符串 * param base64Signature 接收到的Base64编码的数字签名 * param publicKey 签名者的公钥 * return true 验证成功false 验证失败 */ public static boolean verify(String message, String base64Signature, PublicKey publicKey) throws GeneralSecurityException { return verify(message.getBytes(java.nio.charset.StandardCharsets.UTF_8), base64Signature, publicKey); } }验证逻辑的本质verify方法内部做了我们之前原理部分描述的所有事情用公钥解密签名得到摘要A对数据计算摘要B然后比较A和B。如果一致返回true如果不一致数据被改或签名不对返回false。整个过程封装得很好我们无需手动计算哈希或进行解密操作。5. 完整示例与端到端测试让我们把上面的模块组合起来完成一个从生成密钥到签名再到验签的完整流程演示。import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; /** * RSA数字签名完整流程演示 */ public class RSASignatureDemo { public static void main(String[] args) { try { // 第一阶段准备 System.out.println(1. 正在生成RSA密钥对 (2048位)...); KeyPair keyPair RSAKeyPairGenerator.generateKeyPair(2048); PrivateKey privateKey keyPair.getPrivate(); PublicKey publicKey keyPair.getPublic(); System.out.println( 密钥对生成成功。); // 模拟要签名的数据 String originalMessage 这是一条非常重要的交易指令金额10000元收款方张三。; System.out.println(\n2. 原始数据); System.out.println( \ originalMessage \); // 第二阶段发送方签名 System.out.println(\n3. 【发送方】使用私钥对数据进行签名...); String signature Signer.sign(originalMessage, privateKey); System.out.println( 生成的数字签名 (Base64)); System.out.println( signature); // 第三阶段传输模拟 // 假设数据通过网络传输。这里我们模拟一个“中间人”试图篡改数据。 String tamperedMessage 这是一条非常重要的交易指令金额100000元收款方李四。; System.out.println(\n4. 【模拟传输】数据在传输中被篡改); System.out.println( 篡改后的数据); System.out.println( \ tamperedMessage \); // 注意签名我们没有改还是原来的那个签名。 // 第四阶段接收方验签 System.out.println(\n5. 【接收方】使用公钥验证签名...); System.out.println( 场景A验证【原始】数据和签名); boolean isVerifiedOriginal Verifier.verify(originalMessage, signature, publicKey); System.out.println( 验证结果: (isVerifiedOriginal ? ✅ 通过 : ❌ 失败)); System.out.println( - 结论数据完整来源可信。); System.out.println(\n 场景B验证【被篡改】的数据和原始签名); boolean isVerifiedTampered Verifier.verify(tamperedMessage, signature, publicKey); System.out.println( 验证结果: (isVerifiedTampered ? ✅ 通过 : ❌ 失败)); System.out.println( - 结论数据被篡改验证不通过); // 额外测试签名被伪造 System.out.println(\n6. 【额外测试】模拟攻击者用错误的私钥生成签名); // 攻击者自己生成一对密钥 KeyPair attackerKeyPair RSAKeyPairGenerator.generateKeyPair(2048); String fakeSignature Signer.sign(originalMessage, attackerKeyPair.getPrivate()); System.out.println( 使用攻击者私钥生成的伪造签名); System.out.println( fakeSignature.substring(0, 50) ...); System.out.println( 用正确的公钥验证伪造签名); boolean isVerifiedFake Verifier.verify(originalMessage, fakeSignature, publicKey); System.out.println( 验证结果: (isVerifiedFake ? ✅ 通过 : ❌ 失败)); System.out.println( - 结论签名者身份不符验证不通过); } catch (Exception e) { e.printStackTrace(); } } }运行这个Demo你会看到清晰的输出验证了数字签名在防篡改和身份认证两方面的作用。即使数据被轻微改动或者签名是用错误的密钥生成的验证都会失败。6. 进阶话题与生产环境实践掌握了基础操作我们来看看在实际项目中需要注意什么。6.1 签名算法与密钥长度的选择这不是一个可以随意决定的配置。它直接关系到系统的安全寿命。算法组合推荐密钥长度安全强度适用场景备注SHA256withRSA2048位高当前绝大多数应用场景的默认选择在2030年之前被认为是安全的。性能与安全性平衡最佳。SHA256withRSA3072位更高金融、政务等高安全要求场景提供更强的长期安全性抵御未来算力提升。SHA256withRSA4096位极高长期有效如10年以上的根证书、代码签名证书签名/验签速度最慢但安全性最高。SHA384withRSA3072位高需要更强哈希抗碰撞性的场景SHA384输出更长抗碰撞性优于SHA256。SHA512withRSA4096位极高超高标准安全要求哈希输出最长计算开销也最大。选择建议新系统直接使用SHA256withRSA2048位密钥。这是当前事实上的工业标准。高安全系统考虑SHA256withRSA3072位。长期签名如果数据需要被验证很多年如法律合同、固件签名使用SHA384withRSA4096位。绝对禁止使用MD5withRSA或SHA1withRSA以及1024位RSA密钥。这些已被证实存在严重安全风险。6.2 处理大数据与数据摘要前面的例子是对整个字符串进行签名。如果数据是一个几GB的大文件将其全部读入内存再调用update是不现实的也会导致内存溢出。正确的做法是使用流式处理import java.io.*; import java.security.*; public class FileSigner { public static String signFile(String filePath, PrivateKey privateKey) throws IOException, GeneralSecurityException { Signature signature Signature.getInstance(SHA256withRSA); signature.initSign(privateKey); try (FileInputStream fis new FileInputStream(filePath); BufferedInputStream bis new BufferedInputStream(fis)) { byte[] buffer new byte[8192]; // 8KB缓冲区 int len; while ((len bis.read(buffer)) ! -1) { signature.update(buffer, 0, len); // 分块更新签名引擎 } } byte[] digitalSignature signature.sign(); return Base64.getEncoder().encodeToString(digitalSignature); } public static boolean verifyFile(String filePath, String base64Signature, PublicKey publicKey) throws IOException, GeneralSecurityException { Signature signature Signature.getInstance(SHA256withRSA); signature.initVerify(publicKey); try (FileInputStream fis new FileInputStream(filePath); BufferedInputStream bis new BufferedInputStream(fis)) { byte[] buffer new byte[8192]; int len; while ((len bis.read(buffer)) ! -1) { signature.update(buffer, 0, len); // 分块更新验证引擎 } } byte[] signatureBytes Base64.getDecoder().decode(base64Signature); return signature.verify(signatureBytes); } }这种方式可以高效地处理任意大小的文件内存占用恒定。6.3 密钥的安全存储与轮换私钥安全是生命线绝不硬编码不要将私钥的Base64字符串直接写在源代码里。使用KeyStore将私钥保存在受密码保护的JKS或PKCS12格式的KeyStore文件中。访问时通过别名和密码加载。KeyStore ks KeyStore.getInstance(PKCS12); try (InputStream is new FileInputStream(keystore.p12)) { ks.load(is, keystore-password.toCharArray()); } Key key ks.getKey(my-private-key-alias, key-password.toCharArray()); if (key instanceof PrivateKey) { PrivateKey privateKey (PrivateKey) key; // 使用私钥... }环境变量/配置中心在生产环境中KeyStore文件的密码甚至KeyStore文件本身的位置应通过环境变量或配置中心如Spring Cloud Config, Apollo注入而不是写在配置文件中。HSM/KMS对于最高安全等级使用硬件安全模块HSM或云服务商的密钥管理服务如AWS KMS, Azure Key Vault。私钥永远不出硬件或服务边界签名操作在内部完成。密钥轮换任何密钥都不应该永久使用。应制定策略定期如每年更换密钥对。新密钥对生成后需要有一个新旧并存的过渡期确保所有依赖方都更新了公钥后再废弃旧密钥。7. 常见问题排查与调试技巧在实际开发中你肯定会遇到各种签名验签失败的问题。下面是一个快速排查指南。问题现象可能原因排查步骤与解决方案InvalidKeyException1. 密钥类型不匹配如用DSA密钥做RSA签名。2. 密钥编码格式错误。3. 密钥已损坏或不完整。1. 确认加载的是RSA密钥。2. 检查Base64字符串是否完整头尾标识是否已去除。3. 使用key.getAlgorithm()打印算法名确认。SignatureException: Signature length not correct签名数据长度与当前密钥长度不匹配。1. 最常见原因签名算法不匹配。签名用SHA256withRSA验签也必须用SHA256withRSA。2. 检查签名字符串在传输过程中是否被截断或编码如URL编码损坏。验签始终返回false1. 数据在签名后被修改。2. 使用了错误的公钥进行验签。3. 签名本身是错误的用错私钥。4. 字符编码问题字符串签名时。1. 确保验签的数据字节与签名时的数据字节完全一致。对于字符串必须使用相同的字符集强烈推荐UTF-8。2. 核对公钥是否与签名私钥配对。3. 在调试阶段可以分别打印出签名和验签时数据的MD5或SHA256值比对是否一致。性能问题签名慢RSA私钥操作签名是计算密集型操作。1. 对于高频签名场景考虑使用性能更好的算法如ECDSA椭圆曲线数字签名算法。2. 确保使用足够性能的服务器CPU。3. 对于大量文件签名可以考虑异步或批量处理。NoSuchAlgorithmException指定的算法名称在JRE中不支持。1. 检查算法字符串拼写如SHA256withRSA。2. 较老的JRE可能不支持SHA256。确保使用Java 8及以上版本。3. 对于更特殊的算法可能需要安装额外的安全提供者如Bouncy Castle。调试心法隔离测试当验签失败时首先写一个最简单的单元测试。用同一段代码、同一对密钥、同一个字符串在内存中完成签名和验签。如果成功说明核心逻辑没问题问题出在数据传输、编码或密钥加载环节。字节级比对字符串签名问题十有八九出在编码上。在签名和验签前分别将字符串按指定编码如UTF-8转换成字节数组并打印其16进制或Base64表示确保两者完全一致。注意换行符\nvs\r\n、空格等不可见字符。密钥指纹为公钥生成一个指纹如对公钥getEncoded()后的字节进行SHA256哈希在双方交换公钥时先核对指纹确保拿到的公钥是正确的。8. 实际应用场景与代码集成示例理解了原理和细节我们看看如何在常见场景中集成RSA数字签名。8.1 场景一API接口请求签名防篡改与重放在微服务或开放API中确保请求来自合法客户端且未被篡改。客户端签名public class ApiClient { private PrivateKey privateKey; private String clientId; public String generateRequestSignature(String method, String path, String body, String timestamp, String nonce) throws GeneralSecurityException { // 1. 构造待签名字符串规范很重要服务端需按相同规则构造 String dataToSign String.join(|, clientId, method, path, body, timestamp, nonce); // 2. 使用私钥签名 return Signer.sign(dataToSign, privateKey); } public HttpRequest buildSignedRequest(String method, String url, String body) throws GeneralSecurityException { String timestamp String.valueOf(System.currentTimeMillis()); String nonce UUID.randomUUID().toString(); // 随机数防重放 String signature generateRequestSignature(method, new URL(url).getPath(), body, timestamp, nonce); // 3. 将签名和元数据放入HTTP头 HttpRequest request HttpRequest.newBuilder() .uri(URI.create(url)) .header(X-Client-Id, clientId) .header(X-Timestamp, timestamp) .header(X-Nonce, nonce) .header(X-Signature, signature) .method(method, HttpRequest.BodyPublishers.ofString(body)) .build(); return request; } }服务端验签public class ApiServer { private MapString, PublicKey clientPublicKeyMap; // 存储客户端ID与公钥的映射 public boolean verifyRequest(String clientId, String method, String path, String body, String timestamp, String nonce, String receivedSignature) throws GeneralSecurityException { // 1. 根据clientId获取对应的公钥 PublicKey publicKey clientPublicKeyMap.get(clientId); if (publicKey null) { return false; // 未知客户端 } // 2. 按相同规则构造待验签字符串 String dataToVerify String.join(|, clientId, method, path, body, timestamp, nonce); // 3. 验证签名 return Verifier.verify(dataToVerify, receivedSignature, publicKey); } // 附加检查时间戳和Nonce防重放 public boolean checkReplayAttack(String timestamp, String nonce, String clientId) { long requestTime Long.parseLong(timestamp); long currentTime System.currentTimeMillis(); // 允许5分钟内的请求 if (Math.abs(currentTime - requestTime) 5 * 60 * 1000) { return false; } // 检查nonce是否在缓存中存在如Redis存在则为重放 return !isNonceUsed(clientId, nonce); } }这个模式结合了数字签名身份防篡改和时间戳/随机数防重放构成了一个坚固的API安全基础。8.2 场景二软件包/固件发布签名确保用户下载的软件来自官方且未被植入恶意代码。发布方签名生成软件包的哈希值如SHA256。使用公司私钥对该哈希值进行签名。将软件包、哈希值和签名一起发布。用户端验签从官网下载软件包和签名文件。从官网或受信任的渠道获取发布方的公钥通常内置于操作系统或浏览器的信任库中。计算下载软件包的哈希值。使用公钥验证签名文件中的签名是否与计算的哈希值匹配。这个过程在Java中可以用jarsigner工具完成其底层原理就是RSA数字签名。8.3 与Spring Boot集成在Spring Boot项目中你可以将签名/验签服务封装成Bean方便注入使用。Component public class SignatureService { Value(${rsa.private-key-base64}) private String privateKeyBase64; Value(${rsa.public-key-base64}) private String publicKeyBase64; private PrivateKey privateKey; private PublicKey publicKey; PostConstruct public void init() throws GeneralSecurityException { this.privateKey KeyLoader.loadPrivateKey(privateKeyBase64); this.publicKey KeyLoader.loadPublicKey(publicKeyBase64); } public String signData(String data) { try { return Signer.sign(data, privateKey); } catch (GeneralSecurityException e) { throw new RuntimeException(签名失败, e); } } public boolean verifyData(String data, String signature) { try { return Verifier.verify(data, signature, publicKey); } catch (GeneralSecurityException e) { throw new RuntimeException(验签失败, e); } } } // 在配置文件中 // rsa.private-key-base64你的私钥字符串 // rsa.public-key-base64你的公钥字符串然后你就可以在Controller或Service中轻松地调用signatureService进行签名和验签了。数字签名是现代软件安全的基石之一。从简单的字符串验签到复杂的API安全、软件分发其核心逻辑万变不离其宗。我个人的体会是在实现过程中严谨和一致是两个最重要的原则。严谨体现在密钥管理、算法选择和异常处理上一致则体现在签名和验签双方对数据格式、编码、构造规则的约定上。任何一个细微的差别都会导致验证失败。希望这篇详解能帮你不仅“复制即用”更能“心中有数”在项目中构建起可靠的安全屏障。