Encryption

Bouncycastle RSA+OAEP 實施是否容易受到 Manger 的攻擊?

  • August 18, 2018

我已經編寫了一個程式碼來加密明文,如下所示。在這裡,我使用 bouncycastle 加密提供程序並為此參考 RSA+OAEP。

public static void main(String [] args) throws Exception {
   Security.insertProviderAt(new BouncyCastleProvider(), 1);
   String ciphertext = Base64.encode(encrypt(plaintext));
   String recoveredPlaintext = decrypt(Base64.decode(ciphertext));
}

private static byte [] encrypt(String plaintext) throws Exception {
   KeyStore keyStore = getKeyStore();
   Certificate[] certs = keyStore.getCertificateChain("oaep");
   Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA1andMGF1Padding","BC");
   cipher.init(Cipher.ENCRYPT_MODE, certs[0].getPublicKey());
   return cipher.doFinal(plaintext.getBytes());
}

private static String decrypt(byte [] ciphertext) throws Exception {
   KeyStore keyStore = getKeyStore();
   PrivateKey privateKey = (PrivateKey) keyStore.getKey("oaep",
           "oaep".toCharArray());
   Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA1andMGF1Padding","BC");
   cipher.init(Cipher.DECRYPT_MODE, privateKey);
   byte[] cipherbyte=cipher.doFinal(ciphertext);
   return new String(cipherbyte);
}

當我在Google上搜尋時,我偶然發現了這篇博文;這表示 RSA+OAEP 容易受到“Manger 攻擊”。

  1. 我想知道我的程式碼是否受到此漏洞的影響,如果是,我該如何檢查?
  2. 有幾個 PKCS#1版本。bouncycastle 中使用了哪個版本的實現,我如何才能知道 PKCS# 1 的確切版本(2.0、2.1、2.2)?

Bouncy Castle Java 版本 1.60 和 FIPS 1.0.1(及之前版本)正是 Manger 攻擊中利用的問題:當密文出現時發生異常 $ c $ 送出使得 $ c^d\bmod N $ , 表示為寬度為 $ N $ , 不以0x00字節開頭;然後不會發生其餘的解密過程,從而顯著加快執行速度。如果對手可以迭代地送出數千條消息進行解密並檢測到異常或更快的執行,他/她可以解密密文或進行簽名。私鑰本身不會洩漏。

預發布版本 1.61b03(可在此處獲得,也在GitHub 上)有一個修復程序,可以消除不需要的異常,並將時序變化減少幾個數量級。如果它仍然易受攻擊,那隻能由攻擊者精確和重複計時。


PKCS#1v2後期版本中的Manger攻擊及對策

該攻擊在 James Manger 的A Chosen Ciphertext Attack on RSA Optimal Asymmetric Encryption Padding (OAEP) 中公開,該攻擊在 PKCS #1 v2.0 中標準化(在Crypto 2001 的程序中)。它利用了一個 RSA 解密系統,該系統洩露了一些關於 $ y=x^d\bmod N $ 為了 $ x $ 被攻擊者反複選擇。

攻擊的應用部分側重於 if 的洩漏 $ y<2^{8k-8} $ 和 $ N $ 的 $ 8k-7 $ 至 $ 8k $ 少量; 或等效地:確實 $ y $ 0x00當表示為大端字節串時,以字節開頭 $ k $ 字節,因為它應該在 RSAES-OAEP 填充中:

OAEP

PKCS#1 v2 7.1.2(解密操作)步驟 4 中,0x00通過使用“長度 $ k-1 $ “ 在:

轉換消息代表 $ m $ 到一個編碼的消息 $ \text{EM} $ 長度 $ k-1 $ 八位字節: $ \text{EM}=\text{I2OSP}(m,k-1) $

如果 I2OSP 輸出“integer too large”,則輸出“decryption error”並停止。

PKCS#1v2 有一個說明,指出上述“解密錯誤”必須與在取消 OAEP 屏蔽後進行的另一次冗餘檢查失敗時可能發生的錯誤相同。

Manger 觀察到並不是所有的實現都能正確地做到這一點。當測試失敗時,那些按照指示做的人花費的時間更少(由於stop,其餘程序的其餘部分沒有執行,節省時間),因此計時可以揭示所需的資訊位。

