日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

[译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密

發(fā)布時(shí)間:2025/4/16 Android 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文地址:Security Best Practices: Symmetric Encryption with AES in Java and Android

最佳安全實(shí)踐:在 Java 和 Android 中使用 AES 進(jìn)行對(duì)稱加密

我將在本文中為大家介紹高級(jí)加密標(biāo)準(zhǔn)(AES),常見塊模式,為什么需要填充和初始化向量以及如何保護(hù)數(shù)據(jù)不被篡改。最后,我將為大家展示如何使用 Java 輕松實(shí)現(xiàn)此功能,從而避免大多數(shù)安全問題。

為什么每一個(gè)軟件工程師都需要知道 AES

AES,又稱 Rijndael 加密算法,在 2000 年被 NIST 選中以用來替換過時(shí)的數(shù)據(jù)加密標(biāo)準(zhǔn)(DES)。AES 是一種分組密碼,這意味著加密發(fā)生在固定長度的比特組上。在我們的例子中,算法定義塊長度為 128 位。AES 支持 128,192 和 256 位的密鑰長度。

每個(gè)塊都經(jīng)歷多輪轉(zhuǎn)換。我將在這里省略算法的細(xì)節(jié),對(duì)算法感興趣的讀者可以參考維基百科中有關(guān) AES 的文章。這里需要指出的是塊大小受轉(zhuǎn)換輪次的重復(fù)次數(shù)影響(128 位密鑰是 10 個(gè)周期,256 位為 14 個(gè)周期),而密鑰長度并不影響它的大小。

一直到 2009 年 5 月,唯一一次成功發(fā)布,針對(duì)完整 AES 的攻擊是對(duì)某些特定實(shí)現(xiàn)的旁道攻擊。(資源)

想要加密多個(gè)塊?

AES 只會(huì)加密 128 位數(shù)據(jù),如果我們想要加密整個(gè)消息,我們需要選擇一種塊模式,利用該模式可以將多個(gè)塊加密為一個(gè)密文。最簡單的塊模式是電子密碼本或 ECB。它將在每個(gè)區(qū)塊中使用相同的未更改的鍵:

圖片來自維基百科

這將是特別糟糕的,因?yàn)橄嗤拿魑臅?huì)被加密成相同的密文。

使用 ECB 塊模式加密的圖片顯示原始圖案(自己嘗試一下)

請(qǐng)記住,除非你只加密小于 128 位的數(shù)據(jù),否則永遠(yuǎn)不要選擇該模式。不幸的是,它仍然被經(jīng)常誤用,因?yàn)樗恍枰闾峁┏跏枷蛄?#xff08;稍后會(huì)詳細(xì)介紹),因此開發(fā)人員似乎更容易處理。

必須使用塊模式處理的一種情況:如果最后一個(gè)塊的大小不足 128 位會(huì)發(fā)生什么?這就是填充發(fā)揮作用的地方,即填充塊的缺失位。最簡單的方式是用零填充缺失位。在 AES 中選擇填充幾乎沒有任何安全隱患。

密碼分組鏈接(CBC)

那么有什么方案可以替代 ECB 呢?例如 CBC,在該模式中,用當(dāng)前的明文塊和前一個(gè)密文塊進(jìn)行異或。在該方法中,每個(gè)密文塊都依賴于它前面的所有明文塊。使用與之前相同的圖片,加密結(jié)果將是與噪聲數(shù)據(jù)無法區(qū)分的隨機(jī)數(shù)據(jù):

使用 CBC 塊模式加密的圖片看起來是隨機(jī)的

