最近在學習Vue+Node+Mongodb,索性就結合起來想做個個人博客,遇到的第一個坑就是如何在注冊和登錄的時候,給密碼加密傳給后臺,后臺解密。后續(xù)遇到的坑在慢慢梳理和總結哈
網(wǎng)上看了好多加密和解密的中間件,最終選擇了使用jsencrypt,后臺生成的公鑰和私鑰使用了node-rsa。簡述一下前后端在這加密和解密的大致流程哈(至于原理級別的,暫不太清楚):
后臺使用node-rsa生成公鑰和私鑰---->
后臺寫個獲取公鑰的接口(將公鑰轉化為指定格式)---->
前端調(diào)用接口獲取公鑰---->
前端使用JSEncrypt中間件設置公鑰,并加密密碼---->
前端將加密后的數(shù)據(jù)通過接口(如登錄接口)傳給后臺---->
后臺使用密鑰加密前端加密的數(shù)據(jù),在用解密后的數(shù)據(jù)查詢或者保存到數(shù)據(jù)庫中
1.安裝包
"jsencrypt": "^3.0.0-rc.1",
"node-rsa": "^1.0.5",
備注:使用Node開發(fā)時,建議安裝nodemon,可以熱加載后臺改的代碼,超級好用,只需要安裝一下包,在啟動的使用nodemon app.js即可
2.后臺生成公鑰和密鑰
const NodeRSA = require('node-rsa');
const fs = require('fs');
//生成公鑰
function generator() {
var key = new NodeRSA({ b: 512 })
key.setOptions({ encryptionScheme: 'pkcs1' })
var privatePem = key.exportKey('pkcs1-private-pem')
var publicPem = key.exportKey('pkcs8-public-pem')
fs.writeFile('./pem/public.pem', publicPem, (err) => {
if (err) throw err
console.log('公鑰已保存!')
})
fs.writeFile('./pem/private.pem', privatePem, (err) => {
if (err) throw err
console.log('私鑰已保存!')
})
}
generator();
這樣就可以在pem目錄下生成兩個文件:private.pem和public.pem,看文件名就知道誰是公鑰誰是密鑰了哈。
注意(此處有坑)---前端加密后的數(shù)據(jù)始終為false
之前自己也是在網(wǎng)上復制的代碼,有一個地方有問題,對比一下哈

在導出公鑰和密鑰的時候,exportKey的參數(shù)相同,都為pkcs1,這就導致了前端獲取到了公鑰加密之后的數(shù)據(jù)為始終為false。找了好久才知道問題所在,追溯在node-rsa中的源碼可以看到:

exportKey中的參數(shù)是有固定的值的,不然就會導致導出的公鑰或者私鑰有問題。
3.后臺設置獲取公鑰接口供前端使用
router.get('/api/getPublicKey', (req, res) => {
try {
let publicKey = fs.readFileSync('./pem/public.pem', 'utf-8');
console.log('publicKey', publicKey)
res.send({ 'status': 0, 'msg': '公鑰獲取成功', 'resultmap': publicKey });
} catch (err) {
res.send(err);
}
})
我使用的是Express中間件哈,使用Koa或者其他中間件的自行修改。
4.前端獲取公鑰并加密數(shù)據(jù)傳給后臺
import { JSEncrypt } from "jsencrypt";
that.$axios
.get(webUrl + "getPublicKey")
.then(res => {
if (res.data.status === 0) {
let encryptor = new JSEncrypt(); //實例化
encryptor.setPublicKey(res.data.resultmap); //設置公鑰
console.log(that.password);
console.log(encryptor.encrypt(a));
let data = {
name: that.name,
password: encryptor.encrypt(that.password), //加密
nickName: that.nickName
};
that.$axios
.post(webUrl + "admin/signUp", data)
.then(response => {
that.$message({
type: "success",
message: response.data.msg
});
if (response.data.status == 1) {
that.back();
}
})
.catch(reject => {
console.log(reject);
});
}
})
.catch(err => {
console.log(err);
});
5.后臺用私鑰解密
// 注冊
router.post('/api/admin/signUp', (req, res) => {
//是否重名
db.User.find({ name: req.body.name }, (err, docs) => {
if (err) {
res.send(err);
return
}
if (docs.length > 0) {
res.send({ 'status': 0, 'msg': '用戶名已注冊' });
} else {
db.User.find({ nickName: req.body.nickName }, (err, docs) => {
if (err) {
res.send(err);
return
}
if (docs.length > 0) {
res.send({ 'status': 0, 'msg': '昵稱已注冊' });
} else {
const privateKey = fs.readFileSync('./pem/private.pem', 'utf8'); //讀取私鑰
let buffer1 = Buffer.from(req.body.password, 'base64'); //轉化格式
let password = crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING // 注意這里的常量值要設置為RSA_PKCS1_PADDING
}, buffer1).toString('utf8');
console.log('解密之后的密碼', password);
let newUser = new db.User({
name: req.body.name,
password: password,
nickName: req.body.nickName,
avatar: null,
// type: req.body.type
type: 2//1為管理員,2為游客,寫死,新建管理員數(shù)據(jù)庫直接改
});
newUser.save(function (err) {
if (err) {
res.send(err);
} else {
res.send({ 'status': 1, 'msg': '注冊成功' });
}
})
}
})
}
})
})
經(jīng)驗證,后臺解密后的密碼和前端加密前的數(shù)據(jù)一致,說明加密和解密的過程都OK。加密和解密有很多種方法的,以上總結是自己嘗過OK的一種,特此總結一下,希望能幫助到其他人。