Java RSA非对称加密与数字签名的安全数据传输
目录
- 应用场景
- 技术方案设计
- 核心代码实现
- 密钥管理
- 数据加密
- 数字签名
- 验证与解密
- 时间戳防重放
- 完整使用示例
- 密钥对生成
- 总结
保障数据传输安全是系统设计中的重要环节,本文介绍如何利用RSA非对称加密和数字签名技术构建安全的数据传输方案。
应用场景
在实际开发中,我们经常需要在不同系统间传输敏感数据。比如客户端与服务器之间的API通信、微服务之间的数据交换等。为了确保这些数据不被窃取和篡改,我们需要同时实现:
- 数据加密:防止传输内容被第三方窃取
- 身份验证:确认数据来源的真实性
- 完整性校验:确保数据在传输过程中未被篡改
技术方案设计
我们采用RSA非对称加密技术结合数字签名来实现上述目标:
- 加密流程:发送方使用接收方的公钥加密数据
- 签名流程:发送方使用自己的私钥对加密后的数据签名
- 验证流程:接收方先验证签名,再使用自己的私钥解密
这种方案既保证了数据的机密性,又提供了身份验证和完整性保护。
核心代码实现
密钥管理
首先定义双方密钥对:
// 发送方密钥对(用于签名)
private final static String SENDER_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK5+ul3FPnakZ0K3...";
private final static String SENDER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCufrpdxT52pGdCt5NWvWn3aQ09...";// 接收方密钥对(用于加密)
private final static String RECEIVER_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANNGyDYMNYFUBUkF...";
private final static String RECEIVER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTRsg2DDWBVAVJBbav9Cm5EdAx...";
数据加密
使用接收方公钥加密数据:
public static String encryptForReceiver(String plainJson) {RSA rsa = SecureUtil.rsa(null, RECEIVER_PUBLIC_KEY);return rsa.encryptBase64(plainJson.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
}
数字签名
使用发送方私钥对加密后的数据签名:
public static String signBySender(String dataToSign) {Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, SENDER_PRIVATE_KEY, null);byte[] signBytes = sign.sign(dataToSign.getBytes(StandardCharsets.UTF_8));return Base64.encode(signBytes);
}
验证与解密
接收方先验证签名,再解密数据:
// 验证签名
public static boolean verifyBySenderPublic(String encryptedBase64, String signatureBase64) {Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, SENDER_PUBLIC_KEY);byte[] sigBytes = Base64.decode(signatureBase64);return sign.verify(encryptedBase64.getBytes(StandardCharsets.UTF_8), sigBytes);
}// 解密数据
public static String decryptByReceiver(String encryptedBase64) {RSA rsa = SecureUtil.rsa(RECEIVER_PRIVATE_KEY, null);return rsa.decryptStr(encryptedBase64, KeyType.PrivateKey);
}
时间戳防重放
在实际应用中,建议增加时间戳校验机制,防止重放攻击:
// 校验时间戳是否在合理范围内
if (Math.abs(System.currentTimeMillis() - p.timestamp) > 5 * 60 * 1000) {System.out.println("数据已过期");return;
}
完整使用示例
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import java.nio.charset.StandardCharsets;/*** @author Neoooo* @date 2025-09-27*/
public class CryptoUtil {// 发送者私钥(用于签名)private final static String SENDER_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK5+ul3FPnakZ0K3k1a9afdpDT25pGeb/diwhma5e/U0kFFoP0tbAYYWVaEiHJ1tyG0XlnwhETkG7EyWfNX+mjma7lpx27NxWlUodFUnZqn3qlqaHsYxjGjOvQUDRiCbFfBOpFCMViRaAYrD5JWZZHySfrU+/nX399OgI6nhvB7BAgMBAAECgYACnp6t4nSM+6wcr2yX9mVBPHuhR/iQwSHjF0hXQbTbkifR/wyHNTUtRgfQUNao4uFpyNs+nxD3ABR7VF2IqWc3jJQJ4jFHDjRziAX/eh7f9yWxvzD0VBgBbHzeDQvS+wTujOupkWx/CVpQ1aRmTr3h/9Zo2ZwYy4TfMApmtgGSoQJBANY2Vb5kGSTNicRyM3kJvqEeRUz3OwsQIyq+0t9tH4q8aE48DG9xzcZixD3a9pPHvsVEqMS1IDx8tekJ5Yimc5ECQQDQiO7RJxKcjvHXVRnxJEWWct8tjapL1YwOyuUMCiyW/cL5bJhDg6c+eiMAZhGZ8HYwxnkatf3J4N0k+khr4AAxAkAbAGxcfHei6Pm1toOAfVb3Lj6kDgH2Sgl0yOsB2NqB/W/UdMNIhPrgR/DerywnwqTsbtQrP32ZwkqX3nR9fiXRAkEArgyMHv8YlpjsGsiJpW2bsw1fXprttuueQT5w25KmUsOr9xf/IeKBNTElg5CtQimjy+PrcjLRhqxqhxFqXrcQEQJAdzlwYZc1lKMaiyIGEtUXaD3zdWHO6hD/mkrIRlWMoiNqeUiuSieM+rupH/btr9zNAkp52FNMjUIqdxmzXA8CVQ==";// 发送者公钥(提供给接收方)private final static String SENDER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCufrpdxT52pGdCt5NWvWn3aQ09uaRnm/3YsIZmuXv1NJBRaD9LWwGGFlWhIhydbchtF5Z8IRE5BuxMlnzV/po5mu5acduzcVpVKHRVJ2ap96pamh7GMYxozr0FA0YgmxXwTqRQjFYkWgGKw+SVmWR8kn61Pv519/fToCOp4bwewQIDAQAB";// 接收者私钥(用于解密)private final static String RECEIVER_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBANNGyDYMNYFUBUkFtq/0KbkR0DHAKAeWib+EKliBrj/8NAt3QmnLY3tmgn4vHSp3jomnh2ptUdxqE7owYrQWJe8ivDZquC2gdIkdSNN/hwI2+iIBWeNDAaMNcPrA8fP+gVMQ0Gp7myFliqKTGcvP7kJQ1aaQaJrX1QgpvXfKw7nzAgMBAAECgYALPXFg8FNXlwwhvDKuhIaq67F80OR2sUgqYf8MVSt1CGQO1bOfanLXczbicBU7V7/Dv2yErMSb8Lst8gcLEUAuGPl8wLCbh2AOhnxf+zwxTx9MRV7cCfMmY4Cy/K2NOng2Kho/v9UQwzyawQgPtp8mTkVsl8uCXK68bBp/vxyj8QJBAPEAgB5EHRZtAqscSXdAbJy57ZD6Rv0jlUiZ8YvilmQtJs4fl2bEyy7WOG6uNs4bDnyi+hVsxs3EOM2jwwhXygsCQQDgbLaYEXv1r0bhNojbV5ft0konGfl2QS4/b9belfVppVGNL4kqt2z3tZrR2LV0VRhDllWxHnkNja6JTlUs4yi5AkAqpHgG4u5ypV8vf5XQL+oH4S4T1PTynXUwn2yJ39HUb9jJ5/UWDgQViXn6u4Ce/1KU4xF08QZMKkgSusMrmrz5AkA9CVQfx6GPEDyWw940yX3okGjaeZX/M3sAhcpKfz5fnTawz1ze4UQhmqKgr++p0/rlZt2nbkI+DWqKrWM88gsBAkEAvEcCj2Sm0ByZn3DWj74gBbGtJXWNv8PxzflGWp+2b2iu5eH9KlooKo5zXBF6gLgwCSOJobYF87axrn5QG72ngQ==";// 接收者公钥(提供给发送方)private final static String RECEIVER_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTRsg2DDWBVAVJBbav9Cm5EdAxwCgHlom/hCpYga4//DQLd0Jpy2N7ZoJ+Lx0qd46Jp4dqbVHcahO6MGK0FiXvIrw2argtoHSJHUjTf4cCNvoiAVnjQwGjDXD6wPHz/oFTENBqe5shZYqikxnLz+5CUNWmkGia19UIKb13ysO58wIDAQAB";public static void main(String[] args) {Payload payload = Payload.builder().userId(1L).username("test").timestamp(System.currentTimeMillis()).build();String payloadJson = JSONUtil.toJsonStr(payload);// 1) 使用接收方公钥加密String encryptedBase64 = encryptForReceiver(payloadJson);// 2) 使用发送方私钥对加密后的 Base64 字符串签名String signatureBase64 = signBySender(encryptedBase64);System.out.println("----------- 加密完成 -----------");System.out.println("加密数据:" + encryptedBase64);System.out.println("签名:" + signatureBase64);System.out.println("---------------------");System.out.println();System.out.println("----------- 开始解密 -----------");// 1) 使用发送方公钥验签(对 encryptedBase64 验签)boolean ok = verifyBySenderPublic(encryptedBase64, signatureBase64);if (!ok) {System.out.println("----------- 验签失败 -----------");return;}// 2) 解密String plainJson = decryptByReceiver(encryptedBase64);// 3) 解析并校验时间戳Payload p = JSONUtil.toBean(plainJson, Payload.class);// TODO 可增加时间戳校验if (p == null || p.timestamp == 0L) {System.out.println("--------- 无效的Payload ---------");return;}System.out.println("解密成功 = " + p);}/*** 传输主体对象*/@Builder@Data@AllArgsConstructor@NoArgsConstructorstatic class Payload {private Long userId;private String username;private Long timestamp;}/*** 生成密钥对*/private void generateKeys() {// 生成发送方密钥对(用于签名)RSA senderRsa = new RSA();String senderPrivateKey = senderRsa.getPrivateKeyBase64();String senderPublicKey = senderRsa.getPublicKeyBase64();// 生成接收方密钥对(用于加密)RSA receiverRsa = new RSA();String receiverPrivateKey = receiverRsa.getPrivateKeyBase64();String receiverPublicKey = receiverRsa.getPublicKeyBase64();System.out.println("========== 发送方密钥(客户端) ==========");System.out.println("私钥(用于签名):");System.out.println(senderPrivateKey);System.out.println("\n公钥(提供给接收方):");System.out.println(senderPublicKey);System.out.println("\n========== 接收方密钥(服务端) ==========");System.out.println("私钥(用于解密):");System.out.println(receiverPrivateKey);System.out.println("\n公钥(提供给发送方):");System.out.println(receiverPublicKey);}/*** 使用接收方公钥对明文 JSON 加密,返回 Base64 文本*/public static String encryptForReceiver(String plainJson) {RSA rsa = SecureUtil.rsa(null, RECEIVER_PUBLIC_KEY);return rsa.encryptBase64(plainJson.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);}/*** 使用发送方私钥对要传输的(已加密的)数据进行签名,返回 Base64 签名* 这里选用 SHA256withRSA*/public static String signBySender(String dataToSign) {// Sign 的构造: (SignAlgorithm, privateKeyBase64, publicKeyBase64)Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, SENDER_PRIVATE_KEY, null);byte[] signBytes = sign.sign(dataToSign.getBytes(StandardCharsets.UTF_8));return Base64.encode(signBytes);}/*** 使用接收方私钥对 Base64 加密文本解密,返回明文字符串*/public static String decryptByReceiver(String encryptedBase64) {RSA rsa = SecureUtil.rsa(RECEIVER_PRIVATE_KEY, null);return rsa.decryptStr(encryptedBase64, KeyType.PrivateKey);}/*** 使用发送方公钥对Base64的加密数据验签** @param encryptedBase64 Base64的加密数据* @param signatureBase64 Base64的签名*/public static boolean verifyBySenderPublic(String encryptedBase64, String signatureBase64) {Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, null, SENDER_PUBLIC_KEY);byte[] sigBytes = Base64.decode(signatureBase64);return sign.verify(encryptedBase64.getBytes(StandardCharsets.UTF_8), sigBytes);}}
密钥对生成
如果需要生成新的RSA密钥对,可以使用以下方法:
private void generateKeys() {// 生成发送方密钥对(用于签名)RSA senderRsa = new RSA();String senderPrivateKey = senderRsa.getPrivateKeyBase64();String senderPublicKey = senderRsa.getPublicKeyBase64();// 生成接收方密钥对(用于加密)RSA receiverRsa = new RSA();String receiverPrivateKey = receiverRsa.getPrivateKeyBase64();String receiverPublicKey = receiverRsa.getPublicKeyBase64();System.out.println("发送方私钥(用于签名): " + senderPrivateKey);System.out.println("发送方公钥(提供给接收方): " + senderPublicKey);System.out.println("接收方私钥(用于解密): " + receiverPrivateKey);System.out.println("接收方公钥(提供给发送方): " + receiverPublicKey);
}
总结
本文介绍的RSA非对称加密与数字签名方案提供了完整的数据传输安全保护:
- 机密性:通过RSA加密确保只有接收方能解密数据
- 身份验证:通过数字签名确认数据发送方身份
- 完整性:通过签名验证确保数据未被篡改
这种方案适用于各种需要安全传输数据的场景,特别是跨网络、跨系统的数据交换场景。实际应用中,可以根据具体需求调整Payload结构和安全策略。