那如何處理第一個(gè)塊呢?最簡單的方法是使用一個(gè)完整的填充塊(比如用零填充),但這樣每次加密相同密鑰和明文都會(huì)產(chǎn)生一樣的密文。此外,如果你為不同的明文重用相同的密鑰,那么恢復(fù)密鑰將會(huì)更加容易。更好的方法是使用隨機(jī)初始化向量(IV)。這對(duì)于隨機(jī)數(shù)據(jù)來說只是一個(gè)奇特的詞,大約是一個(gè)塊(128 位)大小。將它想象成一個(gè)加密的 salt,也就是說,IV 是可以公開的,隨機(jī)的且只能使用一次。但請(qǐng)注意,因?yàn)?CBC 將密文異或而不是前一個(gè)明文的明文,因此 IV 不僅僅會(huì)阻止第一個(gè)塊的解密。

在傳輸或保持?jǐn)?shù)據(jù)時(shí),通常只將 IV 添加到實(shí)際的密碼消息中。如果你對(duì)如何正確使用 AES-CBC 感興趣,請(qǐng)閱讀本系列的第 2 部分。

記數(shù)模式(CTR)

另外一種選擇是使用 CTR 模式。這種模式很有意思,因?yàn)樗鼤?huì)將密碼轉(zhuǎn)換為密碼流,這意味著不需要進(jìn)行填充。在其基本形式中,所有塊的編號(hào)為 0 到 n。現(xiàn)在每個(gè)塊都將使用密鑰、IV(此處也稱為 nonce)和計(jì)數(shù)器的值來進(jìn)行加密。

圖片來自維基百科

與 CBC 不同,它的優(yōu)點(diǎn)是可以進(jìn)行并行加密并且所有塊都依賴于 IV,而不僅僅是第一個(gè)。一個(gè)很嚴(yán)重的警告是,IV 永遠(yuǎn)不能被相同的密鑰重用,因?yàn)楣粽呖梢詮闹休p松計(jì)算出你所使用的密鑰。

我可以確保沒有人能夠修改我的消息嗎?

事實(shí):加密不會(huì)自動(dòng)防止數(shù)據(jù)修改。這實(shí)際上是一種非常常見的攻擊。有關(guān)該問題更全面的討論,請(qǐng)閱讀此文。

那么我們又能做些什么呢?我們只需將加密驗(yàn)證碼(MAC)添加到加密郵件中。MAC 類似于數(shù)字簽名,不同之處在于驗(yàn)證和驗(yàn)證密鑰實(shí)際上是相同的。這種方法有不同的變化,大多數(shù)研究人員推薦的模式叫做 Encrypt-then-Mac 。也就是說,在加密之后,在密文上計(jì)算并附加 MAC。你通常會(huì)使用基于哈希的消息身份驗(yàn)證代碼(HMAC)作為 MAC 的類型。

現(xiàn)在它開始變得復(fù)雜了。為了完整性/真實(shí)性我們必須選擇 MAC 算法,選擇加密標(biāo)簽?zāi)J?#xff0c;計(jì)算 mac 并附加它。因?yàn)檎麄€(gè)消息必須處理兩次,所以該操作運(yùn)行速度緩慢。反向操作必須與前面一致,但用于解密和驗(yàn)證。

使用 GCM 進(jìn)行認(rèn)證加密

如果有模式可以處理所有的身份驗(yàn)證,那不是很好嗎?幸運(yùn)的是有一種稱為認(rèn)證加密的加密方式,它同時(shí)為數(shù)據(jù)的機(jī)密性、完整性和真實(shí)性提供了保證。支持此功能最流行的塊模式之一為 Galois/Counter Mode or GCM(比如它可以使用 TLS v1.2 中的密碼組件)。

GCM 基于 CTR 模式,它還在加密期間順序計(jì)算身份驗(yàn)證標(biāo)記。然后該標(biāo)記通常會(huì)附加到密文中。它的大小是一個(gè)重要的安全屬性,因此它的長度至少是 128 位。

