原文地址:http://www.moye.me/2015/06/14/cryptography_rsa/
引子
對於加解密,我一直處於一種知其然不知其所以然的狀態,項目核心部分並不倚重加解密算法時,可以勉強對付過去,一旦需要頻繁應用諸如 AES/RSA等算法,這種狀態就頗令人捉急了。
是時候了解一下原理了,所以找來了這本圖解密碼技術 給自己補補課:
在該書深入淺出的指引下 ,補充了一些常識,在此進行一番梳理:
對稱加密算法(共享密鑰)
顧名思義,對稱加密就是用相同的密鑰進行加密和解密。說到對稱加密,異或加密又是一個不得不提的概念:
XOR(異或加密)
明文與密鑰進行一次 異或 (記做㊉ ) 運算將成為密文,密文再與密鑰進行一次異或運算將還原為明文:
例如,字符串“Wiki”(8位ASCII:01010111 01101001 01101011 01101001) 可以按如下的方式用密鑰11110011進行加密:
01010111 01101001 01101011 01101001 11110011 11110011 11110011 11110011 = 10100100 10011010 10011000 10011010 此種加密方法類似對稱加密,故解密的方式如下:
10100100 10011010 10011000 10011010 11110011 11110011 11110011 11110011 = 01010111 01101001 01101011 01101001
我們也可以用代碼驗證這一特性:
var key = 0b10110010; var number = 22; var encrypted = number ^ key; // ㊉ => 密文 164 console.log(encrypted ^ key); // ㊉ => 明文 22
光使用XOR,就能實現最基本的對稱加密,前提是選擇一個合適的密鑰。而其它對稱加密算法 如DES/AES 等,無不是在 XOR 基礎上的擴展。
AES 對稱加密
AES 即 高級加密標准(Advanced Encryption Standard),是取代前任標准(DES)成為新標准的一種對稱加密算法(DES被取代是因為其算法有缺陷,導致其能被短時間內暴力破解,所以DES被棄用,建議使用AES)。現行AES的實現算法為 Rijndael,是比利時科學家Joan Daemen 和 Vincent Rijmen 設計的分組密碼算法。
分組的意思是說,AES 算法的輸入是要分組的,分組長度可以在 128/196/256 比特中進行選擇(即一輪可加密這么多比特的明文生成同樣長度的密文,一次加密可能需要迭代多輪)。
模式
分組密碼算法只能加密固定長度的分組,但是我們需要加密的明文長度可能會超過分組密碼的分組長度,這時就需要對分組密碼算法進行迭代,以便將一段很長的明文全部加密。而迭代的方法就稱為分組密碼的模式。
模式有很多種類,分組密碼的主要模式有:
- ECB 模式:Electronic Codebook mode (電子密碼本模式)
- CBC 模式:Cipher Block Channing mode(密碼分組鏈接模式)
- CFB 模式:Cipher FeedBack mode (密文反饋模式)
- OFB 模式:Output FeedBack mode(輸出反饋模式)
- CTR 模式:CounTeR mode(計數器模式)
這幾種模式的運作流程這里不做贅述,只需知道:
- ECB過於簡單而不安全,已被棄用;
- CFB可被施以重放攻擊;
- OFB 和 CTR 都可被主動攻擊者反轉密文,而引起解密后明文中的相應比特也發生變化;CTR比之OFB,多出能支持並發計算的特性,此外CTR是流式密碼;
- CBC雖不支持並行計算,但是卻是這些模式中最為安全的
公鑰加密算法
公開密鑰加密,也稱為非對稱加密(asymmetric cryptography),一種密碼學算法類型,在這種密碼學方法中,需要一對密鑰,一個是私人密鑰,另一個則是公開密鑰。這兩個密鑰是數學相關,用某用戶密鑰加密后所得的信息,只能用該用戶的解密密鑰才能解密。如果知道了其中一個,並不能計算出另外一個。因此如果公開了一對密鑰中的一個,並不會危害到另外一個的秘密性質。稱公開的密鑰為公鑰;不公開的密鑰為私鑰。
公鑰加密解決了一個對稱加密的密鑰配送難題:如何安全的傳遞解密用的密鑰。方案是:不傳遞,加密者和解密者所持密鑰不一樣,特點如下:
- 密文發送者只需要加密密鑰(公鑰
- 密文接收者只需要解密密鑰(私鑰
- 解密密鑰不可以被竊聽者獲取
- 加密密鑰被竊聽者獲取也沒有安全問題
RSA 公鑰加密
RSA 是一種公鑰加密算法,它的名字是用三位開發者 R. Rivest、A. Shamir 和 L. Adleman 的姓氏首字母組成。RSA 可被用於公鑰密碼和數字簽名,算法於1983年在美國取得專利,目前該專利已過期(由於該算法在申請專利前就已經被發表了,在世界上大多數其它地區這個專利權不被承認)。
在RSA中,明文、密鑰和密文都是數字,公私鑰對是兩對數字:
- 公鑰是 (數E,數N)
- 私鑰是 (數D,數N)
加密就是 用明文數字的E次方求 mod N (取余)的結果,過程可以用下列公式來表達:
密文 = 明文 E mod N
對密文的數字的D次方求 mod N就可以得到明文,解密過程可以用如下公式來表達:
明文 = 密文 D mod N
生成密鑰對流程
(1) 求 N
隨機生成兩個很大的質數 p 和 q,那么 N = p * q
(2) 求 L
臨時量 L 僅被用於生成密鑰對的過程中,它是 p -1 和 q - 1 的最小公倍數(least common multiple, lcm),用lcm(X, Y) 來表示 “X和Y的最小公倍數” ,則L可用公式表示為:
L = lcm(p-1, q-1)
(3) 求E
E 和 L 之間存在如下關系:
1 < E < L
gcd(E, L) = 1 E 和 L 的最大公約數為1 (E 和 L 互質)
要找出滿足 gcd(E, L) = 1 的數,還是要使用偽隨機數生成器。通過偽隨機數生成器在 1 < E < L 的范圍內生成 E 的候選數,然后再判斷其是否滿足 gcd(E, L) = 1 這個條件。
(4) 求D
數D 是由數 E 計算得到的。D、E 和 L 之間必須具備如下關系:
1 < D < L
E * D mod L = 1
只要數 D 滿足上述條件,則通過 (數E,數N) 加密的密文,都可以通過 (數D,數N) 進行解密。
模擬實踐
用較小的數來實踐一把RSA的密鑰生成和加解密:
(1) 求N
選擇兩個質數,比如: p = 17 和 q = 19
N = 17 * 19 = 323
(2) 求 L
L = lcm (p-1, q-1) = lcm(16, 18) = 144
(3) 求 E
E 和 L 的最大公約數必須是1:
gcd(E, L) = 1
滿足條件的數 E有很多,100以內的質數有:
5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97
我們挑一個,比如 5 作為E,那么 公鑰對就是(E=5, N=323)
(4) 求 D
D 必須滿足:
E * D mod L = 1
在 E = 3 的情況下,D = 59 是滿足的,因為:
5 * 29 mod 144 = 1
所以 私鑰對就是(D=29, N=323)
公/私鑰對都有了,就可用於加解密了,假設有明文 42:
(5) 加密
密文 = 明文 E mod N => 42 5 mod 323 = 264
(6) 解密
明文 = 密文 D mod N => 264 29 mod 323 => 數比較大,可分解求冪 :
(264 10 mod 323) * (264 10 mod 323) * (264 9 mod 323) mod 323 = 42
RSA 的算法原理
想了解更多RSA背后的數學知識,可以參考阮一峰的 RSA算法原理(一) 和 (二)
AES和RSA的應用
混合密碼系統
通過比較,我們知道:
- RSA 比 AES 更難破解,因為它不需要擔心密鑰在傳遞過程中有泄露,只存在暴力破解一種可能;
- AES的優勢是以分組為輪,加解密速度非常快,一般而言,AES 速度上數百倍於 RSA
所以在實際應用中,我們會混合應用AES和RSA,比如 需要加密一個尺寸不小的文件,可能會這么干:
- 生成一個一次性隨機密鑰,算法上采用 AES 的CBC模式 aes-256-cbc(加密分組為256比特)對文件進行加密
- 加密完成后,為了安全的傳遞這個一次性隨機密鑰,我們使用 接收方的RSA公鑰 對其進行加密,隨加密后的文件一起發送
- 接收方使用私鑰進行解密,得到AES密鑰原文,並用其解密文件
例子
以上場景的應用,比如在 Node.js 中,可以這么實現:
(1) 生成 AES 隨機密鑰:
var passwdLength = 256; // 初始化隨機向量長度 var aesPassword = require('crypto').randomBytes(passwdLength); require('fs').writeFileSync('aesPassword', aesPassword); // 寫入文件供openssl使用
(2) 使用openssl aes 加密 filename代表的文件:
openssl enc -aes-256-cbc -kfile aesPassword -in filename -out filename.out
(3) 使用open rsa 加密密鑰
openssl enc rsautl -encrypt -pubin -inkey id_rsa.pub -in aespassword -out aespassword.out
將 filename.out 和 aespassword.out 一並發給對方即可,接收方使用openssl 進行一次逆操作即可實現解密。
參考
- openssl 命令手冊:http://netkiller.github.io/cryptography/openssl/index.html
- Node-RSA: https://github.com/rzcoder/node-rsa
更多文章請移步我的blog新地址: http://www.moye.me/