四種場景
- 密碼加密
- 資料加密
- 對稱簽名
- 授權認證
密碼加密
選擇要素
安全性、不可逆、防彩虹表
演算法的安全性通常依賴於演算法速度足夠慢,通常為 300ms ~1000ms,視場景
防彩虹表通常依賴於salt的隨機性和長度
主流推薦
scrypt、bcrypt、pbkdf2
流行但不再推薦
sha1、sha1+salt、sha1+salt+ 多次雜湊、sha256+salt
// 等效於 sha1+salt 方案,且 salt 隨機性長度都很弱
algo = 'sha1'
salt = sha1(Math.random() + '' + Math.random())
hsh = sha1(salt[0..5] + raw_password)
array = [algo, salt[0..5], hsh]
return array.join('$')
複製程式碼
sha1 演算法因為被攻破而不再推薦,目前推薦的是 sha256
salt 可以有效的抵禦彩虹表攻擊,能力取決於 salt 的隨機性和長度
簡單的多次雜湊不會帶來任何好處,通常會迴圈萬次或更多來保證演算法足夠慢,也就是 pbkdf2 的方案
bcrypt vs pbkdf2
pbkdf2 是目前 NIST 官方推薦的標準。它約等於雜湊函式 +salt+ 多次雜湊。nodejs 也源生支援了這個演算法。比對密碼需要用原引數再計算一遍,所以需要儲存 salt 和雜湊次數。下面有一個 nodejs 的簡單加密程式碼:
var crypto = require('crypto')
var salt = crypto.randomBytes(16).toString('base64')
// 雜湊演算法通常選擇 sha256
// 第三個引數是雜湊次數,選擇依據應該是保證演算法耗時在 300ms 以上
var hash = crypto.pbkdf2Sync('hello world', salt, 12000, 64, 'sha256').toString('hex')
複製程式碼
bcrypt 演算法是基於 1993 提出的 Blowfish 演算法優化而來。它是一種塊加密演算法。·也是目前大多數人認為最安全的密碼加密演算法。它的主要優勢在於引入了一個代價因子,用來控制加密速度。演算法 salt 會包含代價因子資訊並被混入密文,比對密碼不需要除密文外其他資訊,所以使用者可以隨機更新代價因子調整加密速度,而不用做任何相容處理。
nodejs 實現 github.com/dcodeIO/bcr…
資料加密
選擇要素
加密效能 、防篡改(通常取決於使用的簽名演算法)
主流推薦
chacha20poly1305, aes-gcm
流行但不再推薦
aes-cbc + sha1 簽名(微信、釘釘等回撥推送使用的模式)
chacha20poly1305 vs aes-gcm
chacha20-poly1305 = ChaCha20(流密碼) + Poly1305(訊息認證碼)
chacha20poly1305 是流密碼,以位元組為單位進行加密,安全性的關鍵體現在金鑰流生成的過程,即所依賴的偽隨機數生成器(PRNG)的強度。
nodejs 實現 github.com/calvinmetca…
const chacha = require('chacha')
const crypto = require('crypto')
let key = crypto.randomBytes(32)
let nonce = crypto.randomBytes(12)
let cipher = chacha.createCipher(key, nonce)
let encryptedData = Buffer.concat([
cipher.update(data, 'utf8'),
cipher.final()
])
let authTag = cipher.getAuthTag()
Buffer.concat([authTag, encryptedData]).toString('base64')
複製程式碼
aes-gcm = aes-ctr(計數器模式) + gmac(Galois訊息認證碼)
aes 是塊密碼,以塊資料為單位進行加密。nodejs 程式碼示例:
const crypto = require('crypto')
let iv = crypto.randomBytes(12)
let secret = crypto.pbkdf2Sync('hello123', crypto.randomBytes(16), 100, 32, 'sha256')
let cipher = crypto.createCipheriv('aes-256-gcm', secret, iv)
let encryptedData = Buffer.concat([
cipher.update(data, 'utf8'),
cipher.final()
])
let authTag = cipher.getAuthTag()
return Buffer.concat([iv, authTag, encryptedData]).toString('base64')
複製程式碼
理論上 chacha20poly1305 優於 aes-gcm,但 aes-gcm 更成熟,使用更廣泛。
在支援 aes-gcm 的 cpu 上,aes-gcm 效能優於 chacha20poly1305
ctr vs cbc
aes-cbc 模式
aes 的一種迴圈模式,前一個分組的密文和當前分組的明文異或操作後再加密,這樣來增強破解難度, 因此不能平行計算。
aes-ctr 計數器模式
在計數器模式下,我們不再對密文進行加密,而是對一個逐次累加的計數器進行加密,用加密後的位元序列與明文分組進行 XOR 得到密文,因此可以平行計算。
ctr 模式支援並行運算,效能更好。cbc 模式出現的更早,支援和運用更廣泛。
對稱簽名
目前推薦的演算法
sha256
雜湊函式需要滿足的條件,如果不滿足,一般就認為不安全了
- 確定性:雜湊函式的演算法是確定性演算法,演算法執行過程不引入任何隨機量。這意味著相同訊息的雜湊結果一定相同。
- 高效性:給定任意一個訊息 m,可以快速計算 Hash(m) 。
- 目標抗碰撞性:給定任意一個訊息 m0,很難找到另一個訊息 m1,使得 hash(m0) = hash(m1).
- 廣義抗碰撞性:很難找到兩個訊息 m0≠m1 ,使得 hash(m0) = hash(m1)。
sha1(160位,理論破解需2^80次運算) -> sha2 (256位,理論破解需2^128次運算)-> sha3
sha1的破解花費了 2^69 次運算,在 google 的雲伺服器上花費了幾天時間,參考連結 www.zhihu.com/question/56…
2010 年密碼專家預測的 sha2 系列的安全期是 5-10 年
nodejs sha256 實現
function sha256 (str) {
return crypto.createHash('sha256').update(str).digest('hex')
}
複製程式碼
有一個誤區是 sha256 數字比 sha1 大很多,效能會慢很多。在安全要求不高的場景,選擇sha1.
但實際上兩者效能差距不大。sha1 稍快一些。
授權認證
推薦
jsonwebtoken
常見方案:對稱加密(sha1, sha256, aes, des3)
const serializer = require('serializer')
function sign (_userId, clientKey, extra) {
let handler = serializer.createSecureSerializer(oauthCryptKey, oauthSignKey)
return handler.stringify([_userId, clientKey, Date.now(), extra])
}
// serializer 中加密流程的對應原始碼
var CYPHER = 'aes256'
var cypher = crypto.createCipher(CYPHER, encrypt_key + nonce_crypt);
var data = JSON.stringify(obj);
var res = cypher.update(nonce_check, DATA_ENCODING, CODE_ENCODING);
res += cypher.update(data, DATA_ENCODING, CODE_ENCODING);
res += cypher.final(CODE_ENCODING);
var digest = signStr(data, validate_key + nonce_check);
return digest + nonce_crypt + res;
複製程式碼
jsonwebtoken 簡介
jwt 是一個輕量級的認證規範,常用來在使用者和伺服器之間傳遞安全可靠的資訊。
jwt 由三部分組成:header、payload、sign:
- header 主要記錄了使用的演算法等基本資訊,預設為 sha256,也支援使用非對稱簽名演算法
- payload 為使用者資訊和 jwt 定義的標準資訊(如過期時間、籤授方等),該部分為 -base64 編碼,一般因只包含核心資訊,避免 token 過長
- signature 主要用於防篡改,也可以傳入 algorithm:'none' 表示不簽名
nodejs 實現 github.com/auth0/node-…
需要注意的點
1.jwt 的 payload 是 base64 編碼的,也就是明文傳遞的,所以不應該用 jwt 傳遞敏感資訊。
2.jwt 對於同樣的輸入,預設是秒級唯一的,如果有更高的要求,可以在 payload 中傳入 iat
// 原始碼中iat欄位預設為當前時間的秒數,同一秒內輸出一致
var timestamp = payload.iat || Math.floor(Date.now() / 1000);
if (!options.noTimestamp) {
payload.iat = timestamp
} else {
delete payload.iat;
}
// 傳入iat
jwt.sign({hello: 'world', iat: Date.now()}, 'secret')
複製程式碼
歡迎大家關注我們的官方公眾號