寫代碼這麼多年了,在涉及RSA跨語言的功能時,總會讓人經歷一番折磨。因此有必要把如今我掌握的這些零碎的知識總結一下公佈出來,省得組內同事再走彎路。因爲RSA實在太複雜,還有不少內容沒弄懂,也沒精力全瞭解,暫且把問題留下,將來也許能回答。因此這篇文章的標題爲「經驗之談」java
本質上是數字運算git
RSA算法的本質是找到幾個很是大的數,而後作運算,加密解密都是運算而已。進行加解密以前你總要產生一個密鑰對,這就是查找這幾個數的過程。在產生的數字中,p,q是兩個大質數,其乘積爲n,通過挑選(你不要管究竟是怎麼挑選的)又會獲得e和d,公鑰就是(n, e),私鑰就是(n, d),這個n的位數就是咱們所說的密鑰長度, 轉換成爲二進制,則要爲64的倍數github
從上面你能夠了解到,其實所謂密鑰最初就是幾個很大的數,給咱們帶來不少麻煩的實際上是如何保存和傳輸這些數字。而所謂的加密解密,簽名驗籤,也不過是指數操做而後取模的數字運算而已(這裏排除了摘要運算)算法
基於字節的存儲編程
平時咱們看到被加密或者簽名的明文都是字符串,公私鑰保存成的也是字符串。實際上在進行RSA運算時,都是基於數字的。在編程語言中,大數字可能很差表示,因此咱們看到的SDK API每每都是以字節數組體現(byte[]),這樣一來,在保存和傳輸時,字節數組和字符串之間的轉換就成了容易出錯的地方。數組
待加密/簽名的明文的字符串要根據其編碼方式轉換成爲字節數組,好比ascii或者UTF-8。解密以後的要作逆運算轉換爲明文。而公私鑰則要看具體遵守的規範來決定如何轉換,這其中涉及到不僅一步編解碼處理,不過基本上最後/第一步都是base64和字節之間的轉換安全
C#的那一大堆參數框架
C#標準庫的RSACryptoServiceProvider產生的密鑰對兒要以xml字符串的形式導出,聯調時候咱們發現不少參數不知道是幹什麼用的,dp, dq, InverseQ...這些多出來的東西對公鑰沒影響,但一股腦的都放到私鑰中了編程語言
在PKCS#1 version 2.2中解釋其爲Chinese Remainder Theorem(CRT),具體這種CRT是怎麼計算的我並沒深刻研究,但其再也不是上面是說的n是兩個大質數的乘積,而是多個的乘積ide
最要命的是RSACryptoServiceProvider好像並沒提供把這個xml轉換成爲我咱們經常使用的字節數組的方法,因此我在demo中使用的BouncyCastle庫
公鑰與私鑰的關係
公鑰用來加密私鑰解密。簽名則是私鑰簽名,公鑰驗籤。不用硬背,你記住私鑰是要本身藏起來的,公鑰是分配出去,而後本身推導就好:有人想把信息隱祕的發送給你,就要求只有你能解密。而你想把信息發出的同時證實本身的身份,則要求你可以簽名
也許你會由於你用過的一些框架而疑惑:私鑰也能夠用來加密,公鑰也能夠用來解密。我以爲用「加密」「解密」這個字眼並不合適,仍是用運算比較好。理論上這一對密鑰徹底能夠倒過來使用,但這樣一來私鑰就要公佈出去,公鑰反倒要妥善保存,可生成密鑰對的實際狀況是,各語言框架會考慮運算時的複雜程度,公鑰中的e通常會取3或者65537, 而私鑰中有n了,整個公鑰很容易猜到了
我聽到過一種說法,是「私鑰能夠生成公鑰」,這是錯誤的,上面一條說私鑰能夠「猜」出公鑰,也並非「生成」。我想這說法應該是看到pfx文件導入到IIS中能夠再導出公鑰,其實pfx文件裏本就包含公鑰信息,不過這種包含全部參數信息(n, e, d, p, q, dp, dq...)的密鑰格式彷佛還很多,不少「私鑰文件能夠導出公鑰」
簽名操做
簽名操做近似於對明文作哈希操做,而後再進行基於私鑰的加密,說近似是由於我曾嘗試基於上面流程本身實現簽名,結果不正確,也許是缺乏某些步驟的處理好比填充
要作哈希是由於一方面RSA運算比較費時,另外一方面其沒有AES那麼多種分組算法,RSA只能作其長度(字節數)- 11的這麼長的明文的運算(聽說這個公式也和填充方式有關,未必準),超過的部分截斷,再進行一次運算。因此基本上沒人用RSA進行加密解密,都是把明文哈希以後縮短了,再進行簽名驗籤
PKCS是啥
咱們在調用SDK時候常常看到的PKCS#1,PKCS#8,甚至到PKCS#12,實際上是「Public-Key Cryptography Standards」,其不一樣編號涉及的內容也不相同,以前寫代碼時候發現PKCS#1常常做爲padding的一個參數,其實#1中規範的內容不止如此(這裏的Public-Key我認爲不是說這個標準僅涉及公鑰),甚至包含了怎麼把數字轉化爲字符串的方式
每一個編號都作了一些事情,你能夠從wiki上看到列表
咱們經常能看見PKCS#1做爲Padding的參數,對應還有經常使用的NoPadding,不填充。Java的Demo中,傳入cipher algorithm 參數爲"RSA"則會默認使用PKCS#1,C#的Demo對應RSAUtils類的Encrypt和Decrypt方法,使用了PKCS1Encoding類,而C的Demo則明確指定了PKCS1/SSL V23(鬼才知道啥是SSL V23),若是你想改爲NoPadding,Java cipher algorithm 參數傳入「RSA/ECB/NoPadding」 (基於Java 8), C#則要去掉PKCS1Encoding類,直接使用RsaEngine類
其餘padding方式均未試驗成功
帶padding的方式會致使每次加密結果不同,由於填充了隨機數
RSA是否更安全
RSA未必更難破解,聽說等價於AES256安全級別的RSA,密鑰長度要遠大於如今咱們經常使用的2048。反過來,同安全級別的RSA,聽說,聽說,要比其餘算法慢1000倍。之因此咱們要用它主要仍是由於密鑰泄露的概率很低
還有一大堆沒弄懂的事
爲何Java讀取公鑰都用X509來封裝,而讀取私鑰則用PKCS8封裝?哪怕我生成密鑰對根本沒作指定?
ASN.1規定了什麼,DER和BER格式是怎樣?
還有一大堆crt,cer, pfx格式的證書文件都是啥?
簽名的流程究竟是啥,爲何不用指定padding方式?我本身寫的簽名方法差了哪一步?
C#標準庫到底要怎麼導出可供別的語言用的公私鑰字符串而不是證書?
最後獻上4個能夠互通的demo(雖然我不太知道是怎麼通的)僅供參考