它還可以驗(yàn)證未包括在明文中的附加信息。該數(shù)據(jù)稱為關(guān)聯(lián)數(shù)據(jù)。這為什么有用呢?例如,加密數(shù)據(jù)具有元屬性,即用于檢查是否必須重新加載內(nèi)容的創(chuàng)建日期。攻擊者可以輕松更改創(chuàng)建日期,但如果將其添加為關(guān)聯(lián)數(shù)據(jù), CGM 將驗(yàn)證此信息并識(shí)別出更改。

激烈的討論:使用多長的密鑰?

直覺會(huì)說:越大越好 - 很明顯,強(qiáng)制 256 位隨機(jī)值比 128 位更難。根據(jù)我們目前的理解,強(qiáng)制通過 128 位長字節(jié)的所有值都需要天文數(shù)量的能量,對(duì)于任何在合理時(shí)間內(nèi)的人來說都是不現(xiàn)實(shí)的(看著你,NSA)。因此,決定基本上在無限和無限時(shí)間 212? 之間。

AES 實(shí)際上有三種不同的密鑰大小,因?yàn)樗贿x為美國聯(lián)邦政府的標(biāo)注加密算法以用于聯(lián)邦政府「包括軍方」控制的各個(gè)領(lǐng)域。(...)因此,精明的軍事首腦提出了應(yīng)該有三個(gè)“安全級(jí)別”的想法,以便使用重量級(jí)方法加密最重要的秘密,但較低價(jià)值的數(shù)據(jù)可以用更實(shí)用,更輕量級(jí)的算法加密。(...)因此,NIST 決定正式遵守規(guī)定(要求三個(gè)關(guān)鍵尺寸),但也要做前瞻性的事(最低級(jí)別必須通過可遇見的技術(shù)不可攻破)(來源)。

論點(diǎn)如下:AES 加密消息可能不會(huì)被暴力破壞密鑰破壞,而是通過其他較便宜的攻擊(當(dāng)前未知)。這些攻擊對(duì)于 128 位密鑰模式和 256 位模式一樣有害,因此在這種情況下選擇更大的密鑰大小也無濟(jì)于事。

所以基本上 128 位密鑰對(duì)于大多數(shù)用例來說都足夠安全,但量子計(jì)算機(jī)保護(hù)除外。同樣使用比 256 位更快的 128 位加密。128 位密鑰的密鑰強(qiáng)度似乎可以更好的防止相關(guān)密鑰攻擊(但這與大多數(shù)實(shí)際用途無關(guān))。

旁注:旁道攻擊

旁道攻擊是利用特定于某些實(shí)現(xiàn)的問題的攻擊。加密密碼方案本身不能有效地保護(hù)它們。簡單的 AES 實(shí)現(xiàn)可能容易發(fā)生計(jì)時(shí),緩存攻擊及其他攻擊。

作為一個(gè)非常基本的例子:一個(gè)容易發(fā)生定時(shí)攻擊的簡單算法是一個(gè)比較兩個(gè)秘密字節(jié)數(shù)組的 equals() 方法。如果 equals() 有一個(gè)快速返回,意味著在第一對(duì)不匹配的字節(jié)結(jié)束循環(huán)之后,攻擊者可以測量 equals() 完成所需要的時(shí)間,并且可以一個(gè)字節(jié)一個(gè)字節(jié)的猜測,直到全部匹配為止。

使用快速返回可能受到定時(shí)攻擊的代碼

在這種情況下,一個(gè)修復(fù)方法是使用恒定時(shí)間等于。請(qǐng)注意,在類似于 JVM 等解釋語言中編寫常量時(shí)間代碼往往并非易事。