這在PKCS#1 v2.1(和PKCS#1 v2.2,在這方面相同)中通過使用 $ \text{I2OSP}(m,k) $ 沒有 $ -1 $ ,它輸出一個額外的額外字節 $ Y $ 並且永遠不會觸發錯誤。 $ Y $ 最後進行測試,以及在 OAEP 取消屏蔽後應用於消息的其他冗餘標準。

如果沒有十六進制值的八位字節0x01來分隔 $ \text{PS} $ 從 $ M $ , 如果 $ \text{lHash} $ 不等於 $ \text{lHash’} $ , 或者如果 $ Y $ 為非零,輸出“解密錯誤”並停止。

筆記。必須注意確保對手無法區分(上述)中的不同錯誤條件,無論是通過錯誤消息還是時間,或者更一般地,了解有關編碼消息 EM 的部分資訊。否則,對手可能能夠獲得有關密文解密的有用資訊,從而導致選擇密文攻擊,例如Manger觀察到的攻擊。

PKCS#1v2.0、2.1 和 2.2 中的所有 RSAES-OAEP 版本都是可互操作的,並且所描述的過程導致相同的結果,除了它們的時間變化不太可能在 2.1 和更高版本中洩漏資訊。


充氣城堡的程式碼

在 Bouncy Castle 的 1.60 版中(目前是提出問題並且首先起草此答案時),特定於 RSAES-OAPEP 解密的程式碼decodeBlock位於OAEPEncoding.java. 包含

   byte[]  data = engine.processBlock(in, inOff, inLen);
   byte[]  block = new byte[engine.getOutputBlockSize()];
   // as we may have zeros in our leading bytes for the block we produced
   // on encryption, we need to make sure our decrypted block comes back
   // the same size.
   System.arraycopy(data, 0, block, block.length - data.length, data.length);

的定義engine是這樣的,即在前導零字節被抑制的情況下processBlock返回data,並getOutputBlockSize返回 $ k-1 $ 什麼時候 $ N $ 是 $ k $ -字節。隨之而來的block.length - data.length是 $ -1 $ 什麼時候 $ (c^d\bmod N)\ge2^{8k-8} $ ,然後System.arraycopy使ArrayIndexOutOfBoundsException未在本地擷取,也未在邀請呼叫者期望的那些中列出,並且該函式的其餘部分未執行。

PKCS1 v2.0、v2.1 和 v2.2(分別為 rfc2437、rfc3447、rfc8017)中的 RSAES-OAEP 方案是相同的方案,儘管符號在 2.0 和 2.1 之間發生了變化;看看為什麼 oaep 從 pkcs1 v2.0 和 v2.1 發生變化?https://stackoverflow.com/questions/42709020/is-there-a-default-label-for-the-built-in-rsa-oaep-encryption-in-java。該方案與 Bellare & Rogaway 最初提出的方案略有不同(不可互操作)。

JCA API 不允許返回任何指示 RSA 解密但仍填充的值是 < $ 2^{(nsize-detectable)} $ 所以不可能有明確的預言。unpadding 的 BC 實現org.bouncycastle.crypto.encodings.OAEPEncoding顯然試圖保持恆定時間,我沒有看到他們遺漏的任何內容。但是,底層的 RSA ‘核心’(org.bouncycastle.crypto.engines.RSABlindedEngine用於防禦定時或側通道)確實間接地將結果BigInteger暫時放入,這可能會給出一些定時信號,儘管我認為通常很少用於(圓形二進制)密鑰大小使用,因為該值的前 32 位必須為零,而不僅僅是 8。如果您想確定,可以嘗試測試它。

當然,這只有在攻擊者能夠接收到信號時才有意義;如果它們是本地的(在同一台機器上),通常有幾種可能性,但如果它們是遠端的,那麼只有在您(總是)對每個聲稱的密文(好壞)給出及時可見的響應時,它才能工作。

此外,正如 Maarten 評論的那樣,使用 JVM 預設字元集進行編碼和解碼是一種破壞數據的好方法,至少是間歇性的,儘管不是安全問題。

引用自:https://crypto.stackexchange.com/questions/61121