前后端API交互使用RSA和AES加密解密(js、Java)

前后端API交互使用RSA和AES加密解密(js、Java)

一、前言

​ 数据安全是非常重要的,如用户相关信息、订单相关信息等。web应用程序可通过F12查看服务器API返回的信息。那么极容易造成信息泄露。

​ 针对上面的问题。设计了一套信息加密形式。如果不足或者存在更好的方式,欢迎大家讨论指正。

二、整体流程

在这里插入图片描述

  1. 客户端启动,发送请求到服务端,服务端用RSA算法生成一对公钥和私钥,我们简称为pubkey1,prikey1,将公钥pubkey1返回给客户端。
  2. 客户端拿到服务端返回的公钥pubkey1后,自己用RSA算法生成一对公钥和私钥,我们简称为pubkey2,prikey2,并将公钥prikey2通过公钥pubkey1加密,加密之后传输给服务端。
  3. 此时服务端收到客户端传输的密文,用私钥prikey1进行解密,因为数据是用公钥pubkey1加密的,通过解密就可以得到客户端生成的公钥prikey2
  4. 然后自己在生成对称加密,也就是我们的AES,其实也就是相对于我们配置中的那个16的长度的加密key,生成了这个key之后我们就用公钥prikey2进行加密,返回给客户端,因为只有客户端有prikey2对应的私钥pubkey2,只有客户端才能解密,客户端得到数据之后,用pubkey2进行解密操作,得到AES的加密key,最后就用加密key进行数据传输的加密,至此整个流程结束。

三、RSA加解密获取AES加密的key

RSA算法密钥长度的选择:

​ RSA算法密钥长度的选取直接关系到加解密、签名验签的安全强度和运算速度。密钥的长度越大,安全强度越高,算法运算速度越慢。所以RSA算法密钥的长度要结合项目的实际情况来选取,以求在安全和效率之间取得平衡。总体来说,现在市场上RSA密钥长度应用较多的是1024位和2048位。简称RSA1024算法和RSA2048算法。

​ RSA加密算法特性:

  1. 密钥长度增长一倍,公钥操作所需时间增加约4倍,私钥操作所需时间约增长8倍,公私钥生成时间约增长16倍。

  2. 一次能加密的密文长度与公钥长度成正比,如RSA1024,一次能加密的内容长度为 1024/8 = 128byte(包含填充字节)。所以非对称加密一般都用于加密对称加密算法的密钥,而不是直接加密内容。

  3. 加密后密文的长度为公钥的长度,例如公钥长度为1024Bit(128Byte),最后生成的密文固定为 1024Bit(128Byte)。

综上,当密文过长时,需要分开进行加密和解密。附上后台加解密代码:

package com.skysys.qicloud.service.utils;

import sun.misc.BASE64Decoder;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.HashMap;

public class RSAUtils {
    
    /**
     * 使用getPublicKey得到公钥,返回类型为PublicKey
     *
     * @paramv String to PublicKey
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 转换私钥
     *
     * @param
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        return privateKey;
    }


    /**
     * 生成公钥和私钥
     *
     * @throws NoSuchAlgorithmException
     */
    public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException {
        HashMap<String, Object> map = new HashMap<String, Object>();
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        map.put("public", publicKey);
        map.put("private", privateKey);
        return map;
    }
    

    /**
     * 私钥加密
     * @param generatePrivate
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeyException
     * @throws IOException
     */
    public static String testEncrypt(RSAPrivateKey generatePrivate,String data) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException{
        Cipher ci = Cipher.getInstance("RSA");
        ci.init(Cipher.ENCRYPT_MODE, generatePrivate);

        byte[] bytes = data.getBytes();
        int inputLen = bytes.length;
        int offLen = 0;//偏移量
        int i = 0;
        ByteArrayOutputStream bops = new ByteArrayOutputStream();
        while(inputLen - offLen > 0){
            byte [] cache;
            if(inputLen - offLen > 117){
                cache = ci.doFinal(bytes, offLen,117);
            }else{
                cache = ci.doFinal(bytes, offLen,inputLen - offLen);
            }
            bops.write(cache);
            i++;
            offLen = 117 * i;
        }
        bops.close();
        byte[] encryptedData = bops.toByteArray();
        String encodeToString = java.util.Base64.getEncoder().encodeToString(encryptedData);
        return encodeToString;
    }