針對(duì) AES 的定時(shí)和緩存攻擊不僅僅是理論上的,甚至可以通過網(wǎng)絡(luò)進(jìn)行實(shí)施。雖然防止旁道攻擊主要是實(shí)施加密原語的開發(fā)人員關(guān)注的問題,但了解編碼實(shí)踐可能對(duì)整個(gè)例程的安全性有害是明智的。最一般的主題是,可觀察到的與時(shí)間相關(guān)的行為不應(yīng)該依賴于私密數(shù)據(jù)。此外,你應(yīng)該仔細(xì)考慮要選擇的實(shí)現(xiàn)方案。例如,使用帶有 OpenJDK 的 Java 8+ 和默認(rèn)的 JCA 提供程序應(yīng)該在內(nèi)部使用 Intel 的 AES-NI 指令集,該指令集通過恒定時(shí)間和在硬件中實(shí)現(xiàn)(同時(shí)仍具有良好的性能)來防止大多數(shù)時(shí)序和緩存攻擊。Android 使用它的 AndroidOpenSSLProvider,內(nèi)部可能會(huì)在硬件中使用 AES(ARM TrustZone),具體取決于 SoC。但我不相信它具有與 Intels pedant 相同的防護(hù)。但即使你改進(jìn)硬件,也可以使用其他攻擊向量,例如功率分析。存在專門用于防止大多數(shù)這些問題的專用硬件,即硬件安全模塊(HSM)。不幸的是,這些設(shè)備的成本通常高達(dá)數(shù)千美元(有趣的是:你的基于芯片的信用卡也是 HSM)。

在 Java 和 Android 中實(shí)現(xiàn) AES-GCM

最后它變得實(shí)用了。現(xiàn)在 Java 擁有我們需要的所有工具,但加密 API 可能不是最直接的。細(xì)心的開發(fā)人員也可能不確定要使用的長度/大小/默認(rèn)值。注意:如果沒有說明,所有內(nèi)容都同樣適用于 Java 和 Android

在我們的示例中,我們使用隨機(jī)生成的 128 位密鑰。傳遞 192 和 256 位長度的密鑰時(shí),Java 會(huì)自動(dòng)選擇正確的模式。但請(qǐng)注意,256 位加密通常需要在 JRE 中安裝 無政策限制權(quán)限文件(Android 中無需安裝)。

SecureRandom secureRandom = new SecureRandom(); byte[] key = new byte[16]; secureRandom.nextBytes(key); SecretKey secretKey = SecretKeySpec(key, “AES”); 復(fù)制代碼

然后我們必須創(chuàng)建我們的初始化向量。對(duì)于 CGM,NIST 建議使用 12 字節(jié)(非16字節(jié)!)隨機(jī)字?jǐn)?shù)組,因?yàn)樗?#xff0c;更安全。請(qǐng)注意始終使用像 SecureRandom 這樣的強(qiáng)偽隨機(jī)數(shù)生成器(RNG)。

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY secureRandom.nextBytes(iv); 復(fù)制代碼

然后初始化你的密碼。AES-GCM 模式應(yīng)該適用于大多數(shù)現(xiàn)代 JRE 和 Android v2.3 以上版本(雖然僅在 SDK 21+ 上可以完全正常運(yùn)行)。如果碰巧不可用,請(qǐng)安裝像 BouncyCastle 這樣的自定義加密提供程序,但通常首選默認(rèn)提供程序。我們選擇 128 位大小的認(rèn)證標(biāo)簽。

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); 復(fù)制代碼

如果需要,添加可選的關(guān)聯(lián)數(shù)據(jù)(例如元數(shù)據(jù))

if (associatedData != null) {cipher.updateAAD(associatedData); } 復(fù)制代碼

加密;如果你正在加密大塊數(shù)據(jù),請(qǐng)研究 CipherInputStream,這樣整個(gè)內(nèi)容就無需加載到堆中。

byte[] cipherText = cipher.doFinal(plainText); 復(fù)制代碼

現(xiàn)在將所有內(nèi)容連接到一條消息。

ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length); byteBuffer.putInt(iv.length); byteBuffer.put(iv); byteBuffer.put(cipherText); byte[] cipherMessage = byteBuffer.array(); 復(fù)制代碼

