本文主要內(nèi)容:探討密碼加密和安全性殿较。
靜態(tài)數(shù)據(jù)&動(dòng)態(tài)數(shù)據(jù)
靜態(tài)數(shù)據(jù):不活動(dòng)的(或靜止的)數(shù)字?jǐn)?shù)據(jù),存儲(chǔ)在服務(wù)器中桩蓉,例如用于存儲(chǔ)密碼淋纲、個(gè)人資料、或其他應(yīng)用所需數(shù)據(jù)的數(shù)據(jù)庫(kù)院究。
動(dòng)態(tài)數(shù)據(jù):傳輸中的數(shù)據(jù)帚戳,在應(yīng)用和數(shù)據(jù)庫(kù)之間來(lái)回發(fā)送,或者在網(wǎng)站和 API 或外部數(shù)據(jù)源之間來(lái)回傳送儡首。
靜態(tài)數(shù)據(jù)
- 數(shù)據(jù)庫(kù)加密是絕對(duì)有必要的片任,盡管 99% 的團(tuán)體沒(méi)有這么做。
- 加密數(shù)據(jù)庫(kù)應(yīng)該使用強(qiáng)加密蔬胯、受 NIST 認(rèn)可的算法对供,如:SHA-256、AES氛濒、RSA产场。
- 遵循標(biāo)準(zhǔn)做法:1. 分開(kāi)訪問(wèn)控制(用戶登錄)和數(shù)據(jù)加密;2. 定期更新加密數(shù)據(jù)庫(kù)的密鑰舞竿;3. 分開(kāi)存儲(chǔ)加密密鑰和數(shù)據(jù)京景。
- 對(duì)全球覆蓋的應(yīng)用來(lái)說(shuō),可以使用數(shù)據(jù)聯(lián)合避免惡意訪問(wèn)數(shù)據(jù)存儲(chǔ)器骗奖,在需要個(gè)人信息的不同區(qū)域維護(hù)不同的數(shù)據(jù)庫(kù)系統(tǒng)确徙。
- 根據(jù)運(yùn)行應(yīng)用、網(wǎng)站或服務(wù)的需要执桌,應(yīng)該盡量少存儲(chǔ)敏感的用戶數(shù)據(jù)鄙皇。
- 敏感的財(cái)務(wù)信息(例如信用卡數(shù)據(jù))可以交由他方(支付提供商)處理。
動(dòng)態(tài)數(shù)據(jù)
動(dòng)態(tài)數(shù)據(jù)的使用場(chǎng)景:
- 用戶填寫(xiě)的注冊(cè)信息仰挣,用于訪問(wèn)賬戶和認(rèn)證身份伴逸。
- 把個(gè)人檔案信息傳輸給API服務(wù),或從中獲取膘壶。
- 應(yīng)用或網(wǎng)站收集的其他數(shù)據(jù)错蝴,傳給數(shù)據(jù)庫(kù)存儲(chǔ)起來(lái)。
密碼攻擊媒介
- 釣魚(yú)颓芭;
- 社會(huì)工程顷锰;
- 暴力攻擊;(抵抗方式:密鑰延伸技術(shù))
- 登錄失敗后顯示驗(yàn)證碼(Google reCAPTCHA畜伐、易盾 - 智能無(wú)感知驗(yàn)證碼)馍惹,增加登錄難度躺率。
- 添加 2FA (雙因素身份認(rèn)證)機(jī)制玛界。
- 字典攻擊万矾;(抵抗方式:加鹽)
- 彩虹表;(抵抗方式:加鹽)
- 惡意軟件慎框;(抵抗方式:短信驗(yàn)證良狈、n因素認(rèn)證)
- 離線破解;
加鹽
鹽值是一種隨機(jī)數(shù)據(jù)笨枯,計(jì)算密碼的哈希值時(shí)用于加強(qiáng)數(shù)據(jù)薪丁,抵御多種攻擊媒介尤其是字典攻擊和彩虹表。這個(gè)隨機(jī)數(shù)據(jù)(特別長(zhǎng))能確保生成的哈希值是唯一的馅精,即使多個(gè)用戶使用相同的密碼(確實(shí)有這種情況)严嗜,添加唯一的鹽值后能確保得到的哈希值仍是唯一的。就因?yàn)榈玫降墓V凳俏ㄒ坏闹薷遥覀儾诺靡悦馐懿屎绫砗妥值涔舻奈:Α?/p>
鹽值的特點(diǎn):鹽值需要足夠長(zhǎng)漫玄、不可預(yù)測(cè)、高度隨機(jī)压彭、需要使用安全的偽隨機(jī)函數(shù)生成睦优,另外,還要避免使用全局的鹽值壮不。
生成隨機(jī)鹽值
使用 Node 原生支持的的 crypto 庫(kù)生成隨機(jī)鹽值汗盘。
const crypto = require('crypto');
// 1.異步方法生成鹽值
// crypto.randomBytes() 生成 16 位強(qiáng)加密的偽隨機(jī)數(shù)
crypto.randomBytes(16, (err, buf) => {
if (err) throw err;
console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`);
console.log(`${buf.length} bytes of random data: ${buf.toString('base64')}`);
});
// 2.同步方法生成鹽值
const buf = crypto.randomBytes(256);
重用鹽值
用戶注冊(cè)賬戶或修改密碼時(shí)應(yīng)該生成并存儲(chǔ)新的鹽值和哈希值。
鹽值的長(zhǎng)度
- 根據(jù)經(jīng)驗(yàn)询一,鹽值的長(zhǎng)度應(yīng)該與哈希函數(shù)的輸出長(zhǎng)度一致隐孽。
- PBKDF2 標(biāo)準(zhǔn)建議至少應(yīng)該使用 64 位(8字節(jié))長(zhǎng)度的鹽值。通常健蕊,多數(shù)情況下使用的是2的7次方缓醋,即 128 位(16字節(jié))。
把鹽值存儲(chǔ)在哪绊诲?
鹽值可以和哈希值一起以明文形式存儲(chǔ)在數(shù)據(jù)庫(kù)中送粱。
salt 不能和哈希值存儲(chǔ)在一起,不然就和沒(méi)有 salt 一樣掂之,因?yàn)楣粽咄蠋?kù)后抗俄,構(gòu)建字典非常簡(jiǎn)單,因?yàn)樗軌蛑苯涌吹?salt世舰,所以一定要分開(kāi)存儲(chǔ)(比如在不同的機(jī)器上)动雹,這樣即使口令密文表被拖庫(kù)了,而 salt 表沒(méi)有被拖庫(kù)跟压,也是相對(duì)安全的胰蝠。
撒胡椒
- 胡椒是在計(jì)算哈希值時(shí)隨鹽值和密碼一起傳入的值。
- 使用胡椒的簡(jiǎn)單公式:hash ( salt + pepper + password ) = password hash
- 胡椒在代碼層計(jì)算,而不是使用存儲(chǔ)的值茸塞。
- 使用胡椒的原因:利用額外的字符和符號(hào)加強(qiáng)密碼的強(qiáng)度躲庄。
選擇正確的密碼哈希函數(shù)
bcrypt ??????
GitHub 源碼:node.bcrypt.js
特性:專(zhuān)為加密密碼設(shè)計(jì),底層基于 Blowfish 密碼法钾虐,有異步方法和同步方法噪窘、密碼哈希函數(shù)、校驗(yàn)密碼函數(shù)效扫、帶 promise 特性...
const bcrypt = require('bcrypt');
// 封裝 hash 函數(shù)
function bcrypt_encrypt(username, password) {
// 1. bcrypt 內(nèi)置了生成鹽值的方法:bcrypt.genSalt()
bcrypt.genSalt(10, (err, salt) => {
if (err) throw err;
// 2. 生成哈希值:hash()
bcrypt.hash(password, salt, (err, key) => {
if (err) throw err;
// 3. 把用戶名倔监、密碼哈希值和鹽值存入數(shù)據(jù)庫(kù)
});
});
}
// 調(diào)用示例
bcrypt_encrypt('zhangsan', '123456');
// 對(duì)比哈希值,驗(yàn)證密碼:
bcrypt.compare(password, hash, (err, same) => {
// 返回 true 或 false
});
PBKDF2
- 1Password菌仁、LastPass 等密碼管理系統(tǒng)采用的算法浩习;
- Node.js 中的 crypt 模塊原生支持的標(biāo)準(zhǔn)算法;
- 通過(guò)該算法可以實(shí)現(xiàn)基于口令的加密(PBE)济丘,即基于口令生成密鑰瘦锹。
加密示例:
// PBKDF2 算法
function pbkdf2_encrypt(username, password) {
// 1. crypto.randomBytes()方法生成 32 字節(jié)的隨機(jī)鹽值
crypto.randomBytes(32, (err, salt) => {
if (err) throw err;
// 2. 參數(shù)列表:(密碼,鹽值闪盔,迭代次數(shù)弯院,輸出密鑰長(zhǎng)度,摘要算法)
crypto.pbkdf2(password, salt, 4096, 512, 'sha256', (err, key) => {
if (err) throw err;
// 3. 將用戶名泪掀、密碼哈希值和鹽值存入數(shù)據(jù)庫(kù)
// Salt 鹽值是明文保存的听绳,一般不和最終生成的密鑰保存在一起。
console.log(username, key.toString('hex'), salt.toString('hex'));
});
});
}
// 調(diào)用示例
pbkdf2_encrypt('zhangSan', '123456');
對(duì)比哈希值异赫,驗(yàn)證密碼:
const dbsalt = 'USER RECORD SALT FROM YOUR DATABASE';
const dbhash = 'USER RECORD KEY FROM YOUR DATABASE';
crypto.pbkdf2(password, dbsalt, 4096, 512, 'sha256', (err, comparsehash) => {
if (err) throw err;
// 比較
if (dbhash.toString('hex') === comparsehash.toString('hex')) {
// 密碼匹配
} else {
// 密碼不匹配
}
});
scrypt
GitHub 源碼:node-scrypt
scrypt 的優(yōu)勢(shì)和實(shí)現(xiàn)如下:
- 做了特殊設(shè)計(jì)椅挣,是硬件和內(nèi)存密集型算法,攻擊者要實(shí)施大型攻擊塔拳,想要破解需要耗費(fèi)異常多的硬件和內(nèi)存鼠证。
- 是加密數(shù)字貨幣萊特幣和狗狗幣背后采用的算法。
'use strict';
const scrypt = require('scrypt');
const crypto = require('crypto');
function scrypt_encrypt(username, password) {
// 1. 使用 crypto 的 crypto.randomBytes(...) 方法生成鹽值靠抑。
crypto.randomBytes(32, (err, salt) => {
if (err) throw err;
// 2. scrypt.hash(...) 生成 64 位的哈希值
// - N: scrypt 最多使用多長(zhǎng)時(shí)間(秒數(shù))計(jì)算密鑰(偶數(shù))量九。
// - r:計(jì)算密鑰時(shí)最多使用多少字節(jié) RAM(整數(shù))。默認(rèn)為0颂碧。
// - p:計(jì)算密鑰時(shí)所用 RAM 占可用值的比例(0-1荠列,換算成百分比)默認(rèn)為0.5。
scrypt.hash(password, {"N":16384,"r":8,"p":1}, 64, salt, (err, key) => {
if (err) throw err;
// 3. 把用戶名载城、密碼哈希值和鹽值存入數(shù)據(jù)庫(kù)
console.log(`key is ${key}`);
});
});
}
密鑰延伸
bcrypt肌似、scrypt 和 PBKDF2 行之有效涉及到的底層概念——密鑰延伸(Key Derivation Function,KDF)诉瓦。
密鑰延伸:把弱密碼變成特別復(fù)雜的長(zhǎng)密碼川队,致使暴力攻擊等攻擊媒介不再可行力细。
對(duì)加密哈希函數(shù)來(lái)說(shuō),密鑰延伸體現(xiàn)在不斷循環(huán)應(yīng)用哈希函數(shù)(哈希函數(shù)迭代)固额,直到得到所需長(zhǎng)度和復(fù)雜度的哈希值為止眠蚂。
重新計(jì)算哈希值
有時(shí)可能需要為用戶生成新的密碼哈希值。比如說(shuō):
- 根據(jù)摩爾定律对雪,硬件更新了河狐,要修改加密算法使用的權(quán)重/工作因子米绕。
- 算法變了瑟捣,或者有更好的算法出現(xiàn),目前使用的算法不安全栅干。
- 覺(jué)得現(xiàn)有哈希值不再安全迈套。
遇到這些情況,符合常規(guī)的做法是為用戶生成新哈希值碱鳞,存儲(chǔ)在系統(tǒng)中桑李。用戶提供用戶名和密碼請(qǐng)求登錄時(shí),正常比較根據(jù)輸入值計(jì)算的哈希值和存儲(chǔ)的哈希值窿给,而不是拒絕登錄贵白。隨后,為用戶生成新的哈希值崩泡,替換用戶記錄中的舊值禁荒。