摘要:如何解決這個問題正如我在上一篇文章中所說的那樣,一種可能的解決方案是將加密原語組合在一起以包含加密驗(yàn)證碼。但是,這些原語通常在所有環(huán)境中都可用,因此它可能是你唯一的選擇。
本文是我上一篇文章:“最佳安全實(shí)踐:在 Java和 Android 中使用 AES 進(jìn)行對稱加密” 的續(xù)篇,在這篇文章中我總結(jié)了關(guān)于 AES 最為重要的事情并演示了如何通過 AES-GCM 來使用它。在閱讀本文并深入下一個主題之前,我強(qiáng)烈建議你閱讀它,因?yàn)樗忉屃俗钪匾幕A(chǔ)知識。
本文討論了以下可能發(fā)生的情況:你不能通過類似 Galois/Counter Mode (GCM) 的認(rèn)證加密模式來使用高級加密標(biāo)準(zhǔn)(AES)?你當(dāng)前使用的平臺不支持它,或者你必須兼容老版本或其它第三方協(xié)議?無論你放棄 GCM 的原因是什么,你都不應(yīng)該放棄它所具有的安全屬性:
保密性:沒有密鑰的人無法閱讀該消息
完整性:沒有人會修改消息內(nèi)容
真實(shí)性:可以對消息的發(fā)送者進(jìn)行驗(yàn)證
選擇非認(rèn)證加密,比如塊模式密碼分組鏈接(CBC),不幸的是,由于具備很好的延展性,它缺少后兩個安全屬性。如何解決這個問題?正如我在上一篇文章中所說的那樣,一種可能的解決方案是將加密原語組合在一起以包含加密驗(yàn)證碼(MAC)。
加密驗(yàn)證碼(MAC)那么什么是 MAC,我們?yōu)槭裁匆褂盟??MAC 類似于散列函數(shù),這意味著它將消息作為輸入并生成一個所謂的簡短標(biāo)記。為了確保并非任何人都可以為任意消息創(chuàng)建標(biāo)記,MAC 函數(shù)需要一個密鑰來進(jìn)行計(jì)算。與使用非對稱加密的簽名相比,MAC 可使用相同的密鑰來進(jìn)行標(biāo)記生成和認(rèn)證。
例如,如果雙方安全地交換了 MAC 密鑰,并且每條消息都附加了認(rèn)證標(biāo)記,那么它們都可以檢查消息是否是由另一方創(chuàng)建的,并且在傳輸過程中沒有被更改。攻擊者需要保密的 MAC 密鑰來偽造身份進(jìn)行標(biāo)記驗(yàn)證。
最廣泛使用的 MAC 類型之一是 散列消息密鑰驗(yàn)證碼(HMAC),它包含一個哈希加密函數(shù),該函數(shù)通常是 SHA256。由于我不會詳細(xì)介紹其算法,因此我建議你閱讀相關(guān) RFC。當(dāng)然還有如 CBC-MAC 等其他可用于對稱加密的類型。幾乎所有的加密框架都至少包含一個 HMAC 實(shí)現(xiàn),包括通過 Mac 實(shí)現(xiàn)的 JCA/JCE。
使用加密的 MAC:架構(gòu)那么正確應(yīng)用 MAC 的方法是什么呢?根據(jù)安全研究院 Hugo Krawcyzk 的說法,這里有三種基本選項(xiàng):
MAC-then-Encrypt:基于明文生成 MAC,然后將其追加到明文中后再進(jìn)行加密(在 SSL 中使用)
Encrypt-then-MAC:基于密文和初始向量生成 MAC,然后將其追加到密文中(在 IPsec 中使用)
Encrypt-and-MAC: 基于明文生成 MAC、然后將其追加到密文中(在 SSH 中使用)
每一個選項(xiàng)都有它自己的屬性,我建議你通過這篇文章來獲取每個選項(xiàng)的完整參數(shù)??偠灾?,大部分 研究員 推薦使用 Encrypt-then-MAC(EtM)。由于 MAC 可以防止不正確消息的解密,它可以防止選擇密文攻擊。此外也由于 MAC 在密文中運(yùn)行,它不能泄漏有關(guān)明文的任何信息。然而它的缺點(diǎn)是,因?yàn)?IV 和標(biāo)記中必須包含可能的協(xié)議/算法版本或類型,因此實(shí)施起來稍微有些困難。重要的是在驗(yàn)證 MAC 之前永遠(yuǎn)不要進(jìn)行任何加密操作,否則你可能受到 padding-oracle 攻擊(Moxie 稱之為末日原則)。
Encrypt-then-Mac 架構(gòu)
附錄:CGM 和 Encrypt-then-MAC 通常情況下它們的安全強(qiáng)度可能類似,CGM 有以下優(yōu)點(diǎn):
簡單易用而不易出錯
更快,因?yàn)樗恍枰淮瓮ㄟ^整個信息
它的缺點(diǎn)是只能允許 96 位初始向量(對于 128 位),HMAC 理論上比 GCM 的內(nèi)部 MAC 算法 GHASH(128 位標(biāo)記大小對 256 位及以上)更強(qiáng)。GCM 無法進(jìn)行 IV + 密鑰重用。相關(guān)詳細(xì)討論,請查閱此處。
使用加密的 MAC:驗(yàn)證密鑰我們必須解決的最后一個問題是:我們應(yīng)該從哪里獲得用于 MAC 計(jì)算的密鑰?如果使用的是強(qiáng)密鑰(即足夠隨機(jī)且可以安全地切換),那么使用與加密相同的密鑰(當(dāng)使用 HMAC 時)似乎沒有已知問題。但最佳實(shí)踐是使用密鑰派生函數(shù)(KDF)派生出 2 個子密鑰以防范未來可能發(fā)現(xiàn)的任何問題。這可以像計(jì)算主密鑰上的 SHA256 并將其拆分為兩個 16 字節(jié)塊一樣簡單。 但是我更喜歡標(biāo)準(zhǔn)化的協(xié)議,比如基于 HMAC 的 Extract-and-Expand 密鑰派生函數(shù),它直接支持此場景而不需要字節(jié)調(diào)整。
2 個子密鑰的派生
理論已經(jīng)足夠了,現(xiàn)在讓我們開始編碼!在接下來的例子中,我將使用 AES-CBC,這是一個看似保守的決定。這樣做的原因是,應(yīng)該保證幾乎每個 JRE 和 Android 版本都可以使用它。如前所述,我們將使用帶有 HMAC 的 Encrypt-then-Mac 方案。這里唯一的外部依賴是 HKDF。這段代碼基本上是我在上一篇文章中描述的 GCM 示例的一個映射。
加密簡單起見,我們使用隨機(jī)生成的 128 位密鑰。當(dāng)你傳遞 128、192 或 256 位長度的密鑰時,Java 將自動選擇正確的模式。但請注意,256 位加密通常需要在 JRE 中安裝 無政策限制權(quán)限文件(OpenJDK 和 Android 無需安裝)。如果你不確定要使用的密鑰大小,請?jiān)谖业纳弦黄恼轮虚喿x關(guān)于該主題的相關(guān)段落。
SecureRandom secureRandom = new SecureRandom(); byte[] key = new byte[16]; secureRandom.nextBytes(key);
然后我們需要創(chuàng)建我們的初始化向量。對于 CBC,應(yīng)該使用 16 個字節(jié)長的初始向量(IV)。請注意,始終使用像 SecureRandom 這樣的強(qiáng)偽隨機(jī)數(shù)生成器(PRNG)。
byte[] iv = new byte[16]; secureRandom.nextBytes(iv);
重用 IV 不像 GCM 那樣具有災(zāi)難性,但最好還是避免使用。在這里可以看到可能的攻擊。
下一步,我們將派生出加密和身份驗(yàn)證所需的 2 個子密鑰。我們將在配置 HMAC-SHA256(使用此庫)中使用 HKDF,由于它使用起來簡單直接。我們使用 HKDF 中的 info 參數(shù)來生成兩個 16 字節(jié)子密鑰,從而對它們進(jìn)行區(qū)分。
// 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 字節(jié)塊大小的信息。由于對使用的填充方案似乎沒有安全隱患,我們選擇了支持最廣泛的:PKCS#7。
注意: 由于歷史原因,我們必須將密碼套件設(shè)置為 PKCS5。除了被定義為了不同的塊尺寸,兩者幾乎完全相同;通常情況下 PKCS#5 與 AES 并不兼容,但由于定義可追溯到使用了 8 字節(jié)塊的 3DES,我們堅(jiān)持使用它。如果你的 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);
接下來,我們需要準(zhǔn)備 MAC 并添加主要數(shù)據(jù)來進(jìn)行身份驗(yàn)證。
SecretKey macKey = new SecretKeySpec(authKey, "HmacSHA256"); Mac hmac = Mac.getInstance("HmacSHA256"); hmac.init(macKey); hmac.update(iv); hmac.update(cipherText);
如果你想要驗(yàn)證其他元數(shù)據(jù)(比如協(xié)議版本),你還可以將其添加到 mac 生成過程中。這與將關(guān)聯(lián)數(shù)據(jù)添加到經(jīng)過身份驗(yàn)證的加密算法的概念相同。
if (associatedData != null) { hmac.update(associatedData); }
然后計(jì)算 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();
這基本上就是加密。將構(gòu)建消息、IV、IV 的長度以及 mac 的長度、mac 和加密數(shù)據(jù)附加到單個字節(jié)數(shù)組。
如果你需要字符串表示,可以選用 Base64 對其進(jìn)行編碼。Android 中有該編碼的標(biāo)準(zhǔn)實(shí)現(xiàn),JDK 僅從版本 8 開始支持(如果可能,我將避免使用 Apache Commons Codec,因?yàn)樗苈覍?shí)現(xiàn)混亂)。
由于 Java 是一種自動內(nèi)存管理語言,因此最佳做法是盡可能快地從內(nèi)存中擦除加密密鑰或 IV 等敏感數(shù)據(jù)。我們無法保證以下內(nèi)容能夠按照預(yù)期工作,但在大多數(shù)情況下應(yīng)該如此:
Arrays.fill(authKey, (byte) 0); Arrays.fill(encKey, (byte) 0);
注意不要覆蓋還在其他地方使用的數(shù)據(jù)。
解密解密和反向加密類似:首先解構(gòu)消息。
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);
仔細(xì)驗(yàn)證輸入?yún)?shù)以防止拒絕服務(wù)攻擊,如 IV 或 mac 長度,因?yàn)楣粽呖赡軙南嚓P(guān)值。
然后導(dǎo)出解密和身份驗(yàn)證所需的密鑰。
// 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);
在我們解密任何東西之前,我們將驗(yàn)證 MAC。首先我們像之前一樣計(jì)算 MAC;不要忘記之前添加的相關(guān)數(shù)據(jù)。
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 時,我們需要一個恒定的時間比較函數(shù)來避免旁道攻擊;閱讀此文了解為什么這很重要。幸運(yùn)的是我們可以使用 MessageDigest.isEquals()(舊的 bug 已在 Java 6u17 中修復(fù)):
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);
以上便是所有內(nèi)容,如果你想查看一個完整的例子,請查看我托管到 Github 中的一個使用 AES-CBC 的項(xiàng)目 Armadillo。如果你遇到了什么問題,也可以在 Gist 中找到這個確切的示例。
總結(jié)我們演示了使用密碼分組鏈接(CBC)的 AES 和使用 HMAC 的 Encrypt-then-MAC 架構(gòu)提供了我們希望從加密協(xié)議中看到的所有理想的安全屬性:保密性、完整性和真實(shí)性。
可以看出,僅僅使用了 GCM,協(xié)議就變得復(fù)雜了。但是,這些原語通常在所有 Java/Android 環(huán)境中都可用,因此它可能是你唯一的選擇。請考慮以下事項(xiàng):
使用 16 字節(jié)隨機(jī)初始化向量(使用強(qiáng) PRNG)
使用 128 位以上的 MAC 長度(HMAC-SHA256 輸出 256 位)
使用 Encrypt-then-Mac
使用 KDF 派生出 2 個子密鑰
解密之前進(jìn)行驗(yàn)證(末日原則)
通過使用恒定時間等于實(shí)現(xiàn)來防止定時攻擊
使用 128 位加密密鑰長度(你會沒事的?。?/p>
將所有內(nèi)容整合到一條消息中
參考資料:patrickfav/hkdf
patrickfav/armadillo
進(jìn)一步閱讀:最佳安全實(shí)踐:在 Java 和 Android 中使用 AES 進(jìn)行對稱加密
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/72972.html
摘要:還有很多開發(fā)者沒有意識到的加密算法的問題。不要使用哈希函數(shù)做為對稱加密算法的簽名。開發(fā)者建議使用基于口令的加密算法時,生成密鑰時要加鹽,鹽的取值最好來自,并指定迭代次數(shù)。不要使用沒有消息認(rèn)證的加密算法加密消息,無法防重放。 本文作者:阿里移動安全@伊樵,@舟海 Android開發(fā)中,難免會遇到需要加解密一些數(shù)據(jù)內(nèi)容存到本地文件、或者通過網(wǎng)絡(luò)傳輸?shù)狡渌?wù)器和設(shè)備的問題,但并不是使用了加...
摘要:所謂對稱加密,就是加密和解密使用同一秘鑰,這也是這種加密算法最顯著的缺點(diǎn)之一。非對稱加密算法由于對稱加密在通信加密領(lǐng)域的缺陷,年和提出了非對稱加密的概念。非對稱加密,其主要缺點(diǎn)之一就是慢,適合加密少量數(shù)據(jù)。 1. 加密的目的 加密不同于密碼,加密是一個動作或者過程,其目的就是將一段明文信息(人類或機(jī)器可以直接讀懂的信息)變?yōu)橐欢慰瓷先]有任何意義的字符,必須通過事先約定的解密規(guī)則才能將...
閱讀 1613·2021-11-23 09:51
閱讀 1184·2019-08-30 13:57
閱讀 2264·2019-08-29 13:12
閱讀 2018·2019-08-26 13:57
閱讀 1202·2019-08-26 11:32
閱讀 981·2019-08-23 15:08
閱讀 708·2019-08-23 14:42
閱讀 3089·2019-08-23 11:41