国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

最佳安全實踐:在 Java 和 Android 中使用 AES 進行對稱加密:第2部分:AES-CB

Ververica / 726人閱讀

摘要:如何解決這個問題正如我在上一篇文章中所說的那樣,一種可能的解決方案是將加密原語組合在一起以包含加密驗證碼。但是,這些原語通常在所有環境中都可用,因此它可能是你唯一的選擇。

本文是我上一篇文章:“最佳安全實踐:在 Java和 Android 中使用 AES 進行對稱加密” 的續篇,在這篇文章中我總結了關于 AES 最為重要的事情并演示了如何通過 AES-GCM 來使用它。在閱讀本文并深入下一個主題之前,我強烈建議你閱讀它,因為它解釋了最重要的基礎知識。

本文討論了以下可能發生的情況:你不能通過類似 Galois/Counter Mode (GCM) 的認證加密模式來使用高級加密標準(AES)?你當前使用的平臺不支持它,或者你必須兼容老版本或其它第三方協議?無論你放棄 GCM 的原因是什么,你都不應該放棄它所具有的安全屬性:

保密性:沒有密鑰的人無法閱讀該消息

完整性:沒有人會修改消息內容

真實性:可以對消息的發送者進行驗證

選擇非認證加密,比如塊模式密碼分組鏈接(CBC),不幸的是,由于具備很好的延展性,它缺少后兩個安全屬性。如何解決這個問題?正如我在上一篇文章中所說的那樣,一種可能的解決方案是將加密原語組合在一起以包含加密驗證碼(MAC)。

加密驗證碼(MAC)

那么什么是 MAC,我們為什么要使用它呢?MAC 類似于散列函數,這意味著它將消息作為輸入并生成一個所謂的簡短標記。為了確保并非任何人都可以為任意消息創建標記,MAC 函數需要一個密鑰來進行計算。與使用非對稱加密的簽名相比,MAC 可使用相同的密鑰來進行標記生成和認證。

例如,如果雙方安全地交換了 MAC 密鑰,并且每條消息都附加了認證標記,那么它們都可以檢查消息是否是由另一方創建的,并且在傳輸過程中沒有被更改。攻擊者需要保密的 MAC 密鑰來偽造身份進行標記驗證。

最廣泛使用的 MAC 類型之一是 散列消息密鑰驗證碼(HMAC),它包含一個哈希加密函數,該函數通常是 SHA256。由于我不會詳細介紹其算法,因此我建議你閱讀相關 RFC。當然還有如 CBC-MAC 等其他可用于對稱加密的類型。幾乎所有的加密框架都至少包含一個 HMAC 實現,包括通過 Mac 實現的 JCA/JCE。

使用加密的 MAC:架構

那么正確應用 MAC 的方法是什么呢?根據安全研究院 Hugo Krawcyzk 的說法,這里有三種基本選項:

MAC-then-Encrypt:基于明文生成 MAC,然后將其追加到明文中后再進行加密(在 SSL 中使用)

Encrypt-then-MAC:基于密文和初始向量生成 MAC,然后將其追加到密文中(在 IPsec 中使用)

Encrypt-and-MAC: 基于明文生成 MAC、然后將其追加到密文中(在 SSH 中使用)

每一個選項都有它自己的屬性,我建議你通過這篇文章來獲取每個選項的完整參數。總而言之,大部分 研究員 推薦使用 Encrypt-then-MAC(EtM)。由于 MAC 可以防止不正確消息的解密,它可以防止選擇密文攻擊。此外也由于 MAC 在密文中運行,它不能泄漏有關明文的任何信息。然而它的缺點是,因為 IV 和標記中必須包含可能的協議/算法版本或類型,因此實施起來稍微有些困難。重要的是在驗證 MAC 之前永遠不要進行任何加密操作,否則你可能受到 padding-oracle 攻擊(Moxie 稱之為末日原則)。


Encrypt-then-Mac 架構

附錄:CGM 和 Encrypt-then-MAC 通常情況下它們的安全強度可能類似,CGM 有以下優點:

簡單易用而不易出錯

更快,因為它只需要一次通過整個信息

它的缺點是只能允許 96 位初始向量(對于 128 位),HMAC 理論上比 GCM 的內部 MAC 算法 GHASH(128 位標記大小對 256 位及以上)更強。GCM 無法進行 IV + 密鑰重用。相關詳細討論,請查閱此處。

使用加密的 MAC:驗證密鑰

