RSA 非对称加密与数字签名的安全数据传输

前言

保障数据传输安全是系统设计中的重要环节,本文介绍如何利用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通信、微服务之间的数据交换等。为了确保这些数据不被窃取和篡改,我们需要同时实现:

  1. 数据加密:防止传输内容被第三方窃取
  2. 身份验证:确认数据来源的真实性
  3. 完整性校验:确保数据在传输过程中未被篡改

技术方案设计

我们采用RSA非对称加密技术结合数字签名来实现上述目标:

  1. 加密流程:发送方使用接收方的公钥加密数据
  2. 签名流程:发送方使用自己的私钥对加密后的数据签名
  3. 验证流程:接收方先验证签名,再使用自己的私钥解密

这种方案既保证了数据的机密性,又提供了身份验证和完整性保护。

核心代码实现

密钥管理

首先定义双方密钥对

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);
}
相关推荐
Neoooo2 小时前
数据库备份攻略:支持Docker/本地部署
后端·mysql
shark_chili2 小时前
深入浅出:进程与线程的奥秘 - 从内存管理到CPU调度的艺术
后端
间彧3 小时前
JWT Claims详解
后端
IT_陈寒4 小时前
JavaScript性能优化:7个90%开发者不知道的V8引擎黑科技
前端·人工智能·后端
摸鱼的春哥4 小时前
“全栈模式”必然导致“质量雪崩”!和个人水平关系不大
前端·javascript·后端
野犬寒鸦7 小时前
多级缓存架构:性能与数据一致性的平衡处理(原理及优势详解+项目实战)
java·服务器·redis·后端·缓存
Tony Bai12 小时前
【Go开发者的数据库设计之道】05 落地篇:Go 语言四种数据访问方案深度对比
开发语言·数据库·后端·golang
eqwaak013 小时前
Flask实战指南:从基础到高阶的完整开发流程
开发语言·后端·python·学习·flask
笨蛋不要掉眼泪13 小时前
SpringBoot项目Excel成绩录入功能详解:从文件上传到数据入库的全流程解析
java·vue.js·spring boot·后端·spring·excel