node.js的crypto在0.8版本並沒有改版多少,這個模塊的主要功能是加密解密。 node利用 OpenSSL庫來實現它的加密技術,這是因為OpenSSL已經是一個廣泛被采用的加密算法。它包括了類似MD5 or SHA-1 算法,這些算法你可以利用在你的應用中。
1、我們先來看hash算法: 我們可以通過 crypto.createHash() 來創建一個Hash實例。 我們可以利用以下算法來創建hash實例
-
md5
-
sha1
-
sha256
-
sha512
-
ripemd160 MD5是最常用的,但是他有一定的碰撞的問題,你可以使用更新的sha1算法。 我們看hash的生成方法,代碼如下:
var md5 = crypto.createHash(‘md5’); md5.update(‘foo’); {} md5.digest(); ‘??\u0018?L??\í?eO??¤?’ md5.digest(‘hex’); Error: Not initialized at [object Context]:1:5 at Interface.<anonymous> (repl.js:147:22) at Interface.emit (events.js:42:17) at Interface._onLine (readline.js:132:10) at Interface._line (readline.js:387:8) at Interface._ttyWrite (readline.js:564:14) at ReadStream.<anonymous> (readline.js:52:12) at ReadStream.emit (events.js:59:20) at ReadStream._emitKey (tty_posix.js:280:10) at ReadStream.onData (tty_posix.js:43:12) var md5 = crypto.createHash(‘md5’); md5.update(‘foo’); {} md5.digest(‘hex’); ‘acbd18db4cc2f85cedef654fccc4a4d8’
這里我們生成了一個md5的hash實例,然后直接運行 md5.digest(); 出現了亂碼,因為它默認返回的是2進制的數據,然后我們接着 md5.digest(‘hex’); 期望以16進制的形式打印md5值,但是拋出異常了。 也就是說一旦md5.digest();這個方法被調用了,hash 對象就被清空了是不能被重用的。當然我們可以利用base64格式打印出md5字符串。
對於hash.update()方法是有記憶功能的,我們看如下代碼:
> var sha1 = crypto.createHash('sha1'); > sha1.update('foo'); {} > sha1.update('bar'); {} > sha1.digest('hex'); '8843d7f92416211de9ebb963ff4ce28125932878' > var sha1 = crypto.createHash('sha1'); > sha1.update('foobar'); {} > sha1.digest('hex'); '8843d7f92416211de9ebb963ff4ce28125932878' >
這2次sha1加密結果是一樣的,也就是說hash.update()方法就是將字符串相加,然后在hash.digest()將字符串加密返回
2、HMAC HMAC全名是 keyed-Hash Message Authentication Code,中文直譯就是密鑰相關的哈希運算消息認證碼,HMAC運算利用哈希算法,以一個密鑰和一個消息為輸入,生成一個加密串作為輸出。HMAC可以有效防止一些類似md5的彩虹表等攻擊,比如一些常見的密碼直接MD5存入數據庫的,可能被反向破解。 crypto.createHmac(algorithm, key) 這個方法返回和createHash一樣,返回一個HMAC的實例,有update和digest方法。我們來看下實際的應用:
> var crypto = require('crypto'); > var fs = require('fs'); > > var pem = fs.readFileSync('key.pem'); > var key = pem.toString('ascii'); > > var hmac = crypto.createHmac('sha1', key); > > hmac.update('foo'); {} > hmac.digest('hex'); '7b058f2f33ca28da3ff3c6506c978825718c7d42' >
我們先通過 fs.readFileSync 方法讀取了key.pem密鑰,然后將它轉為ascii碼,最后通過 createHmac('sha1’, key) 方法獲得HMAC實例,然后執行update和digest,生成一串密鑰字符串。 由於key的不同,所以同樣的字符串’foo’經過hmac加密后生成的16進制字符串也是不同的,從而更加保障了數據的安全性。
關於key.pem是什么,我們可以利用opensll命令來創建一個key.pem,簡單命令如下:
[root[@localhost](/user/localhost) test]# openssl genrsa -out server.pem 1024 Generating RSA private key, 1024 bit long modulus ...............................++++++ ....................++++++ e is 65537 (0x10001) [root[@localhost](/user/localhost) test]# vi server.pem -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQCoWUIOtZAA4EB/bBpKBiHzDRdPCsSqTkR0Bva5kUUjVQ/2y6mi eZl244GGTwfiFtbHdu0eum4MkIes6IL/kJ8IDeyg4za26fEcO2mfty6BQbSo2SRr ZdIGVCZ2atvuc2ObzgVpJ+p1GGoFeNS1HOhw0Hq2GoeKt2rMdXceklXaLQIDAQAB AoGASC/t+Wy1UQrX3Uu3giJbEGN+qTAO4JArHi2WQkAei2YAMG1oUfkeazJm6fad hL8SXfmK9/AtHEolQ2l4MqoK5rD5kvoLNOWs+sIOd1Zi9jLow8F8x+ctphn5FdZz J444Mk3M3ua5ZS1YCy12pEbdO6urNMgUpqYcEla0BHIJ8MECQQDaxG9UyId5CgOH PCXy2oT3dSAa3XJeQ7fC5Tl4Tb4lKezho0Lk/g369cU3O6LWM9g65QQk/Vewtbpe fKoOtQcFAkEAxQAciOPbASojxZDiyy0CTbgj4PGaPBFwJis/wetAyBQcOKOM7l1h wHg8fUSXNYLHi6paDqtjaOvVfDOnRdMfCQJBAJg/4WNV88JvD6mMWLS9E5DMeL15 pGCqLDd9JBPvtwdSOEOIRcPsc3pWlRwtatQ8XJ4QSGQd1Gts7flYNVnq7qkCQQC0 3txUBrfNwu3i95pyppll1/oBDnHoUq5gLPc1yRPKX0Rl8Ct1soEMYJhQ/wfBlpg1 MCvNKih0bYqtpRMgNa1ZAkAT5kLpwW5Rb0OxVkHLwtOvixgKABPeGg8JuQS/POHS lEWvqaaUKdcVcje5YgMg6hDjWHTFJRQ1UEUubYTLFU/D -----END RSA PRIVATE KEY-----
這樣我們就生成了一個密鑰,具體openssl命令可以參考相關文案,openssl的命令很多,多的如天上的繁星!這里給大家簡單介紹一下ssl的知識。 其實整個ssl知識很多,我查閱了一些資料,目前 SSL(SecureSocketLayer)是netscape公司提出的主要用於web的安全通信標准,.TLS(TransportLayerSecurity)是IETF的TLS工作組在SSL3.0基礎之上提出的安全通信標准,SSL/TLS提供的安全機制可以保證應用層數據在互聯網絡傳輸不被監聽,偽造和竄改。
在介紹 SSL/TLS 知識的之前,我們有必要了解下加密算法的知識: 1、什么是加密算法 加密算法很容易理解,就是把明文變成人家看不懂的東西,然后送給自己想要的送到的地方,接收方用配套的解密算法又把密文解開成明文,這樣就不怕密文給人家截獲而泄密。 2、加密算法的種類 大致分為2類,一種是基於key的,一種不是基於key的。 不基於key的算法就是消息雙方都通過一定的加密和解密算法來進行通信,這種算法缺點很明顯如果加密算法被破解了就泄露了。 3、基於key的加密算法 key是一個什么東西呢?隨便你,可以是一個隨機產生的數字,或者一個單詞,啥都行,只要你用的算法認為你選來做key的東西合法就行。所以基於key的加密算法又分為2類:對稱加密和不對稱加密。對稱加密算法的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同樣的KEY來解密就可以得到明文。 4、不對稱加密算法 不對稱加密指雙方用不同的KEY加密和解密明文,通信雙方都要有自己的公共密鑰和私有密鑰。舉個例子比較容易理解,我們們假設通信雙方分別是A,B. A,擁有KEY_A1,KEY_A2,其中KEY_A1是A的私有密鑰,KEY_A2是A的公共密鑰。 B,擁有KEY_B1,KEY_B2,其中KEY_B1是B的私有密鑰,KEY_B2是B的公共密鑰。公共密鑰和私有密鑰的特點是,經過其中任何一把加密過的明文,只能用另外一把才能夠解開。也就是說經過KEY_A1加密過的明文,只有KEY_A2才能夠解密,反之亦然。 5、不對稱加密算法通信過程: A-------->;KEY_A2------------>B A<--------KEY_B2<------------A 這個過程叫做公共密鑰交換,老外管這叫keyexchange。之后A和B就分別用對方的公共密鑰解密,用自己的私有密鑰加密。 一般公共密鑰是要發布出去的,這就是SSL使用的驗證機制(注意不是數據傳輸機制)。常用的不對稱加密一般有RSA,DSA,DH等。我們一般使用RSA。
關於SSL: 一般情況下的網絡協議應用中,數據在機器中經過簡單的由上到下的幾次包裝,就進入網絡,如果這些包被截獲的話,那么可以很容易的根據網絡協議得到里面的數據.由網絡監聽工具可以很容易的做到這一點。
SSL就是為了加密這些數據而產生的協議,可以這么理解,它是位與應用層和 TCP/IP之間的一層,數據經過它流出的時候被加密,再往TCP/IP送,而數據從TCP/IP流入之后先進入它這一層被解密,同時它也能夠驗證網絡連接兩端的身份(根據我們之前學習的不對稱加密算法只是可知)。
SSL協議包含2個子協議,一個是包協議,一個是握手協議。包協議位於握手協議更下一層,我們暫時對包協議的內容沒有興趣。SSL握手過程說簡單點就是:通信雙方通過不對稱加密算法來協商好一個對稱加密算法以及使用的key,然后用這個算法加密以后所有的數據完成應用層協議的數據交換。
SSL通信流程: 握手一般都是由client發起的,SSL也不例外。 1、client送給server它自己本身使用的ssl的version(ssl一共有三個version),加密算法的一些配置,和一些隨機產生的數據,以及其他在SSL協議中需要用到的信息。 2、server送給client它自己的SSL的version,加密算法的配置,隨機產生的數據,還會用自己的私有密鑰加密SERVER-HELLO信息。Server還同時把自己的證書文件給送過去。同時有個可選的項目,就是server可以要求需要客戶的certificate。 3、client就用server送過來的certificate來驗證server的身份。如果server身份驗證沒通過,本次通信結束。通過證書驗證之后,得到server的公共密鑰,解開server送來的被其用私有密鑰加密過的SERVER-HELLO信息,看看對頭與否。如果不對,說明對方只有該server的公共密鑰而沒有私有密鑰,必是假的。通信告吹。 4、client使用到目前為止所有產生了的隨機數據(sharedsecret),client產生本次握手中的premastersecret(這個步驟是有可能有server的參與的,由他們使用的加密算法決定),並且把這個用server的公共密鑰加密,送回給server.如果server要求需要驗證client,那么client也需要自己把自己的證書送過去,同時送一些自己簽過名的數據過去。 RSA就是我們上一章說過的一種不對稱加密算法。首先server把自己的RSA公共密鑰送給client,client於是用這個key加密一個隨機產生的值(這個隨機產生的值就是sharedsecret),再把結果送給server. 5、Server驗證完client的身份之后,然后用自己的私有密鑰解密得到premastersecret然后雙方利用這個premastersecret來共同協商,得到mastersecret. 6、雙方用master一起產生真正的sessionkey,着就是他們在剩下的過程中的對稱加密的key了。這個key還可以用來驗證數據完整性。雙方再交換結束信息。握手結束。
回過頭來我們看openssl,openssl就是實現ssl的一個軟件,下面我就討論剛才的命令,生成一個私有密鑰: openssl genrsa -des3 -out server.key 1024 genras表示生成RSA私有密鑰文件,-des3表示用DES3加密該文件,1024是我們的key的長度。一般用512就可以了,784可用於商業行為了,1024可以用於軍事用途了。生成server.key的時候會要你輸入一個密碼,這個密鑰用來保護你的server.key文件,這樣即使人家偷走你的server.key文件,也打不開,拿不到你的私有密鑰。
Public Key Cryptography 公開密鑰加密包括4個類,Cipher, Decipher,Sign, and Verify,即加密,解密,簽名,驗證。 我們先看Cipher, Decipher這個加密和解密,這里是使用對稱加密算法。 看如下代碼:
> var crypto = require('crypto'); > var fs = require('fs'); > > var pem = fs.readFileSync('key.pem'); > var key = pem.toString('ascii'); > > var cipher = crypto.createCipher('blowfish', key); > > cipher.update(new Buffer(4), 'binary', 'hex'); '' > cipher.update(new Buffer(4), 'binary', 'hex'); 'ff57e5f742689c85' > cipher.update(new Buffer(4), 'binary', 'hex'); '' > cipher.final('hex') '96576b47fe130547'
我們看下代碼,我們讀取之前生成的key,然后利用 blowfish 加密算法生成 cipher 實例,接着update內容到cipher實例,最后通過cipher.final()方法輸出加密串。 其中有幾個方法我們要看下api的解釋 crypto.createCipher(algorithm, password) crypto.createCipheriv(algorithm, key, iv) 上面這2個方法都返回cipher實例,第一個參數 algorithm 表示用何種加密算法,可以利用 openssl list-cipher-algorithms 命令來查看你的系統支持哪些加密算法。password和key, iv表示密鑰,即利用何種密鑰加密,password是用來派生key和iv的,key的話是算法原生的key,iv表示初始化向量。
[root[@localhost](/user/localhost) ~]# openssl list-cipher-algorithms openssl:Error: 'list-cipher-algorithms' is an invalid command. Standard commands asn1parse ca ciphers crl crl2pkcs7 dgst dh dhparam dsa dsaparam enc engine errstr gendh gendsa genrsa nseq ocsp passwd pkcs12 pkcs7 pkcs8 prime rand req rsa rsautl s_client s_server s_time sess_id smime speed spkac verify version x509 Message Digest commands (see the `dgst' command for more details) md2 md4 md5 rmd160 sha sha1 Cipher commands (see the `enc' command for more details) aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb aes-256-cbc aes-256-ecb base64 bf bf-cbc bf-cfb bf-ecb bf-ofb cast cast-cbc cast5-cbc cast5-cfb cast5-ecb cast5-ofb des des-cbc des-cfb des-ecb des-ede des-ede-cbc des-ede-cfb des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb des-ede3-ofb des-ofb des3 desx rc2 rc2-40-cbc rc2-64-cbc rc2-cbc rc2-cfb rc2-ecb rc2-ofb rc4 rc4-40
cipher.update(data, [input_encoding], [output_encoding]) 往cipher實例中添加數據,第一個參數是填充的數據,第二個參數表示傳入數據的格式,可以是’utf8’, ‘ascii’ 或 'binary’,默認是 'binary’。第三個參數是返回block的數據格式。 注意這里我們update了 new Buffer(4),表示通過隨機內存中的4byte字節的內容填充進去。為什么第一次update沒有block返回呢,因為4byte不夠生成一個block,所以這點我們要注意下。 最后我們通過final方法和之前digest方法一樣,生成加密過后的串。
最后還有一個api: decipher.setAutoPadding(auto_padding=true) 如果這些加密塊不是使用標准的填充塊的話,你可以把自動填充關閉。這么做是為了防止執行 decipher.final()的時候監察和去除標准填充塊,從而可能出錯,一般這個方法不會去用。必須在執行update之前執行它。
下面我們看2個完整的加密和解密的代碼示例,代碼很容易理解不解釋了。
var crypto = require('crypto'); var cipher = crypto.createCipher('aes-256-cbc','InmbuvP6Z8') var text = "123|123123123123123"; var crypted = cipher.update(text,'utf8','hex') crypted += cipher.final('hex') var decipher = crypto.createDecipher('aes-256-cbc','InmbuvP6Z8') var dec = decipher.update(crypted,'hex','utf8') dec += decipher.final('utf8')
第二段代碼
> var crypto = require('crypto'); > var fs = require('fs'); > > var pem = fs.readFileSync('key.pem'); > var key = pem.toString('ascii'); > > var plaintext = new Buffer('abcdefghijklmnopqrstuv'); > var encrypted = ""; > var cipher = crypto.createCipher('blowfish', key); > .. > encrypted += cipher.update(plaintext, 'binary', 'hex'); > encrypted += cipher.final('hex'); > > var decrypted = ""; > var decipher = crypto.createDecipher('blowfish', key); > decrypted += decipher.update(encrypted, 'hex', 'binary'); > decrypted += decipher.final('binary'); > > var output = new Buffer(decrypted); > > output <Buffer 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76> > plaintext <Buffer 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76> >
最后我們看下簽名和驗證 Class: Signer 和 Class: Verify 還記得我們之前說的不對稱加密算法么,這里我們就利用私鑰和公鑰來做個簡單的例子,我先通過openssl命令生成公鑰:
[root[@localhost](/user/localhost) test]# openssl req -key server.pem -new -x509 -out cert.pem You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [GB]: State or Province Name (full name) [Berkshire]: Locality Name (eg, city) [Newbury]: Organization Name (eg, company) [My Company Ltd]: Organizational Unit Name (eg, section) []: Common Name (eg, your name or your server's hostname) []: Email Address []: [root[@localhost](/user/localhost) test]# [root[@localhost](/user/localhost) test]# ls cert.pem len.js server.key server.pem [root[@localhost](/user/localhost) test]#
node.js代碼:
> var crypto = require('crypto'); > var fs = require('fs'); > > var privatePem = fs.readFileSync('server.pem'); > var publicPem = fs.readFileSync('cert.pem'); > var key = privatePem.toString(); > var pubkey = publicPem.toString(); > > var data = "abcdef" > > var sign = crypto.createSign('RSA-SHA256'); > sign.update(data); {} > var sig = sign.sign(key, 'hex'); > > var verify = crypto.createVerify('RSA-SHA256'); > verify.update(data); {} > verify.update(data); {} > verify.verify(pubkey, sig, 'hex'); 1
首先通過,crypto.createVerify(algorithm)和crypto.createSign(algorithm)方法生成實例,然后利用update方法更新數據,最后利用key(私鑰)生成簽名,同樣的驗證也是如此,最后通過 verify.verify(pubkey, sig, ‘hex’); 函數簽名。
另外幾個api 像 createDiffieHellman 等下次再研究吧
本文例子參考部分:node up and running 本文示例代碼我都跑通過,請放心參考
原文地址:http://snoopyxdy.blog.163.com/blog/static/601174402012730105523656/