我們必須解決的最后一個問題是:我們應該從哪里獲得用于 MAC 計算的密鑰?如果使用的是強密鑰(即足夠隨機且可以安全地切換),那么使用與加密相同的密鑰(當使用 HMAC 時)似乎沒有已知問題。但最佳實踐是使用密鑰派生函數(KDF)派生出 2 個子密鑰以防范未來可能發現的任何問題。這可以像計算主密鑰上的 SHA256 并將其拆分為兩個 16 字節塊一樣簡單。 但是我更喜歡標準化的協議,比如基于 HMAC 的 Extract-and-Expand 密鑰派生函數,它直接支持此場景而不需要字節調整。


2 個子密鑰的派生

在 Java 和 Android 中使用 EtM 實現 AES-CBC

理論已經足夠了,現在讓我們開始編碼!在接下來的例子中,我將使用 AES-CBC,這是一個看似保守的決定。這樣做的原因是,應該保證幾乎每個 JRE 和 Android 版本都可以使用它。如前所述,我們將使用帶有 HMAC 的 Encrypt-then-Mac 方案。這里唯一的外部依賴是 HKDF。這段代碼基本上是我在上一篇文章中描述的 GCM 示例的一個映射。

加密

簡單起見,我們使用隨機生成的 128 位密鑰。當你傳遞 128、192 或 256 位長度的密鑰時,Java 將自動選擇正確的模式。但請注意,256 位加密通常需要在 JRE 中安裝 無政策限制權限文件(OpenJDK 和 Android 無需安裝)。如果你不確定要使用的密鑰大小,請在我的上一篇文章中閱讀關于該主題的相關段落。

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);

然后我們需要創建我們的初始化向量。對于 CBC,應該使用 16 個字節長的初始向量(IV)。請注意,始終使用像 SecureRandom 這樣的強偽隨機數生成器(PRNG)。

byte[] iv = new byte[16];
secureRandom.nextBytes(iv);

重用 IV 不像 GCM 那樣具有災難性,但最好還是避免使用。在這里可以看到可能的攻擊。

下一步,我們將派生出加密和身份驗證所需的 2 個子密鑰。我們將在配置 HMAC-SHA256(使用此庫)中使用 HKDF,由于它使用起來簡單直接。我們使用 HKDF 中的 info 參數來生成兩個 16 字節子密鑰,從而對它們進行區分。

// import at.favre.lib.crypto.HKDF;
byte[] encKey = HKDF.fromHmacSha256().expand(key, "encKey".getBytes(StandardCharsets.UTF_8), 16);
byte[] authKey = HKDF.fromHmacSha256().expand(key, "authKey".getBytes(StandardCharsets.UTF_8), 32); //HMAC-SHA256 key is 32 byte

接下來,我們將初始化密碼并加密我們的明文。由于 CBC 的行為類似于塊模式,因此我們需要一個填充模式用于填充不完全符合 16 字節塊大小的信息。由于對使用的填充方案似乎沒有安全隱患,我們選擇了支持最廣泛的:PKCS#7。

注意: 由于歷史原因,我們必須將密碼套件設置為 PKCS5。除了被定義為了不同的塊尺寸,兩者幾乎完全相同;通常情況下 PKCS#5 與 AES 并不兼容,但由于定義可追溯到使用了 8 字節塊的 3DES,我們堅持使用它。如果你的 JCE 提供程序接受 AES/CBC/PKCS7Padding,那么使用此定義更好,如此你的代碼將更容易被理解。

final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //actually uses PKCS#7
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encKey, "AES"), new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(plainText);

接下來,我們需要準備 MAC 并添加主要數據來進行身份驗證。

SecretKey macKey = new SecretKeySpec(authKey, "HmacSHA256");
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(macKey);
hmac.update(iv);
hmac.update(cipherText);

如果你想要驗證其他元數據(比如協議版本),你還可以將其添加到 mac 生成過程中。這與將關聯數據添加到經過身份驗證的加密算法的概念相同。

if (associatedData != null) {
    hmac.update(associatedData);
}

然后計算 mac:

byte[] mac = hmac.doFinal();

最后將所有信息序列化為單個消息:

ByteBuffer byteBuffer = ByteBuffer.allocate(1 + iv.length + 1 + mac.length + cipherText.length);
byteBuffer.put((byte) iv.length);
byteBuffer.put(iv);
byteBuffer.put((byte) mac.length);
byteBuffer.put(mac);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

這基本上就是加密。將構建消息、IV、IV 的長度以及 mac 的長度、mac 和加密數據附加到單個字節數組。

如果你需要字符串表示,可以選用 Base64 對其進行編碼。Android 中有該編碼的標準實現,JDK 僅從版本 8 開始支持(如果可能,我將避免使用 Apache Commons Codec,因為它很慢且實現混亂)。

由于 Java 是一種自動內存管理語言,因此最佳做法是盡可能快地從內存中擦除加密密鑰或 IV 等敏感數據。我們無法保證以下內容能夠按照預期工作,但在大多數情況下應該如此:

