1. 程式人生 > >關於rsa加解密的內容超長的問題

關於rsa加解密的內容超長的問題

轉自:https://blog.csdn.net/taoxin52/article/details/53782470

一. 現象:
     有一段老程式碼用來加密的,但是在使用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);


     將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,一次加密內容不能超過245bytes。

而分段加密程式碼中用128為單位分段,從而使得一個金鑰報錯,另一個不報錯。

四.擴充套件:
為什麼一次rsa加密的資料長度限制為 (金鑰長度/8-11) ?
  網上有說明文長度小於等於金鑰長度(Bytes)-11,這說法本身不太準確,會給人感覺RSA 1024只能加密117位元組長度明文。實際上,RSA演算法本身要求加密內容也就是明文長度m必須0<m<n,也就是說內容這個大整數不能超過n,否則就出錯。那麼如果m=0是什麼結果?普遍RSA加密器會直接返回全0結果。如果m>n,由於me ≡ 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為明文):  me ≡ c (mod n)
解密(c為密文,m為明文):  cd ≡ 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加解密金鑰)加密對稱金鑰。原問題中由於雙方約定了非對稱加密的方式,所以用分段加密來解決了問題,但是可以知道這樣是比較低效的。
---------------------