异或运算配合 RSA 实现加密和解密
目前的项目需要用到加密算法,内部协商实现了一种简单的版本。这里使用了 2 种加密方式,一种是基于 Unicode 编码加密,一种是 RSA 加密。
异或,英文为 exclusive OR,缩写成 xor,如:
a = 1; b = 2; a^b=3
RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
我们实现的加密原理:系统生成 RSA 公钥和私钥,公钥使用 Unicode 编码加密处理后写入 JS 文件,用户提交敏感数据时,使用自定义解码规则解出 RSA 公钥,使用 RSA 加密数据传入后台,后台使用 RSA 私钥解析数据。
首先是自定义使用的公钥和私钥,我测试的时候是使用 在线工具 生成的。
它生成的字符是这样的:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbj+vUIk6Wruqa2HVZij9lr2LC
TTLgsAvSa3+rTlBSfzkkQwtgENqe7ZcgZ6VPofasLTR3W6IAxqvh1jm8a/2f5IPL
VEgq4cuj3Uu2TTOs054vcGztEIdrGzFYqEcS4PJVlCJtMqrsR+AOhGQQTMnwPiMJ
LsC6sd3us9KKMn3sKQIDAQAB
我们使用 NodeRSA 这个包进行 RSA 加密处理,所以,上面的字符串要适当修改一下:
const PublickKey = '-----BEGIN PUBLIC KEY-----\n'+
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbj+vUIk6Wruqa2HVZij9lr2LC\n'+
'TTLgsAvSa3+rTlBSfzkkQwtgENqe7ZcgZ6VPofasLTR3W6IAxqvh1jm8a/2f5IPL\n'+
'VEgq4cuj3Uu2TTOs054vcGztEIdrGzFYqEcS4PJVlCJtMqrsR+AOhGQQTMnwPiMJ\n'+
'LsC6sd3us9KKMn3sKQIDAQAB\n'+
'-----END PUBLIC KEY-----';
使用异或运算和 Unicode 编码实现加密解密:
// 加密处理
function encode(str, split) {
let res = [];
for (let i = 0; i < str.length; i++) {
let a = str.charCodeAt(i); // 获取当前字符的 Unicode 编码数字
let b = 'abcdefghijklmnopqrstuvwxyz'.charCodeAt(Math.ceil(Math.random() * 25)); // 使用随机字母作为异或运算混淆
res.push([a ^ b, b].join(','));
}
return res.join(split);
}
// 解密处理
function decode(str, split) {
let res = [];
str = str.split(split); // 解析出所有的字符
str.forEach(s => {
s = s.split(',');
res.push(String.fromCharCode(s[0] ^ s[1])); // 使用异或运算还原原始字符
});
return res.join('');
}
注意的内容:
- 加密和解密中使用的
split
设计具有迷惑性,如:我们设置为,0
,加密后的密文则为:93,112,091,118,088,117,069,104,093,112,042,104,046,107,037,98
的格式,看上去是一堆数字,解析的初始化实际为93,112 | 91,118 | 88,117 | 69,104 | 93,112 | 42,104 | 46,107 | 37,98
; - 这里的加密和解密是忽略内容格式的,也就是说,传入和传出的值都是字符串,所以,在调用的时候,需要先把数据处理成字符串在进行加密;
为了尽量保证公钥的不直接暴露在前台,我们需要使用上面的加密算法先把生成的公钥进行加密:
let EndeoRSAPublicKey = encode(PublickKey, ',0');
// 加密后的值:95,98,091,102,078,115,074,119,086,107,072,117,081,···略···,071,122
此时 PublickKey
已经被加密处理了,而其真实的值已经不需要了,我们不应该把 encode
函数和 PublickKey
的真实值暴露在前台 js 中,而应该仅保留 decode
和 EndeoRSAPublicKey
的值。
在提交数据时,我们需要先把 EndeoRSAPublicKey
解析为真实的公钥,然后再使用 RSA 进行加密。
// 引入 RSA 加密扩展
import NodeRSA from 'node-rsa'
import axios from 'axios'
// 原始数据
let data = {
username: 'user',
password: '123321',
};
let key = new NodeRSA(decode(EndeoRSAPublicKey, ',0'));
axios.post('./test.do', key.encrypt(data,'base64')).then(res => {
//
}, err => {
throw err;
});
后端需要使用对应的私钥进行数据解密。
提供一个 NodeJs 版本的测试,将该文件存储为 rsa.js,然后终端中使用 node rsa.js
命令执行该文件的内容:
const NodeRSA = require('node-rsa');
// 后端提供的公钥
const PublickKey = '-----BEGIN PUBLIC KEY-----\n' +
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCvcZwLmfIk9XOHUksUwsgB1mT\n' +
'okM+aoRYR3E9NRcZ/Y+YjpUlnisk7LO26Ps4P3O1r1BW5ZBpWtQmzFjbNPB7PWXu\n' +
'tavQycWTSM70RX2eardZabCDkCs8FztzTb2OH2i+1V6CWLO0hkBVWCSAPV5kfW12\n' +
'L/vE+4a/8LKzuVDEmQIDAQAB\n' +
'-----END PUBLIC KEY-----';
const PrivateKey = '-----BEGIN PRIVATE KEY-----\n' +
'MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMK9xnAuZ8iT1c4d\n' +
'SSxTCyAHWZOiQz5qhFhHcT01Fxn9j5iOlSWeKyTss7bo+zg/c7WvUFblkGla1CbM\n' +
'WNs08Hs9Ze61q9DJxZNIzvRFfZ5qt1lpsIOQKzwXO3NNvY4faL7VXoJYs7SGQFVY\n' +
'JIA9XmR9bXYv+8T7hr/wsrO5UMSZAgMBAAECgYEAlXWE6PAUovIjM49ya1xIu4oo\n' +
'i5ALP8oMTJx4IluuoTnjjVhQy5A62Jn5y7W/qQm5yoUEicyiKtmU3ToUMBjPPs1j\n' +
'HJ2pjpJ1Qz4vWy1XPq6h8ozFSr6nrOw+9bk8Y5+TyGDFc6FDNjqt2Bg89xXSzwdx\n' +
'Gp1ZyRjo1l6Qb2fPUMkCQQD7LoqLxQ+9Ssau7mRX+2iLVN8zg22KoLUBi3ylvSS3\n' +
'+Vcbf69pGmfmpU2ii2RjmpVgnkYDpl4C9d9xlqCMaEmfAkEAxnoTd9qUE44fAW2x\n' +
'FAuhnHuf3xpZLMpdSe2j5bVIrXtv9+9Fj86Z0vW+j24tAkytDRy0GFGqBMgNdjBA\n' +
'IEg2xwJAPaQPRfunQCnglj9Uiq7c2gyK9eZT9Ig5w1ZK0ZWYNDnRYaM1FdLwGo8I\n' +
'fVI94Z+m9t4AipbCTXGvUv3HCo3xOwJBAJmFsetiemmJ5DfRpkhQGukUwvvqwJGh\n' +
'0nktxToYeKggM+K/BLqQ33FLvuPpIA2IS885paCuAmoCaE9EUUXnNd8CQQDn/66B\n' +
'LGHLJjBcav7PKQkMSubt/BQnNfjZMnAMMbR7/V/uFFHuqrN/3HuIGuOt5HVaSUoR\n' +
'2G8ESeKbcCrFQ86n\n' +
'-----END PRIVATE KEY-----';
// 加密处理
function encode(str, split) {
let res = [];
for (let i = 0; i < str.length; i++) {
let a = str.charCodeAt(i); // 获取当前字符的 Unicode 编码数字
let b = 'abcdefghijklmnopqrstuvwxyz'.charCodeAt(Math.ceil(Math.random() * 25)); // 使用随机字母作为异或运算混淆
res.push([a ^ b, b].join(','));
}
return res.join(split);
}
// 解密处理
function decode(str, split) {
let res = [];
str = str.split(split); // 解析出所有的字符
str.forEach(s => {
s = s.split(',');
res.push(String.fromCharCode(s[0] ^ s[1])); // 使用异或运算还原原始字符
});
return res.join('');
}
// 加密处理
let test = 'aX4q/NlmeG5E5TndKCJEOgJJnmByo4Sd5bEGl6CaP7KypyfKYkBrA2Zlh4mPtDZ9F0f7J48LRW88eZWATNuXEA3FTfNgnwpRfwLWttunaQAZZjTMTE4BsszYOyKIGzDeKD4S9xzasW+/46jI3Dw7+Oc7u6/FHWysEfPyyAiuGGsEEouuHJ6h0NHNM5lRYWw+cGG2wH41iNpm7VoAqFQ6l+hWD9ZfcA3OXXWxE4xRhkhZ3W4XYjNvPR2+lC852HQiOk0wWdHnH9hjKOxgKSrahTvk8+msae38gn4rTa3hbxudezcsRqSG+caC7+/QUxNxoqlbcHr/1gCmX4cFgxQIxS4IigSFOF/t56O4Hytef4BihDdKemNMWMgqzzjs+rzZYW66ofEmzvpM4zIsp1DkAG1BuS5d2jzdHVgXPI0YAg4lCCILr9vFrGb1tKexGWSECRMP4mj4w0TsLhlLjEBVCc119uYSqLOWjDhhIrpNaEiNDbyV1nnrJ0XCl4/n83d/';
let split = ',0';
let output = encode(PublickKey, split);
console.log('加密后的公钥:', output);
let realPublicKey = decode(output, split);
console.log('解密出的原始公钥:', realPublicKey);
let deKey = new NodeRSA(PrivateKey);
console.log('测试数据:', test);
console.log('私钥解析出的测试数据:', deKey.decrypt(test, 'utf-8'));
执行效果如下:
执行效果