Arrays.fill(authKey, (byte) 0);
Arrays.fill(encKey, (byte) 0);

注意不要覆蓋還在其他地方使用的數據。

解密

解密和反向加密類似:首先解構消息。

ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);

int ivLength = (byteBuffer.get());
if (ivLength != 16) { // check input parameter
    throw new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);

int macLength = (byteBuffer.get());
if (macLength != 32) { // check input parameter
    throw new IllegalArgumentException("invalid mac length");
}
byte[] mac = new byte[macLength];
byteBuffer.get(mac);

byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);

仔細驗證輸入參數以防止拒絕服務攻擊,如 IV 或 mac 長度,因為攻擊者可能會更改相關值。

然后導出解密和身份驗證所需的密鑰。

// import at.favre.lib.crypto.HKDF;
byte[] encKey = HKDF.fromHmacSha256().expand(key, "encKey".getBytes(StandardCharsets.UTF_8), 16);
byte[] authKey = HKDF.fromHmacSha256().expand(key, "authKey".getBytes(StandardCharsets.UTF_8), 32);

在我們解密任何東西之前,我們將驗證 MAC。首先我們像之前一樣計算 MAC;不要忘記之前添加的相關數據。

SecretKey macKey = new SecretKeySpec(authKey, "HmacSHA256");
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(macKey);
hmac.update(iv);
hmac.update(cipherText);
if (associatedData != null) {
    hmac.update(associatedData);
}
byte[] refMac = hmac.doFinal();

比較 mac 時,我們需要一個恒定的時間比較函數來避免旁道攻擊;閱讀此文了解為什么這很重要。幸運的是我們可以使用 MessageDigest.isEquals()(舊的 bug 已在 Java 6u17 中修復):

if (!MessageDigest.isEqual(refMac, mac)) {
    throw new SecurityException("could not authenticate");
}

作為最后一步,我們最終可以解密我們的消息。

final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encKey, "AES"), new IvParameterSpec(iv));
byte[] plainText = cipher.doFinal(cipherText);

以上便是所有內容,如果你想查看一個完整的例子,請查看我托管到 Github 中的一個使用 AES-CBC 的項目 Armadillo。如果你遇到了什么問題,也可以在 Gist 中找到這個確切的示例。

總結

我們演示了使用密碼分組鏈接(CBC)的 AES 和使用 HMAC 的 Encrypt-then-MAC 架構提供了我們希望從加密協議中看到的所有理想的安全屬性:保密性、完整性和真實性。

可以看出,僅僅使用了 GCM,協議就變得復雜了。但是,這些原語通常在所有 Java/Android 環境中都可用,因此它可能是你唯一的選擇。請考慮以下事項:

使用 16 字節隨機初始化向量(使用強 PRNG)

使用 128 位以上的 MAC 長度(HMAC-SHA256 輸出 256 位)

使用 Encrypt-then-Mac

使用 KDF 派生出 2 個子密鑰

解密之前進行驗證(末日原則)

通過使用恒定時間等于實現來防止定時攻擊

使用 128 位加密密鑰長度(你會沒事的!)

將所有內容整合到一條消息中

參考資料:

patrickfav/hkdf

patrickfav/armadillo

進一步閱讀:

最佳安全實踐:在 Java 和 Android 中使用 AES 進行對稱加密

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72972.html

相關文章

  • Android 應用安全開發之淺談加密算法的坑

    摘要:還有很多開發者沒有意識到的加密算法的問題。不要使用哈希函數做為對稱加密算法的簽名。開發者建議使用基于口令的加密算法時,生成密鑰時要加鹽,鹽的取值最好來自,并指定迭代次數。不要使用沒有消息認證的加密算法加密消息,無法防重放。 本文作者:阿里移動安全@伊樵,@舟海 Android開發中,難免會遇到需要加解密一些數據內容存到本地文件、或者通過網絡傳輸到其他服務器和設備的問題,但并不是使用了加...

    不知名網友 評論0 收藏0
  • 聊聊加密那點事——PHP加密最佳實踐

    摘要:所謂對稱加密,就是加密和解密使用同一秘鑰,這也是這種加密算法最顯著的缺點之一。非對稱加密算法由于對稱加密在通信加密領域的缺陷,年和提出了非對稱加密的概念。非對稱加密,其主要缺點之一就是慢,適合加密少量數據。 1. 加密的目的 加密不同于密碼,加密是一個動作或者過程,其目的就是將一段明文信息(人類或機器可以直接讀懂的信息)變為一段看上去沒有任何意義的字符,必須通過事先約定的解密規則才能將...

    Mr_zhang 評論0 收藏0

發表評論

0條評論

Ververica

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<