Node.js應用中常用安全保護機制和加密算法實現 保護密碼 保護存儲數據 保護通信信道 雙因子認證 總結 參考資料

當你知道如何在應用中針對不同的使用場景使用不同的加密算法,你的應用纔是安全的。因此,這篇文章將會介紹平常工作中比較常用的幾種加密方式,並給出對應的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模塊來實現雙因子驗證機制,希望這篇文章對大家有所幫助。

——本文首發於個人公衆號,轉載請註明出處———


最後,歡迎大家關注我的公衆號,一起學習交流。

參考資料

Crypto In Node.js

Zero to Hashing in Under 10 Minutes: Argon2 in Nodejs – Hunter2: AppSec Training

Symmetric Encryption in JavaScript - Ekene Izukanne - Medium

深入理解雙因子認證

Practical Cryptography in Node.js

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章