使用JDK的密码流的加密怪癖(以及如何做)
在我們的日常工作中,我們經常遇到經常性的主題,即將數據(例如文件)從一個位置傳輸到另一個位置。 這聽起來像是一個非常簡單的任務,但讓我們通過聲明這些文件可能包含機密信息并可以通過非安全的通信渠道進行傳輸這一事實,使其變得更加困難。
首先想到的解決方案之一是使用加密算法。 由于文件可能真的很大,可能是數百兆或數十千兆字節,所以使用像AES這樣的對稱加密方案可能很有意義。 除了僅加密外,確保數據在傳輸過程中不被篡改也將是一件很棒的事情。 幸運的是,有一種叫做認證加密的東西,它同時為我們提供了機密性,完整性和真實性保證。 Galois /計數器模式 ( GCM )是最流行的模式之一,支持身份驗證加密 ,可以與AES一起使用。 這些想法使我們使用了足夠強大的加密方案AES256-GCM128 。
如果您使用的是JVM平臺,則應該感到幸運,因為Java密碼體系結構 ( JCA )支持AES和GCM 。 話雖這么說,讓我們看看我們能走多遠。
我們要做的第一件事是生成一個新的AES256密鑰。 與往常一樣, OWASP對于正確使用JCA / JCE API 提出了許多建議 。
final SecureRandom secureRandom = new SecureRandom(); ???????? final byte [] key = new byte [ 32 ]; secureRandom.nextBytes(key); final SecretKey secretKey = new SecretKeySpec(key, "AES" );另外,要初始化AES / GCM密碼,我們需要生成隨機初始化向量(或簡稱為IV)。 根據NIST的建議,其長度應為12個字節 (96位)。
對于IV,建議實現將支持范圍限制為96位,以提高互操作性,效率和設計的簡便性。 –
針對塊密碼模式的建議:伽羅瓦/計數器模式(GCM)和GMAC
所以我們在這里:
final byte [] iv = new byte [ 12 ]; secureRandom.nextBytes(iv);準備好AES密鑰和IV后,我們可以創建一個密碼實例并實際執行加密部分。 處理大文件假定依賴于流,因此我們將BufferedInputStream / BufferedOutputStream與CipherOutputStream結合使用進行加密。
public static void encrypt(SecretKey secretKey, byte [] iv, final File input, final File output) throws Throwable { final Cipher cipher = Cipher.getInstance( "AES/GCM/NoPadding" ); final GCMParameterSpec parameterSpec = new GCMParameterSpec( 128 , iv); cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec); try ( final BufferedInputStream in = new BufferedInputStream( new FileInputStream(input))) { try ( final BufferedOutputStream out = new BufferedOutputStream( new CipherOutputStream( new FileOutputStream(output), cipher))) { int length = 0 ; byte [] bytes = new byte [ 16 * 1024 ]; while ((length = in.read(bytes)) != - 1 ) { out.write(bytes, 0 , length); } } } }請注意,我們如何指定標簽大小為128位的 GCM密碼參數,并以加密模式對其進行初始化(在處理64Gb以上的文件時要注意一些GCM限制 )。 除了在解密模式下初始化密碼之外,解密部分沒有什么不同。
public static void decrypt(SecretKey secretKey, byte [] iv, final File input, final File output) throws Throwable { final Cipher cipher = Cipher.getInstance( "AES/GCM/NoPadding" ); final GCMParameterSpec parameterSpec = new GCMParameterSpec( 128 , iv); cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec); ????????try (BufferedInputStream in = new BufferedInputStream( new CipherInputStream( new FileInputStream(input), cipher))) { try (BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(output))) { int length = 0 ; byte [] bytes = new byte [ 16 * 1024 ]; ????????????????while ((length = in.read(bytes)) != - 1 ) { out.write(bytes, 0 , length); } } } }看來我們完成了,對吧? 不幸的是,并不是真的,對小文件進行加密和解密只需要花一點時間,但是處理或多或少的實際數據樣本卻會產生令人震驚的結果。
處理一個?42Mb文件通常需要8分鐘(您可能會猜到,文件越大,花費的時間就越長),快速分析顯示,大部分時間都是在解密數據時花費的(請注意,這絕不是基準,僅是測試)。 在這里 , 這里 , 這里和這里 ,尋找可能的罪魁禍首指出了JCA實現中AES / GCM和CipherInputStream / CipherOutputStream的長期問題清單。
那么還有哪些選擇呢? 似乎有可能犧牲CipherInputStream / CipherOutputStream ,重構實現以直接使用密碼,并使用JCA原語使加密/解密工作。 但是可以說,引入經過戰斗測試的BouncyCastle庫是更好的方法。
從實現的角度來看,解決方案看起來幾乎是相同的。 確實,盡管命名約定沒有改變,但以下代碼段中的CipherOutputStream / CipherInputStream來自BouncyCastle 。
public static void encrypt(SecretKey secretKey, byte [] iv, final File input, final File output) throws Throwable { final GCMBlockCipher cipher = new GCMBlockCipher( new AESEngine()); cipher.init( true , new AEADParameters( new KeyParameter(secretKey.getEncoded()), 128 , iv)); try (BufferedInputStream in = new BufferedInputStream( new FileInputStream(input))) { try (BufferedOutputStream out = new BufferedOutputStream( new CipherOutputStream( new FileOutputStream(output), cipher))) { int length = 0 ; byte [] bytes = new byte [ 16 * 1024 ]; while ((length = in.read(bytes)) != - 1 ) { out.write(bytes, 0 , length); } } } } public static void decrypt(SecretKey secretKey, byte [] iv, final File input, final File output) throws Throwable { final GCMBlockCipher cipher = new GCMBlockCipher( new AESEngine()); cipher.init( false , new AEADParameters( new KeyParameter(secretKey.getEncoded()), 128 , iv)); try (BufferedInputStream in = new BufferedInputStream( new CipherInputStream( new FileInputStream(input), cipher))) { try (BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream(output))) { int length = 0 ; byte [] bytes = new byte [ 16 * 1024 ]; ????????????????while ((length = in.read(bytes)) != - 1 ) { out.write(bytes, 0 , length); } } } }使用BouncyCastle加密原語重新運行之前的加密/解密測試會產生完全不同的畫面。
公平地說,JVM平臺上的文件加密/解密最初看起來像是一個已解決的問題,但事實證明它充滿了令人驚訝的發現。 盡管如此,由于BouncyCastle的存在 , JCA實施的一些缺陷得以有效,簡潔地解決。
請在Github上找到完整的資源。
翻譯自: https://www.javacodegeeks.com/2020/05/the-crypto-quirks-using-jdks-cipher-streams-and-what-to-do-about-that.html
總結
以上是生活随笔為你收集整理的使用JDK的密码流的加密怪癖(以及如何做)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 十大巅峰网游小说排行榜完结(10本完结高
- 下一篇: 吉利丁粉怎么用 吉利丁粉的注意事项