JAVA+Node/JavaScript 前后端通讯 RSA 加解密实现

实际项目中,前后端或跨语言加密通讯的场景十分常见。这里以 JavaNode.js(兼容浏览器)两种开发语言为例,实现 RSA 加解密通讯。

JAVA端加解密

此代码采用分段加解密,理论上支持无限长度的文本内容

java 复制代码
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public final class RSAProvider {

    private final static String RSA = "RSA";
    private final static String SHA256WithRSA = "SHA256WithRSA";
    private final static int MAX = 117;

    private String privateKey;
    private String publicKey;

    /**
     * 创建 RSA 工具类,自动生成公私钥(字符串格式)
     * @throws Exception
     */
    public RSAProvider() throws Exception {
        //自动生成密钥
        KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA);
        generator.initialize(1024, new SecureRandom());
        KeyPair keyPair = generator.genKeyPair();

        Base64.Encoder encoder = Base64.getEncoder();
        initKey(
                encoder.encodeToString(keyPair.getPublic().getEncoded()),
                encoder.encodeToString(keyPair.getPrivate().getEncoded())
        );
    }

    /**
     * 使用特定公私钥初始化工具类
     * @param pubKey
     * @param priKey
     */
    public RSAProvider(String pubKey, String priKey){
        initKey(pubKey, priKey);
    }

    private void initKey(String pubKey, String priKey){
        this.privateKey = priKey;
        this.publicKey = pubKey;
    }

    private PrivateKey buildPriKey() throws Exception {
        if(privateKey == null || privateKey.isEmpty())  throw new RuntimeException("PRIVATE KEY is empty!");

        return KeyFactory.getInstance(RSA)
                .generatePrivate(
                        new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))
                );
    }

    private PublicKey buildPubKey() throws Exception {
        if(publicKey == null || publicKey.isEmpty())  throw new RuntimeException("PUBLIC KEY is empty!");

        return KeyFactory.getInstance(RSA)
                .generatePublic(
                        new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey))
                );
    }

    /**
     * 使用私钥签名
     * @param content
     * @return
     */
    public String sign(String content) throws Exception {
        Signature signature = Signature.getInstance(SHA256WithRSA);
        signature.initSign(buildPriKey());
        signature.update(content.getBytes());
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    /**
     * 公钥验签
     * @param message
     * @param signed
     * @return
     * @throws Exception
     */
    public boolean verifySign(String message, String signed) throws Exception {
        Signature signature = Signature.getInstance(SHA256WithRSA);
        signature.initVerify(buildPubKey());
        signature.update(message.getBytes());
        return signature.verify(Base64.getDecoder().decode(signed));
    }


    /**
     * 使用公钥加密
     * @param content 任意长度的字符
     * @return
     */
    public String encrypt(String content) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, buildPubKey());
        //此方法只能加密长度小于 117 bytes
//        return Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes()));

        byte[] bytes = content.getBytes();
        ByteArrayOutputStream bops = new ByteArrayOutputStream();
        int offLen = 0;

        while (bytes.length - offLen > 0){
            bops.write(cipher.doFinal(bytes, offLen, Math.min(bytes.length - offLen, MAX)));
            offLen += MAX;
        }
        bops.close();
        return Base64.getEncoder().encodeToString(bops.toByteArray());
    }

    /**
     * 使用私钥解密
     * @param data 任意长度的密文(分段解密)
     * @return
     * @throws Exception
     */
    public String decrypt(String data) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, buildPriKey());
        // 此方法报错:Data must not be longer than 128 bytes
//        return new String(cipher.doFinal(Base64.getDecoder().decode(data)));

        byte[] bytes =  Base64.getDecoder().decode(data);
        ByteArrayOutputStream bops = new ByteArrayOutputStream();
        int offLen = 0;

        while (bytes.length - offLen > 0){
            bops.write(cipher.doFinal(bytes, offLen, Math.min(bytes.length - offLen, 128)));
            offLen += 128;
        }
        bops.close();
        return bops.toString();
    }

    public String getPrivateKey() {
        return privateKey;
    }
    public String getPublicKey() {
        return publicKey;
    }
}

使用示例:

kotlin 复制代码
RSAProvider().also {
    println("公钥:${it.publicKey}")
    println("私钥:${it.privateKey}")

    val text = "Hello,集成显卡!!"
    val miwen = it.encrypt(text)
    println("密文:${miwen}")
    println("解码:${it.decrypt(miwen)}")
}

Node/JavaScript 加解密

javascript 复制代码
// 请先执行 npm i node-rsa
const NodeRSA = require("node-rsa")

const encryptionScheme = "pkcs1"
module.exports = {
    encrypt (data, publicKey) {
        const pubKey = new NodeRSA(publicKey, 'pkcs8-public')
        //设置与后端一致的加密方式 pkcs1
        pubKey.setOptions({ encryptionScheme })
        return pubKey.encrypt(Buffer.from(data), 'base64')
    },

    decrypt (data, privateKey){
        const priKey = new NodeRSA(privateKey, 'pkcs8-private')
        priKey.setOptions({ encryptionScheme })
        return priKey.decrypt(Buffer.from(data, 'base64'), 'utf8')
    },

    sign (data, privateKey){
        const priKey = new NodeRSA(privateKey, 'pkcs8-private')
        return priKey.sign(Buffer.from(data)).toString('base64')
    },

    verify (data, signature, publicKey){
        const pubKey = new NodeRSA(publicKey, 'pkcs8-public')
        return pubKey.verify(data, Buffer.from(signature, 'base64'))
    }
}
相关推荐
_Aaron___16 分钟前
Spring AI 接入 MCP:工具调用不是“能调就行”,关键是边界治理
java·人工智能·spring
向量引擎31 分钟前
从零起步,如何打造专属向量引擎 API 中转工作流?
java·服务器·前端
LJianK131 分钟前
普通接口,用到getter和setter方法的地方,jackson转换
java
辰海Coding34 分钟前
MiniSpring框架学习-分解 Dispatcher
java·学习·spring·架构
AI人工智能+电脑小能手38 分钟前
【大白话说Java面试题 第84题】【Mysql篇】第14题:为什么用 InnoDB 存储引擎的表建议用整型的自增主键?
java·开发语言·数据库·mysql·面试
小江的记录本1 小时前
【JVM虚拟机】JVM调优:常用JVM参数、调优核心指标、OOM排查、GC日志分析、Arthas工具使用(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
金銀銅鐵1 小时前
[Java] 用图形化界面演示 iadd, isub, iconst_<i> 指令的效果
java·后端·python
J2虾虾1 小时前
Spring AI Alibaba文档
java·人工智能·spring
YikNjy1 小时前
break和continue
java·开发语言·算法
SomeOtherTime1 小时前
Geojson相关(AI回答)
java·前端·python