当你知道如何在应用中针对不同的使用场景使用不同的加密算法,你的应用才是安全的。因此,这篇文章将会介绍平常工作中比较常用的几种加密方式,并给出对应的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 class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="' + 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模块来实现双因子验证机制,希望这篇文章对大家有所帮助。