RSA分段加解密 中文乱码

888 阅读3分钟

最近客户要求不能在接口中暴露信息 于是在项目中对请求接口进行加密操作,总结一下

前端常用的加密/解密算法主要有两类: 两者的主要区别在于加密和解密的密钥是否一致,一致的就是对称加密,不一致的就是非对称加密。

非对称加密算法:常用的是RSA加密算法。jsencrypt

npm install jsencrypt --save-dev

采用加解密模式为:前端使用公钥加密,公钥解密

需要进入依赖包中修改jsencrypt.js 文件中方法(可以copy一份 避免修改源码),具体方法如下:

拷贝pkcs1unpad2方法为拷贝pkcs1unpad2public方法

1.  // 这里将如下三行代码注释
1.  // if (b.length - i != n - 1 || b[i] != 2) {
1.  // return null;
1.  // }

拷贝RSAKey.prototype.decrypt方法为RSAKey.prototype.publicDecrypt方法,将 this.doPrivate(c)  改为 this.doPublic(c) ; 将 pkcs1unpad2  改为 pkcs1unpad2public

RSAKey.prototype.publicDecrypt = function (ctext) {
    var c = parseBigInt(ctext, 16);
    var m = this.doPublic(c);
    if (m == null) {
        return null;
    }
    return pkcs1unpad2public(m, (this.n.bitLength() + 7) >> 3);
};

使用rsa加解密过程中会出现待加密和待解密字符串过长情况,需要用到分段加解密:

公钥加密长字符

JSEncrypt.prototype.encryptLong = function(string) {
    var k = this.getKey();
    var maxLength = (((k.n.bitLength() + 7) >> 3) - 11);//117
    try {
        var ct = '';
        // RSA每次加密117bytes,需要辅助方法判断字符串截取位置
        // 1.获取字符串截取点
        var bytes = [];
        bytes.push(0);
        var byteNo = 0;
        var len, c;
        len = string.length;
        var temp = 0;
        for (var i = 0; i < len; i++) {
            c = string.charCodeAt(i);
            if (c >= 0x010000 && c <= 0x10FFFF) {
                byteNo += 4;
            } else if (c >= 0x000800 && c <= 0x00FFFF) {
                byteNo += 3;
            } else if (c >= 0x000080 && c <= 0x0007FF) {
                byteNo += 2;
            } else {
                byteNo += 1;
            }
            // if ((byteNo % maxLength) >= maxLength-3 || (byteNo % maxLength) === 0) {
                if (byteNo - temp >= maxLength) {
                    bytes.push(i);
                    temp = byteNo;
                }
            // }
        }
        // 2.截取字符串并分段加密
        if (bytes.length > 1) {
            for (let i = 0; i < bytes.length - 1; i++) {
                var str;
                if (i === 0) {
                    str = string.substring(0, bytes[i + 1] + 1);
                } else {
                    str = string.substring(bytes[i] + 1, bytes[i + 1] + 1);
                }
                var t1 = k.encrypt(str);
                // console.log(`%c【part${i}】`, 'color:#52BBFF', ``,str)
                // console.log(`part${i}`,t1)
                ct += t1;
            }
            ;
            if (bytes[bytes.length - 1] !== string.length - 1) {
                var lastStr = string.substring(bytes[bytes.length - 1] + 1);
                ct += k.encrypt(lastStr);
                // debugger;
            }
            return hex2b64(ct);
        }
        var t = k.encrypt(string);
        var y = hex2b64(t);
        return y;

    } catch (ex) {
        return false;
    }
};

公钥解密长字符串

JSEncrypt.prototype.publiDecryptLong = function (string) {
    // Return the decrypted string.
    var k = this.getKey();
    var maxLength = ((k.n.bitLength() + 7) >> 3);
    try {
        var str = b64tohex(string);
        var inputLen = str.length;
        var ct = '';
        if (inputLen > maxLength) {
            var lt = str.match(/.{1,256}/g);
            lt.forEach(function(entry) {
                var t1 = k.publicDecrypt(entry);
                ct += t1;
            });
            return ct;
        }
        var y = k.publicDecrypt(b64tohex(string));
        return y;
    } catch (ex) {
        return false;
    }

};

私钥解密长字符串

JSEncrypt.prototype.privateDecryptLong = function (text) {
    var k = this.getKey();
    var maxLength = ((k.n.bitLength() + 7) >> 3);
    try {
        var str = b64tohex(text);
        var inputLen = str.length;

        var ct = "";
        if (inputLen > maxLength) {
            var lt = str.match(/.{1,256}/g);
            lt.forEach(function (entry) {
                console.log(entry)
                var t1 = k.decrypt(entry);
                console.log(t1)
                ct += t1;
                
            });
            return ct;
        }
        var y = k.decrypt(b64tohex(string));
        return y;
        }
    catch (ex) {
        return false;
    }
};

//公钥加密
export const jsencrypt = (value) => {
  const jse = new JSEncrypt();
  jse.setPublicKey(PUBLIC_KEY);
  // 加密前使用encodeURIComponent解决中文乱码
  // const encodestr =encodeURIComponent(value) 
  return jse.encryptLong(value)
}
//公钥解密
export function jsdecrypt(value) {
  const jse = new JSEncrypt();
  jse.setPublicKey(PUBLIC_KEY);
  const jsestr = jse.publiDecryptLong(value)
  // return decodeURIComponent(jsestr)
  return jsestr
}

一顿猛虎操作后,发现在代码应用过程中还是会有加密错误的情况,非常偶现,可能是截断那块代码的问题 还不确定

于是还是选择了AES

对称加密算法:常用的是AES加密算法 crypto-js

crypto-js 是一个纯 javascript 写的加密算法类库 ,可以非常方便地在 javascript 进行 MD5、SHA1、SHA2、SHA3、RIPEMD-160 哈希散列,进行 AES、DES、Rabbit、RC4、Triple DES 加解密。

npm install crypto-js

加密字符没有长度限制,具体使用如下

/**
 * 工具类
 */
import CryptoJS from 'crypto-js'

export default {// 加密
  encrypt (word) {
    const { keyStr,ivStr } = getIVandKEY()
    // 加密前使用encodeURIComponent解决中文乱码
    const urlstr = encodeURIComponent(word) 
    var wordstr = CryptoJS.enc.Utf8.parse(urlstr)
    var key = CryptoJS.enc.Utf8.parse(keyStr)
    var ivv = CryptoJS.enc.Utf8.parse(ivStr)
    var encrypted = CryptoJS.AES.encrypt(
      wordstr, 
      key, 
      {  iv: ivv,  mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
    )
    return encrypted.toString()
  },
  // 解密
  decrypt (word) {
    const { keyStr,ivStr } = getIVandKEY()
    var key = CryptoJS.enc.Utf8.parse(keyStr)
    var ivv = CryptoJS.enc.Utf8.parse(ivStr)
    var decrypt = CryptoJS.AES.decrypt(
      word, 
      key, 
      { iv: ivv,  mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
    )
    const urlstr = CryptoJS.enc.Utf8.stringify(decrypt).toString()
    return decodeURIComponent(urlstr) 
  }

}

😊