前言
保障数据传输安全是系统设计中的重要环节,本文介绍如何利用RSA非对称加密和数字签名技术构建安全的数据传输方案。
完整代码案例
java
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 Infocare
* @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
@NoArgsConstructor
static 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);
}
}
应用场景
在实际开发中,我们经常需要在不同系统间传输敏感数据。比如客户端与服务器之间的API通信、微服务之间的数据交换等。为了确保这些数据不被窃取和篡改,我们需要同时实现:
- 数据加密:防止传输内容被第三方窃取
- 身份验证:确认数据来源的真实性
- 完整性校验:确保数据在传输过程中未被篡改
技术方案设计
我们采用RSA非对称加密技术结合数字签名来实现上述目标:
- 加密流程:发送方使用接收方的公钥加密数据
- 签名流程:发送方使用自己的私钥对加密后的数据签名
- 验证流程:接收方先验证签名,再使用自己的私钥解密
这种方案既保证了数据的机密性,又提供了身份验证和完整性保护。
核心代码实现
密钥管理
首先定义双方密钥对
java
// 发送方密钥对(用于签名)
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...";
数据加密
使用接收方公钥加密数据
java
public static String encryptForReceiver(String plainJson) {
RSA rsa = SecureUtil.rsa(null, RECEIVER_PUBLIC_KEY);
return rsa.encryptBase64(plainJson.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
}
数字签名
使用发送方私钥对加密后的数据签名
java
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);
}
验签与解密
接收方先使用发送方公钥验证签名,再使用自己的私钥解密数据
java
// 验证签名
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);
}
时间戳防重放
在实际应用中,建议增加时间戳校验机制,防止重放攻击:
java
// 校验时间戳是否在合理范围内
if (Math.abs(System.currentTimeMillis() - p.timestamp) > 5 * 60 * 1000) {
System.out.println("数据已过期");
return;
}
RSA公私密钥生成
java
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);
}