    /**
     * 私钥解密
     * @param generateprivate
     * @param data
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws NoSuchPaddingException
     * @throws InvalidKeySpecException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IOException
     */
    public static String testDecrypt(RSAPrivateKey generateprivate,String data) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, IOException {
        Cipher ci = Cipher.getInstance("RSA");
        ci.init(Cipher.DECRYPT_MODE,generateprivate);

        byte[] bytes = java.util.Base64.getDecoder().decode(data);
        int inputLen = bytes.length;
        int offLen = 0;
        int i = 0;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        while(inputLen - offLen > 0){
            byte[] cache;
            if(inputLen - offLen > 128){
                cache = ci.doFinal(bytes,offLen,128);
            }else{
                cache = ci.doFinal(bytes,offLen,inputLen - offLen);
            }
            byteArrayOutputStream.write(cache);
            i++;
            offLen = 128 * i;

        }
        byteArrayOutputStream.close();
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        return new String(byteArray);
    }
}

前台使用node-rsa.js实现RSA加解密。需要注意的是

1. node-rsa加解密的填充方式是 pkcs1_oaep 。而在js中加密解密默认使用的是 pkcs1。所以需要加上key.setOptions({encryptionScheme: ‘pkcs1’})

2. 前台加密之后可能存在特殊字符,如“+”。加密字符串转base64编码时,特殊字符不会被转码,传输过程中特殊字符可能存在丢失,所以需要加上encodeURIComponent(x),可以将一些特殊字符转成编码

var NodeRSA = require('node-rsa');

//调用“获取”加密公钥获得返回值
var str ='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL+vGeXIxSQGZ+rK4eYajnstMO2u3u0xZFWNFd3oXsKC6ZfSRvI9/Wkpvdc1tnbk0FkbscDHXON2Q89T+9ihg8r4uvZ6OYE4IWyVLrgZkLv9hdtil9R2o57nqKaN6YWGuYYFoLUq1R8H1hVDuJIsUmmEH9Bl4da3Gkg7SfTwFDiQIDAQAB'

//生成一对RSA密钥
var key = new NodeRSA({b: 512});
key.setOptions({encryptionScheme: 'pkcs1'})
var publicDer = key.exportKey('public-der');
var privateDer = key.exportKey('pkcs8-private-der');
var publicKeyStr = publicDer.toString('base64')
var privateKeyStr = privateDer.toString('base64')
console.log('publicKeyStr: ', publicKeyStr)
console.log('privateKeyStr: ', privateKeyStr)

//使用公钥2对秘钥1进行加密,使用x作为参数,调用“获取密钥接口”
var key2 = new NodeRSA();
key2.setOptions({encryptionScheme: 'pkcs1'})
key2.importKey('-----BEGIN PUBLIC KEY-----\n'+str+'-----END PUBLIC KEY-----', "public")
var x = key2.encrypt(privateKeyStr, 'base64');
console.log('encrypt :',encodeURIComponent(x))

//第二步中获得的公钥,对第三步的返回值进行解密,得到AES密钥
var publickey1= "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALIGLG60w2oR4LZrGzxaYDEfOVZFRqRwSvUUI7O7FqKRj11OCLL186DWVB8nL1csr/8BWBWbN2M/kFqQjresTLUCAwEAAQ==";

var key3 = new NodeRSA();
key3.setOptions({encryptionScheme: 'pkcs1'})
key3.importKey('-----BEGIN PUBLIC KEY-----\n'+publickey1+'-----END PUBLIC KEY-----', "public")
var data = "AgQeNnK8Pu/wEkgsp0at7j99BZwPAlRM4n/SYzxKM+lWTfzmt/2shqYO2fnLsrHUK6Ooww5lj9AjGOmR2erNDw==";
var AesKey = key3.decryptPublic(data, 'utf8');
console.log("AesKey:",AesKey);

使用Java进行RSA加解密,可以参考博文:https://www.cnblogs.com/jpfss/p/10207044.html

四、使用AES对请求和响应都进行加解密操作

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值