当你知道如何在应用中针对不同的使用场景使用不同的加密算法,你的应用才是安全的。因此,这篇文章将会介绍平常工作中比较常用的几种加密方式,并给出对应的Node.js代码。
保护密码
用户在注册应用的时候难免会使用一些过于简单的密码,同时也倾向于在不同的站点使用相同的密码。最可怕的是用来存储用户密码的数据库可能被黑客侵入。因此,对于用户密码的保护是最基础的防护措施。(ps: 大家可以使用这个网站来检查自己的密码是否被泄露了。)
对于密码的加密,通常使用的是哈希(hash)算法。比较常见的哈希算法有Argon2、PBKDF2、scrypt和bcrypt等。关于这些加密算法的介绍,大家可以参考Password Storage Cheat Sheet这篇文章。
node.js中提供了crypto模块,该模块主要用于实现基础的加解密算法。接下来就来介绍一下如何使用该模块来实现对于密码的保护:
// 这里是最基础的md5算法
const crypto = require('crypto');
const hash = crypto.createHash('md5'); // 最基本的md5算法
hash.update('password1'); // update用来更新数据
hash.digest('hex'); // 以string格式输出密文
hash.digest('sha256');
对于md5算法,这里推荐使用https://hashtoolkit.com/这个网站进行调试。普通的md5算法对于程序员来说就是明文的,因此我们一般还会对它加盐(salt the hash):
const crypto = require('crypto');
const password = 'ddd';
const salt = crypto.randomBytes(256).toString('hex');
// 这里使用pbkdf2算法
const hashedPwd = crypto.pbkdf2Sync(password, salt, 100000, 512, 'sha512');
console.log(hashedPwd.toString('hex'));
保护存储数据
对称加密算法(Symmetric Encryption)是一种使用同一密钥进行加密和解密文本的算法,这也意味着通信双方要使用同一密钥进行加解密。对称加密算法对于大型数据的加密来说速度很快,因此它也主要用于存储数据的加密。比较常用的对称加密算法有AES、Blowfish、DES和RC4等。
crypto模块中主要利用以下方法来实现对称加密:
-
createCipheriv: 提供对称加密, 该方法接收三个参数,第一个是加密算法、第二个是密钥,而第三个是初始向量(initialization vector)
-
update 和 final: 先更新数据,然后获得密文
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const password = 'Hello world';
const salt = crypto.randomBytes(32);
cnst key = crypto.scryptSync(password, salt, 32);
const iv = crypto.randomBytes(16);
const cipher = crypto.crateCipheriv(algorithm, key, iv);
let ssn = '111-000-2342';
let encrypted = cipher.update(ssn, 'utf8', 'hex');
// 获得加密密文
encrypted += cipher.final('hex');
// 解密算法是反向操作
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.final('utf8');
console.log(decrypted)
同时用于对称加密的密钥也需要有合适的密码管理策略进行处理,否则一旦密钥被破解,数据的安全性就无从保障。通常我们会使用一个密钥管理系统(KMS)进行管理密钥。所有的加密密钥都由该系统进行统一分配和管理,而用于数据加密的密钥由一个主密钥(master
key)进行加密。比较流行的KMS有https://cloud.google.com/kms/ 和 https://aws.amazon.com/kms/,当然你可以选择自己实现。
保护通信信道
存储数据的保护使用的是对称算法,与之相对应的非对称算法则主要用于保护通信信道。
const crypto = require('crypto');
// 加密方法
exports.encrypt = (data, key) => {
// 公钥加密
return crypto.publicEncrypt(key, Buffer.from(data));
};
// 解密方法
exports.decrypt = (encrypted, key) => {
// 私钥解密
return crypto.privateDecrypt(key, encrypted);
};
在数据传输过程中,我们还会使用**Diffie-Hellman**算法进行交换密钥:
const crypto = require('crypto');
const sally = crypto.createDiffieHellman(2048);
const sallKeys = sally.generateKeys();
const bob = crypto.createDiffieHellman(sally.getPrime(), sally.getGenerator());
const bobKey = bob.generateKeys();
const sallySecret = sally.computeSecret(bobKey);
const bobSecret = bob.computeSecret(sallyKeys);
console.log(sallySecret.toString('hex'));
console.log(bobSecret.toString('hex'));
接下来介绍一下HMAC算法的使用,这个算法主要用于身份认证和生成消息摘要:
const hmac = crypto.createHmac('sha256', 'a secret');
hmac.update('some data');
console.log(hmac.digest('hex'));
双因子认证
双因子认证(Two-factor authentication,也叫2FA)是一种组合使用两种不同的验证机制来确认用户身份的机制。主要是通过手机等设备来生成token,具体到代码实现层面,可以在服务端和客户端之间生成一个临时的代码序列来校验。
这里推荐使用speakeasy这个npm库来实现2FA:
const express = require('express');
const app = express();
const speakeasy = require('speakeasy');
const qrcode = require('qrcode');
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded( { extended: true } ));
var router = express.Router();
var user = {
two_factor_temp_secret: null,
two_factor_secret: null,
two_factor_enabled: false
};
router.get('/2fa', function(req, res){
// 生成私钥
var secret = speakeasy.generateSecret();
user.two_factor_temp_secret = secret.base32;
qrcode.toDataURL(secret.otpauth_url, function(err, data_url){
res.send('<img src="' + data_url + '">');
});
});
// 认证页面
router.get('/authenticate', function(req, res){
res.send('<form action="/app/verify" method="post">Enter Token: <input type="text" name="token"><br><input type="submit" value="submit">');
});
//校验用户输入
router.post('/verify', function(req, res){
var userToken = req.body.token;
var base32secret = user.two_factor_temp_secret;
var verified = speakeasy.totp.verify({
secret: base32secret,
encoding: 'base32',
token: userToken
});
if(verified){
user.two_factor_secret = user.two_factor_temp_secret;
user.two_factor_enabled = true;
console.log('Successfully verified');
res.send('<p>Your token has been verified!</p>');
} else {
console.log('verification failed');
res.send('<p>verification failed</p>');
}
});
app.use('/app', router);
app.listen(3000);
console.log('App is running on port 3000');
总结
这篇文章主要介绍了如何在Node.js中使用crypto模块来保护密码、存储数据和通信信道。最后还介绍了如何使用speakeasy模块来实现双因子验证机制,希望这篇文章对大家有所帮助。