如果你需要字符串表示,可選用 Base64 來編碼它。 Android 中有該編碼的標(biāo)準(zhǔn)實(shí)現(xiàn),JDK 僅從版本 8 開始(如果可能,我會(huì)避免使用 Apache Commons Codec,因?yàn)樗苈覍?shí)現(xiàn)混亂)。

這基本上就是加密。為了構(gòu)造消息,IV 長度,IV,加密數(shù)據(jù)和認(rèn)證標(biāo)簽被附加到單個(gè)字節(jié)數(shù)組。(在 Java 中,身份驗(yàn)證標(biāo)記會(huì)自動(dòng)附加到消息中,無法使用標(biāo)準(zhǔn)加密 API 自行處理)。

最佳事件是盡可能快地從內(nèi)存中擦除加密密鑰或 IV 等敏感數(shù)據(jù)。由于 Java 是一種具有自動(dòng)內(nèi)存管理的語言,因此我們無法保證以下內(nèi)容能夠預(yù)期工作,但在大多數(shù)情況下應(yīng)該如此:

Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros 復(fù)制代碼

注意不要覆蓋仍在其他地方使用的數(shù)據(jù)。

現(xiàn)在到解密部分,它的工作原理類似加密,首先解構(gòu)消息:

ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage); int ivLength = byteBuffer.getInt(); if(ivLength < 12 || ivLength >= 16) { // check input parameterthrow new IllegalArgumentException("invalid iv length"); } byte[] iv = new byte[ivLength]; byteBuffer.get(iv); byte[] cipherText = new byte[byteBuffer.remaining()]; byteBuffer.get(cipherText); 復(fù)制代碼

小心驗(yàn)證輸入?yún)?shù),比如 IV 長度,因?yàn)楣粽呖赡軙?huì)將長度值更改為如 231,它會(huì)分配 2 GiB內(nèi)存并可能很快填滿你的堆,使得拒絕服務(wù)攻擊變得微不足道。

初始化密碼并添加可選的關(guān)聯(lián)數(shù)據(jù)并解密:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv)); if (associatedData != null) {cipher.updateAAD(associatedData); } byte[] plainText= cipher.doFinal(cipherText); 復(fù)制代碼

以上便是所有內(nèi)容,如果你想查看一個(gè)完整的例子,請(qǐng)查看我托管到 Github 中的一個(gè)使用 AES-GCM 的項(xiàng)目 Armadillo。

總結(jié)

我們需要三個(gè)屬性來保護(hù)我們的數(shù)據(jù):

  • 保密性:防止竊聽者發(fā)現(xiàn)明文消息或有關(guān)明文消息的信息的能力。
  • 完整性:防止攻擊者在合法用戶未注意的情況下修改消息的能力。
  • 真實(shí)性:證明消息是由特定方生成并防止偽造新消息的能力。 這通常通過消息驗(yàn)證代碼(MAC)提供。注意,真實(shí)性也意味著完整性。

具有 Galois/Counter(GCM)塊模式的 AES 提供所有這些屬性,并且相當(dāng)容易使用,并且在大多數(shù) Java/Android環(huán)境中都可用。 請(qǐng)考慮以下事項(xiàng):

  • 使用永遠(yuǎn)不會(huì)與相同密鑰一起使用的 12 字節(jié)初始化向量(使用像 SecureRandom 這樣的強(qiáng)偽隨機(jī)數(shù)生成器)。
  • 使用 128 位身份驗(yàn)證標(biāo)記長度。
  • 使用 128 位密鑰長度(你會(huì)沒事的!)。
  • 將所有內(nèi)容整合到一條消息中。

進(jìn)一步閱讀:

最佳安全實(shí)踐:在 Java 和 Android 中使用 AES 進(jìn)行對(duì)稱加密:第2部分:AES-CBC + HMAC。

參考資料:

  • patrickfav/armadillo
  • patrickfav/bytes-java

總結(jié)

以上是生活随笔為你收集整理的[译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。