一. 現象:
有一段老代碼用來加密的,但是在使用key A的時候,拋出了異常:
javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes。
老代碼已經做了分段的加密,應該是已經考慮了加密長度的問題才對。換了另一個線上代碼中的key B,正常加密沒有異常。
二. 解決:
老代碼如下:
private static String
encryptByPublicKey(String plainText
, String publicKey)
throws Exception {
int MAX_ENCRYPT_BLOCK = 128 ;
byte[] data = plainText.getBytes( "utf-8") ;
Key e = RSASignature. getPublicKey(publicKey) ;
// 對數據加密
Cipher cipher = Cipher. getInstance( "RSA") ;
cipher.init(Cipher. ENCRYPT_MODE , e) ;
int inputLen = data. length ;
ByteArrayOutputStream out = new ByteArrayOutputStream() ;
int offSet = 0 ;
byte[] cache ;
int i = 0 ;
// 對數據分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data , offSet , MAX_ENCRYPT_BLOCK) ;
} else {
cache = cipher.doFinal(data , offSet , inputLen - offSet) ;
}
out.write(cache , 0 , cache. length) ;
i++ ;
offSet = i * MAX_ENCRYPT_BLOCK ;
}
byte[] encryptedData = out.toByteArray() ;
out.close() ;
return Base64. encodeBase64String(encryptedData) ;
int MAX_ENCRYPT_BLOCK = 128 ;
byte[] data = plainText.getBytes( "utf-8") ;
Key e = RSASignature. getPublicKey(publicKey) ;
// 對數據加密
Cipher cipher = Cipher. getInstance( "RSA") ;
cipher.init(Cipher. ENCRYPT_MODE , e) ;
int inputLen = data. length ;
ByteArrayOutputStream out = new ByteArrayOutputStream() ;
int offSet = 0 ;
byte[] cache ;
int i = 0 ;
// 對數據分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data , offSet , MAX_ENCRYPT_BLOCK) ;
} else {
cache = cipher.doFinal(data , offSet , inputLen - offSet) ;
}
out.write(cache , 0 , cache. length) ;
i++ ;
offSet = i * MAX_ENCRYPT_BLOCK ;
}
byte[] encryptedData = out.toByteArray() ;
out.close() ;
return Base64. encodeBase64String(encryptedData) ;
}
將
MAX_ENCRYPT_BLOCK值換為64就解決了問題。按報錯提示的改為117也可以,不過為了湊整,選擇了64。
三. 原因:
實際使用RSA加解密算法通常有兩種不同的方式,一種是使用對稱密鑰(比如
AES/
DES等加解密方法)加密數據,然后使用非對稱密鑰(RSA加解密密鑰)加密對稱密鑰;另一種是直接使用非對稱密鑰加密數據。第一種方式安全性高,復雜度也高,不存在加密數據長度限制問題,第二種方式安全性差一些,復雜度低,但是存在加密數據限制問題(即使用非對稱密鑰加密數據時,一次加密的數據長度是(密鑰長度/8-11))。
目前雙方約定的方式為第二種方式,而對應於本次拋錯的密鑰,key長度為1024位,1024/8 - 11 = 117,所以一次加密內容不能超過117bytes。另一個密鑰沒有問題,因為key的長度為2048位,
2048
/8 - 11 = 245,
一次加密內容不能超過
245
bytes。而分段加密代碼中用128為單位分段,從而使得一個密鑰報錯,另一個不報錯。
四.擴展:
- 為什么一次加密的數據長度為 (密鑰長度/8-11) ?
網上有說明文長度小於等於密鑰長度(Bytes)-11,這說法本身不太准確,會給人感覺RSA 1024只能加密117字節長度明文。實際上,RSA算法本身要求加密內容也就是明文長度m必須0<m<n,也就是說內容這個大整數不能超過n,否則就出錯。那么如果m=0是什么結果?普遍RSA加密器會直接返回全0結果。如果m>n,由於 m e ≡ c (mod n) ,c為密文,m為明文,e和n組成公鑰,顯然當m>n時,m與m-n得出的密文一樣,無法解密,運算就會出錯。所以,RSA 1024 實際可加密的明文長度最大也是1024bits,但問題就來了:
如果小於這個長度怎么辦?就需要進行padding,因為如果沒有padding,用戶無法確分解密后內容的真實長度,字符串之類的內容問題還不大,以0作為結束符,但對二進制數據就很難理解,因為不確定后面的0是內容還是內容結束符。
只要用到padding,那么就要占用實際的明文長度,於是才有117字節的說法。我們一般使用的padding標准有NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建議的padding就占用了11個字節。
如果大於這個長度怎么辦?很多算法的padding往往是在后邊的,但PKCS的padding則是在前面的,此為有意設計,有意的把第一個字節置0以確保m的值小於n。
這樣,128字節(1024bits)-減去11字節正好是117字節,但對於RSA加密來講,padding也是參與加密的,所以,依然按照1024bits去理解,但實際的明文只有117字節了。
關於PKCS#1 padding規范可參考:RFC2313 chapter 8.1,我們在把明文送給RSA加密器前,要確認這個值是不是大於n,也就是如果接近n位長,那么需要先padding再分段加密。除非我們是“定長定量自己可控可理解”的加密不需要padding。 - 為什么有不同長度的key?
看一下密鑰的生成過程:第一步,隨機選擇兩個不相等的質數p和q。
第二步,計算p和q的乘積n。n即密鑰長度。
第三步,計算n的歐拉函數φ(n)。
第四步,隨機選擇一個整數e,條件是1< e < φ(n),且e與φ(n) 互質。
第五步,計算e對於φ(n)的模反元素d。
第六步,將n和e封裝成公鑰,n和d封裝成私鑰。加密(c為密文,m為明文): m e ≡ c (mod n)解密 (c為密文,m為明文) : c d ≡ m (mod n)對極大整數做因數分解(由n,e推出d)的難度決定了RSA算法的可靠性。換言之,對一極大整數做因數分解愈困難,RSA算法愈可靠。假如有人找到一種快速因數分解的算法,那么RSA的可靠性就會極度下降。但找到這樣的算法的可能性是非常小的。今天只有短的RSA密鑰才可能被暴力破解。只要密鑰長度足夠長,用RSA加密的信息實際上是不能被解破的。目前一般為1024 bit以上的密鑰,推薦2048 bit以上。 - 對稱加密vs分對稱加密?
對稱加密是最快速、最簡單的一種加密方式,加密(encryption)與解密(decryption)用的是同樣的密鑰(secret key)。對稱加密有很多種算法,由於它效率很高,所以被廣泛使用在很多加密協議的核心當中。對稱加密通常使用的是相對較小的密鑰,一般小於256 bit。因為密鑰越大,加密越強,但加密與解密的過程越慢。密鑰的大小既要照顧到安全性,也要照顧到效率,是一個trade-off。對稱加密的一大缺點是密鑰的管理與分配。非對稱加密為數據的加密與解密提供了一個非常安全的方法,它使用了一對密鑰,公鑰(public key)和私鑰(private key)。私鑰只能由一方安全保管,不能外泄,而公鑰則可以發給任何請求它的人。非對稱加密使用這對密鑰中的一個進行加密,而解密則需要另一個密鑰。雖然非對稱加密很安全,但是和對稱加密比起來,它非常的慢。將兩者結合起來,將對稱加密的密鑰使用非對稱加密的公鑰進行加密,然后發送出去,接收方使用私鑰進行解密得到對稱加密的密鑰,然后雙方可以使用對稱加密來進行溝通。
五.結論:
優先選擇方案:使用對稱密鑰(比如
AES/
DES等加解密方法)加密數據,然后使用非對稱密鑰(RSA加解密密鑰)加密對稱密鑰。原問題中由於雙方約定了非對稱加密的方式,所以用分段加密來解決了問題,但是可以知道這樣是比較低效的。