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'))
    }
}
相关推荐
gobeyye19 分钟前
spring loC&DI 详解
java·spring·rpc
鱼跃鹰飞20 分钟前
Leecode热题100-295.数据流中的中位数
java·服务器·开发语言·前端·算法·leetcode·面试
我是浮夸23 分钟前
MyBatisPlus——学习笔记
java·spring boot·mybatis
什么鬼昵称27 分钟前
Pikachu-xxe-xxe漏洞
网络·安全·xxe
TANGLONG22230 分钟前
【C语言】数据在内存中的存储(万字解析)
java·c语言·c++·python·考研·面试·蓝桥杯
杨荧32 分钟前
【JAVA开源】基于Vue和SpringBoot的水果购物网站
java·开发语言·vue.js·spring boot·spring cloud·开源
Leighteen1 小时前
ThreadLocal内存泄漏分析
java
java6666688881 小时前
Java中的对象生命周期管理:从Spring Bean到JVM对象的深度解析
java·jvm·spring
柚乐果果1 小时前
数据分析实战简例
java·大数据·python