nodejs中各種加密算法的實現(xiàn)詳解
前言
在大前端的趨勢下,前端er也要懂點數(shù)據(jù)加密相關(guān)的知識才行,加密算法的實現(xiàn)和原理我們可以不用深究,畢竟加密是一門高深的學(xué)科,但是基本的加密方式和編碼還是要掌握的,畢竟沒吃過豬肉,豬跑還是見過的嘛。
我對常見的幾種加密和簽名的算法做個歸納,同時附上 nodejs 的編碼實現(xiàn)。
加密算法
為了保證數(shù)據(jù)的安全性和防篡改,很多數(shù)據(jù)在傳輸中都進(jìn)行了加密。舉個場景的栗子,最近很多網(wǎng)站都升級到 https 協(xié)議, https 協(xié)議就是使用了非對稱加密和hash簽名,還有 github 使用的 ssh ,也是非對稱加密。還有大部分登錄時密碼采用的 MD5 加密等等。
加密可分為三大類,對稱加密和非對稱加密,還有摘要算法,我們一一展開。
對稱加密
引用百科的描述:
采用單鑰密碼系統(tǒng)的加密方法,同一個密鑰可以同時用作信息的加密和解密,這種加密方法稱為對稱加密,也稱為單密鑰加密。
對稱加密很好理解,就好比我把我家的鑰匙給你,你要來我家,直接用這把鑰匙開門就行。
對稱加密目前主流的有 AES 和 DES , AES 是新一代的標(biāo)準(zhǔn),速度快,安全級別更高。
AES
AES的加密模式有五種:CBC、ECB、CTR、OCF、CFB
- ECB:電子密本方式,需要一個密鑰即可,特點是簡單,利于并行計算。
- CBC:密文分組鏈接方式,除了需要一個密鑰之外,還需要一個向量,向量的作用也是用于數(shù)據(jù)的加密,所以這個的安全性要好于 ECB
- CTR、OCF、CFB:具體算法的實現(xiàn)方式不一樣,優(yōu)缺點也各不相同,而這幾個都同 CBC 一樣,都需要密鑰和向量。
AES 有三種長度 128位、192位、256位,這三種的區(qū)別,主要來自于密鑰的長度,16字節(jié)密鑰=128位,24字節(jié)密鑰=192位,32字節(jié)密鑰=256位。如下表格:
長度 | 密鑰長度 | 向量長度 |
---|---|---|
128位 | 16 | 16 |
192位 | 24 | 16 |
256位 | 32 | 16 |
DES
加密默認(rèn)與 AES 相同,也有五種模式,除了 ECB 只需要密鑰,其他模式需要密鑰和向量。
與 AES 不同的是, DES 的密鑰長度只有8字節(jié),向量也是8字節(jié)。
編碼實現(xiàn)
在 nodejs 中的實現(xiàn)
/** * @description * 對稱加密 * @param {*} data 加密數(shù)據(jù) * @param {*} algorithm 加密算法 * @param {*} key 密鑰 * @param {*} iv 向量 * @returns */ function cipherivEncrypt(data, algorithm, key, iv) { const cipheriv = crypto.createCipheriv(algorithm, key, iv) let encrypted = cipheriv.update(data, 'utf8', 'hex'); encrypted += cipheriv.final('hex'); return encrypted } /** * @description * 對稱解密 * @param {*} data 解密數(shù)據(jù) * @param {*} algorithm 解密算法 * @param {*} key 密鑰 * @param {*} iv 向量 * @returns */ function cipherivDecrypt(data, algorithm, key, iv) { const decipher = crypto.createDecipheriv(algorithm, key, iv); let decrypted = decipher.update(data, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted }
使用官方提供 crypto 庫來實現(xiàn)加解密,上面的代碼中加密后輸出的是 16 進(jìn)制的字符串,大家可以根據(jù)具體情況換成其他格式的數(shù)據(jù)。
調(diào)用方式如下
// AES對稱加解密 const str = 'xiaoliye'; const key = 'aaaaaaaaaaaaaaaaaaaaaaaa'; // 24 const iv = 'aaaaaaaaaaaaaaaaaaaaaaaa'; // 24 const cipherAesText = cipherivEncrypt(str, 'aes-192-cfb', key,iv) const resultText = cipherivDecrypt(cipherAesText, 'aes-192-cfb', key,iv) console.log(resultText === str) // true
// DES對稱加解密 const str = 'xiaoliye'; const key = 'aaaaaaaa'; //8 const iv = 'aaaaaaaa'; //8 const cipherAesText = cipherivEncrypt(str, 'des-cfb', key,iv) const resultText = cipherivDecrypt(cipherAesText, 'des-cfb', key,iv) console.log(resultText === str) / true
非對稱加密
非對稱加密,有兩把鑰匙,公鑰和私鑰,如下圖:
公鑰是可以公開對外,私鑰就是自個的,不可泄露。因為有兩個密鑰,非對稱加密這個名字就是這么由來的。
發(fā)送方用接收方公開對外的公鑰進(jìn)行加密,接收方收到數(shù)據(jù)后,用私鑰進(jìn)行解密,業(yè)務(wù)處理完后,用私鑰給需要回傳的數(shù)據(jù)加密,收到數(shù)據(jù)的一方在用公鑰解密。
這個過程就是非對稱加解密,簡單理解就是公鑰加密的數(shù)據(jù),用私鑰解密;私鑰加密的數(shù)據(jù),用公鑰解密。
非對稱加密與對稱加密相比,安全性要高很多。對于對稱加密,密鑰鑰匙被某一方不小心泄露了,那秘文就有可能被破解和篡改。而非對稱加密,公鑰隨意流通,只要頒發(fā)密鑰的一方好好把私鑰保管好,安全性是妥妥的。
編碼實現(xiàn)
我們來看 node 中的編碼實現(xiàn),還是使用官方提供的 crypto 庫
- 加解密函數(shù)接收的數(shù)據(jù)是 Buffer 類型,(關(guān)于 Buffer 的介紹,不了解的朋友可以看下這篇二進(jìn)制與Buffer),所以需要約定好接收和輸出的數(shù)據(jù)類型。
- 參數(shù) padding 是填充方式,有這么幾種 crypto.constants.RSA_NO_PADDING, crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING,因為沒有深入研究過,就不展開啦,不過有一點,加密和解密的填充方式必須是要一致的。
const constants = require('constants') const crypto = require('crypto') /** * @description * 公鑰加密數(shù)據(jù) * @param {*} data 待加密數(shù)據(jù) * @param {*} publicKey 公鑰 * @param {*} inputEncoding 加密數(shù)據(jù)類型 * @param {*} outputEncoding 輸出的數(shù)據(jù)類型 * @param {*} padding 填充方式 * @returns */ function publicEncrypt(data, publicKey, inputEncoding, outputEncoding, padding) { const encryptText = crypto.publicEncrypt({ key: publicKey, padding: padding || constants.RSA_PKCS1_PADDING }, Buffer.from(data, inputEncoding)); return encryptText.toString(outputEncoding); } /** * @description * 公鑰解密數(shù)據(jù) * @param {*} data 待解密數(shù)據(jù) * @param {*} publicKey 公鑰 * @param {*} inputEncoding 解密數(shù)據(jù)類型 * @param {*} outputEncoding 輸出的數(shù)據(jù)類型 * @param {*} padding 填充方式 * @returns */ function publicDecrypt(data, publicKey, inputEncoding, outputEncoding, padding) { let decryptText = ''; const decryptText = crypto.publicDecrypt({ key: publicKey, padding: padding || constants.RSA_PKCS1_PADDING }, Buffer.from(data, inputEncoding)); return decryptText.toString(outputEncoding); } /** * @description * 私鑰加密數(shù)據(jù) * @param {*} data 待加密數(shù)據(jù) * @param {*} privateKey 私鑰 * @param {*} inputEncoding 加密數(shù)據(jù)類型 * @param {*} outputEncoding 輸出的數(shù)據(jù)類型 * @param {*} padding 填充方式 * @returns */ function privateEncrypt(data, privateKey, inputEncoding, outputEncoding, padding) { const encryptText = crypto.privateEncrypt({ key: privateKey, padding: padding || constants.RSA_PKCS1_PADDING }, Buffer.from(data, inputEncoding)); return encryptText.toString(outputEncoding); } /** * @description * 私鑰解密數(shù)據(jù) * @param {*} data 待解密數(shù)據(jù) * @param {*} privateKey 私鑰 * @param {*} inputEncoding 解密數(shù)據(jù)類型 * @param {*} outputEncoding 輸出的數(shù)據(jù)類型 * @param {*} padding 填充方式 * @returns */ function privateDecrypt(data, privateKey, inputEncoding, outputEncoding, padding) { const decryptText = crypto.privateDecrypt({ key: privateKey, padding: padding || constants.RSA_PKCS1_PADDING }, Buffer.from(data, inputEncoding)); return decryptText.toString(outputEncoding); }
有四個函數(shù),分別是公鑰的加解密和私鑰的加解密,我們看下如何使用,示例中是我自己生成的密鑰對,大家可以自行替換
const rsaPublicKey = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCncWDMXEToSxtGQCmWY2ywl5CQ tb81PXYZch4v5M8MNUZPpcmf+VDXQbuWqqTqV/tY7rLviu/BAkFbX9NiFCapF5lP siVwSGWJQwq0S/++RCwB6yFVEzOKL25jANRBVNwmSOzojveCStYPcEs5Q829ld68 9TzluDDqUS69dTHGkQIDAQAB -----END PUBLIC KEY-----` const rsaPrivateKey = `-----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKdxYMxcROhLG0ZA KZZjbLCXkJC1vzU9dhlyHi/kzww1Rk+lyZ/5UNdBu5aqpOpX+1jusu+K78ECQVtf 02IUJqkXmU+yJXBIZYlDCrRL/75ELAHrIVUTM4ovbmMA1EFU3CZI7OiO94JK1g9w SzlDzb2V3rz1POW4MOpRLr11McaRAgMBAAECgYEAhNa8/cQh4sxbKgOTOr1MKFlG Fpgpxroo7I0Nh9+Vp1DIpD2Z1PF9ghijEyf0R/pe7LIKgWIPTWdVpIFEeSYVeH43 FLr3zwR9oXzwG7RQTSN4d/Xcvg+24ZxCrvDfn7qDIlXh0jOS0wCvna1or7xgPcOu XG8J3BNbBdUixM0lk0ECQQDR4SCelWn0BY21jsFobX+pGqKOsj+tuvU4Cz47Gmev qvq2suYXwLemkP7EqRu8iNso/IzvrdsuJDG76dzwC4D5AkEAzDz2cDrKOVmqYw7s luOQFHl1TzmY7Umpd9YbZ5iXn0eCjIn1/e1risRF5+IeSpB84OVltUzj4cVDCbFd 9S1wWQJAIeKcFp5+9cPzxi1fMpIDO3Uua6WBvHXj44GFMZuow+byBY9KsOkPfZgJ Wg0Hil/6KlrkEkpaic+ULAetASCKWQJAdMh/Gdlj/LsaxJ2qBvWEU1DIFU8X9Mbk ElPpQ6lrOXaIXZgdgt8ZWTW1y0vuijBoV6iUKcEXpOdI1+gFk8YxsQJBAJsGJClf E1mE6CZgegM82428g4osZznVXBO/QtrQsA78S1xo8bo4qwVm0jQBcto65gwlfeeB Xm7MiIvNVBqzTVs= -----END PRIVATE KEY----- ` const str = 'xiaoliye' const cipherText = publicEncrypt(str, rsaPublicKey, 'utf8', 'hex') // 公鑰加密 const decryptText = privateDecrypt(cipherText, rsaPrivateKey, 'hex', 'utf8') // 私鑰解密 console.log(str === decryptText) // true const cipherTextPrivate = privateEncrypt(str,rsaPrivateKey,'utf8', 'hex') // 私鑰加密 const decryptTextPublic = publicDecrypt(cipherTextPrivate,rsaPublicKey, 'hex', 'utf8') // 公鑰解密 console.log(str === decryptTextPublic) // true
密鑰生成方式
網(wǎng)上有很多工具可以一鍵生成配對的公鑰和私鑰,淘寶、微信都有提供相關(guān)工具,或者使用 OpenSSL 生成也可以。
摘要算法(HASH)
把任意長度的輸入,根據(jù)算法生成一串固定長度的偽隨機數(shù),這一算法就是摘要算法,它有這么幾個特點
- 不需要密鑰,加密出來的數(shù)據(jù)無法被解密,具有不可逆性。
- 生成的摘要長度是固定的,與輸入無關(guān)。
- 相同的輸入,使用相同的實現(xiàn),生成的摘要一定相同;不同的輸入,生成的摘要是大相徑庭的,即,不會發(fā)生碰撞。
根據(jù)這些特點,摘要算法通常用于生成簽名,用來驗證數(shù)據(jù)的完整性。
還有用戶密碼的存儲,如今密碼的存儲主流的方式,就是使用摘要算法生成唯一的標(biāo)識,為了保證安全性,通常在生成摘要后再加上一串隨機數(shù)(加鹽salt),在來hash一次。
目前主流的實現(xiàn)有 MD5 和 SHA-2 , MD5 生成的摘要是 32 字節(jié), sha256 生成的摘要是 64 字節(jié)。
編碼實現(xiàn)
仍然是使用官方提供的 crypto 庫
/** * @description * md5 * @param {*} data * @returns */ function md5(data){ const hash = crypto.createHash('md5'); return hash.update(data).digest('hex'); } /** * @description * sha256 * @param {*} data * @returns */ function sha256(data){ const hash = crypto.createHash('sha256'); return hash.update(data).digest('hex'); } console.log(md5('asdf')) // 912ec803b2ce49e4a541068d495ab570 console.log(sha256('asdf')) // f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b
小結(jié)
涉及加密的活一般是后臺開發(fā)干的,但前端靚仔懂點加密,會讓自己酷酷的~
小伙伴們還有遇到啥其他加密的方式,歡迎一起交流啊~
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
node puppeteer(headless chrome)實現(xiàn)網(wǎng)站登錄
這篇文章主要介紹了node puppeteer(headless chrome)實現(xiàn)網(wǎng)站登錄,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05Nodejs實現(xiàn)的操作MongoDB數(shù)據(jù)庫功能完整示例
這篇文章主要介紹了Nodejs實現(xiàn)的操作MongoDB數(shù)據(jù)庫功能,結(jié)合完整實例形式分析了nodejs針對MongoDB數(shù)據(jù)庫的連接及增刪改查基本操作技巧,需要的朋友可以參考下2019-02-02node.js中實現(xiàn)GET和POST請求的代碼示例
在很多場景中,我們的服務(wù)器都需要跟用戶的瀏覽器打交道,如發(fā)送驗證碼、登錄表單提交,請求服務(wù)器數(shù)據(jù)一般都使用GET請求,表單提交到服務(wù)器一般都使用POST請求,本文詳細(xì)介紹了在Node.js中如何處理GET和POST請求,需要的朋友可以參考下2024-12-12