學習Java AES加解密字元串和文件方法,然後寫個簡單工具類

来源:https://www.cnblogs.com/yy299/archive/2022/10/25/16825269.html
-Advertisement-
Play Games

Reference Core Java Volume Ⅱ 10th Edition 1 對稱加密 “Java密碼擴展”包含了一個Cipher,它是所有密碼演算法的超類。通過getInstance(algorithmName)可以獲得一個密碼對象。 cipher.init(mode, key);模式有以 ...


Reference Core Java Volume Ⅱ 10th Edition

1 對稱加密

“Java密碼擴展”包含了一個Cipher,它是所有密碼演算法的超類。通過getInstance(algorithmName)可以獲得一個密碼對象。

cipher.init(mode, key);模式有以下四種:
Cipher.ENCRYPT;
Cipher.DECRYPT;
Cipher.WRAP_MODE和Cipher.UNWRAP_MODE會用一個秘鑰對另一個秘鑰進行加密

// 可以一直調用cipher.update(),進行加密
int blockSize = cipher.getBlockSize();
byte[] inBytes = new byte[blockSize];
... // read inBytes
int outputSize = cipher.getOutputSize(blockSize);
byte[] outBytes = new byte[outputSize];
int outLength = cipher.update(inBytes, 0, outputSize, outBytes);
... // write outBytes

//完成上述操作後,最後必須調用doFinal返回加密後的數據

//如果最後一個輸入數據塊小於blockSize:
outBytes = cipher.doFinal(inBytes, 0, inLength);

//如果所有數據都已加密:
outBytes = cipher.doFinal();
//如果輸入數組長度不夠blockSize,update方法返回的數組為空,長度為0

doFinal調用時必要的,因為它會對最後的數據塊進行填充,常用填充方案是RSA Security公司在公共秘鑰密碼標準#5(Public Key Cryptography Standard, PKCS)中描述的方案
該方案最後一個數據塊不是全用0填充,而是等於填充位元組數量的值最為填充值

2 秘鑰生成

我們需要確保秘鑰生成時隨機的。這需要遵循下麵的步驟:

1). 為加密演算法獲取KeyGenerator
2). 用隨機源來初始化秘鑰發生器。如果密碼長度是可變的,還需要指定期望的密碼塊長度
3). 調用generateKey方法
例如:

KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
keygen.init(random);
SecretKey key = keygen.generateKey();

或者可以從一組固定的原生數據(也許是口令或者隨機鍵產生的)中生成一個密鑰,這時可以使用SecretKeyFactory
byte[] keyData = ...; // 16 byte for AES
SecretKey key = new SecretKeySpec(keyData, "AES");

如果要生成密鑰,必須使用“真正的隨機”數。例如,在常見的Random中的常規的隨即發生器。是根據當前的日期和時間來產生的,因此它不夠隨機。假設電腦時鐘可以精確到1/10秒,那麼每天最多存在864000個種子。如果攻擊者知道密鑰的日期(通常可以由消息日期或證書有效日期推算出來),那麼就可以很容易的產生那一天的所有可能的種子。
SecureRandom類產生的隨機數,遠比由Random類產生的那些隨機數字安全得多。也可以由我們提供種子。由setSeed(byre[] b)方法傳遞給它。
如果沒有隨機數發生器提供種子,那麼它將通過啟動線程,是他們睡眠,然後測量他們被喚醒的準確時間,一次來計算自己的20個位元組的種子

!註意: 這個演算法仍然被人為是安全的。而且,在過去,依靠對諸如硬碟訪問之間的類的其他電腦組件進行計時的演算法, 後來也被證明不也是完全隨機的。

3 密碼流

密碼流能夠透明地調用update和doFinal方法,所以非常方便,源碼也很簡單。

API javax.crypto.CipherInputStream 1.4

  • CipherInputStream(InputStream in, Cipher cipher)
    構建一個輸入流,讀取in中的數據,並且使用指定的密碼對數據進行解密和加密。
  • int read()
  • int read(byte[] b, int off, int len)
    讀取輸入流中的數據,該數據會被自動解密和加密

API javax.crypto.CipherOutputStream 1.4

  • CipherOutputSream(OutputStream out, Cipher cipher)
    構建一個輸出流,以便將數據寫入out,並且使用指定的秘密對數據進行加密和解密。
  • void write(int ch)
  • void write(byte b, int off, int len)
    將數據寫入輸出流,該數據會被自動加密和解密。
  • void flush()
    刷新密碼緩衝區,如果需要的化,執行填充操作。

CipherInputStream.read讀取完數據後,如果不夠一個數據塊,會自動調用doFinal方法填充後返回

CipherOutputStream.close方法如果發現,還有沒寫完的數據,會調用doFinal方法返回數據然後輸出

4 工具類

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import java.util.Objects;

/**
 * 加解密字元串、文件工具類
 * @author YYang 13047
 * @version 2022/10/25 12:10
 */
public class AESUtils {

  public static final String AES = "AES";
  //PKCS: Public Key Cryptographic Standard
  public static final String AES_ECB = "AES/ECB/PKCS5Padding";
  public static final String AES_CBC = "AES/CBC/PKCS5Padding";
  public static final String AES_CFB = "AES/CFB/PKCS5Padding";
  public static final int KEY_SIZE = 128;
  public static final int BUFFER_SIZE = 512;

  public static String encodeToString(byte[] unEncoded) {
    return Base64.getEncoder().encodeToString(unEncoded);
  }

  public static byte[] decode(String encoded) {
    return Base64.getDecoder().decode(encoded);
  }

  public static String generateAESKey() throws NoSuchAlgorithmException {
    return generateAESKey(KEY_SIZE, null);
  }

  /**
   * @param keySize keySize must be equal to 128, 192 or 256;
   * @param seed    隨機數種子
   * @see #generateAESKey0()
   */
  public static String generateAESKey(int keySize, String seed) throws NoSuchAlgorithmException {
    KeyGenerator keyGen = KeyGenerator.getInstance(AES);
    SecureRandom random = (seed == null || seed.length() == 0) ?
            new SecureRandom() : new SecureRandom(seed.getBytes(StandardCharsets.UTF_8));
    //如果不初始化,SunJCE預設使用new SecureRandom()
    keyGen.init(keySize, random);
    SecretKey secretKey = keyGen.generateKey();
    return encodeToString(secretKey.getEncoded());
  }

  /**
   * @return 密鑰,不初始化,使用預設的
   */
  public static String generateAESKey0() throws NoSuchAlgorithmException {
    return encodeToString(KeyGenerator.getInstance(AES).generateKey().getEncoded());
  }

  /**
   * @param algorithm 演算法名
   * @return 返回一個當前演算法BlockSize大小的隨機數組,然後Base64轉碼
   * @see #generateAESIv()
   */
  public static String generateAESIv(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
    Cipher cipher = Cipher.getInstance(algorithm);
    int blockSize = cipher.getBlockSize();
    byte[] ivByte = new byte[blockSize];
    new SecureRandom().nextBytes(ivByte);
    return encodeToString(ivByte);
  }

  public static String generateAESIv() {
    //AES blockSize == 16
    byte[] bytes = new byte[16];
    new SecureRandom().nextBytes(bytes);
    return encodeToString(bytes);
  }

  public static AlgorithmParameterSpec getIv(String ivStr) {
    if (ivStr == null || ivStr.length() < 1) return null;
    return new IvParameterSpec(decode(ivStr));
  }

  /**
   * @return 指定秘鑰和演算法,返回Key對象
   */
  public static Key getKey(String keyStr, String algorithm) {
    return new SecretKeySpec(decode(keyStr), algorithm);
  }

  public static Cipher initCipher(String algorithm, int cipherMode, Key key, AlgorithmParameterSpec param)
          throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
    Cipher cipher = Cipher.getInstance(algorithm);
    if (param == null) {
      cipher.init(cipherMode, key);
    } else {
      cipher.init(cipherMode, key, param);
    }
    return cipher;
  }

  public static String encrypt(String algorithm, String keyStr, String ivStr, String unencryptedStr) throws Exception {
    return encrypt(algorithm, keyStr, ivStr, unencryptedStr, StandardCharsets.UTF_8);
  }

  public static String encrypt(String algorithm, String keyStr, String ivStr, String unencryptedStr, Charset charset) throws Exception {
    Cipher cipher = initCipher(algorithm, Cipher.ENCRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
    byte[] encrypted = cipher.doFinal(unencryptedStr.getBytes(charset));
    return encodeToString(encrypted);
  }

  public static String decrypt(String algorithm, String keyStr, String ivStr, String encryptedStr) throws Exception {
    return decrypt(algorithm, keyStr, ivStr, encryptedStr, StandardCharsets.UTF_8);
  }

  public static String decrypt(String algorithm, String keyStr, String ivStr, String encryptedStr, Charset charset) throws Exception {
    Cipher cipher = initCipher(algorithm, Cipher.DECRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
    byte[] decrypted = cipher.doFinal(decode(encryptedStr));
    return new String(decrypted, charset);
  }

  /**
   * 解密文件
   */
  public static void encryptFile(String algorithm, String keyStr, String ivStr, File source, File target) throws Exception {
    checkPath(source, target);
    Cipher cipher = initCipher(algorithm, Cipher.ENCRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
    try (FileOutputStream fos = new FileOutputStream(target);
         CipherInputStream cis = new CipherInputStream(new FileInputStream(source), cipher)) {
      byte[] buffer = new byte[BUFFER_SIZE];
      int len;
      while ((len = cis.read(buffer)) != -1) {
        fos.write(buffer, 0, len);
      }
      fos.flush();
    }
  }

  /**
   * 加密文件
   */
  public static void decryptFile(String algorithm, String keyStr, String ivStr, File source, File target) throws Exception {
    checkPath(source, target);
    Cipher cipher = initCipher(algorithm, Cipher.DECRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
    try (FileInputStream fis = new FileInputStream(source);
         CipherOutputStream cos = new CipherOutputStream(new FileOutputStream(target), cipher)) {
      byte[] buffer = new byte[BUFFER_SIZE];
      int len;
      while ((len = fis.read(buffer)) != -1) {
        cos.write(buffer, 0, len);
      }
      cos.flush();
    }
  }

  public static void checkPath(File source, File target) throws IOException {
    Objects.requireNonNull(source);
    Objects.requireNonNull(target);
    if (source.isDirectory() || !source.exists()) {
      throw new FileNotFoundException(source.toString());
    }
    if (Objects.equals(source.getCanonicalPath(), target.getCanonicalPath())) {
      throw new IllegalArgumentException("sourceFile equals targetFile");
    }
    File parentDirectory = target.getParentFile();
    if (parentDirectory != null && !parentDirectory.exists()) {
      Files.createDirectories(parentDirectory.toPath());
    }
  }

  public static void main(String[] args) throws Exception {
    System.out.println(generateAESKey());
    System.out.println(generateAESIv(AES_ECB));
    String keyStr = "dN2VIV86Z2ShT47pEC1XwQ==";
    String ivStr = "00hDTDhCxa9t11TrQSso3w==";
    String encrypted = encrypt(AES_CBC, keyStr, ivStr, "中國深圳");
    System.out.println("encrypted:" + encrypted);
    System.out.println(decrypt(AES_CBC, keyStr, ivStr, encrypted));

    File source = new File("README.md");
    File encryptedFile = new File("out/README1.md");
    File decryptedFile = new File("out/README2.md");
    encryptFile(AES_CBC, keyStr, ivStr, source, encryptedFile);
    decryptFile(AES_CBC, keyStr, ivStr, encryptedFile, decryptedFile);
  }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 認證源碼分析 位置 : APIVIew 》dispatch方法 》self.initial(request, *args, **kwargs) >有認證,許可權,頻率三個版塊 分析: 只讀認證源碼: self.perform_authentication(request) 》 self.perform ...
  • 🌸 前言 嗨嘍~大家好呀,這裡是魔王吶 ! 半世櫻花無礙,緣來過往再生。 在我們看動漫的時候,總少不了被一些唯美的場景所吸引 請添加圖片描述 其中,就有不少櫻花的場景,雖然,現在不能線上下看櫻花~ 但是,我還是能用代碼畫畫來解解饞的(難不倒我) 🌸 婆娑紅塵苦,櫻花自綻放 ❀ 一期一會的絢爛·櫻 ...
  • 記錄一個老PHP項目中遇到的還能記得的坑,後面要是還有興趣研究研究PHP的話,或者又有哪些坑,就都記一記,有些東西真的是不去整就不知道,改起來雖然不難,但是找起來卻不容易啊。┗|`O′|┛ 嗷~~ ...
  • 大家都用過鬧鐘,鬧鐘可以說是一種定時任務。比如我們設定了周一到周五早上7點半的時間響鈴,那麼鬧鐘就會在周一到周五的早上7點半進行響鈴,這種就是定時的任務。 ...
  • [BigDecimal精確度的計數保留法及精度丟失的解決辦法] BigDecimal精確度的計數保留法 在銀行、帳戶、計費等領域,BigDecimal提供了精確的數值計算。其中8種舍入方式值得掌握。 1、ROUND_UP 舍入遠離零的舍入模式。 在丟棄非零部分之前始終增加數字(始終對非零捨棄部分前面 ...
  • [優美的Java代碼之try...catch] 概述 通常我們使用try...catch()捕獲異常時,如果遇到類似IO流的處理,要在finally部分關閉IO流。這是JDK1.7之前的寫法了; 在JDK7以後,可以使用優化後的try-with-resource語句,該語句確保了每個資源,在語句結束 ...
  • 摘要:當你使用java實現一個線程同步的對象時,一定會包含一個問題:你該如何保證多個線程訪問該對象時,正確地進行阻塞等待,正確地被喚醒? 本文分享自華為雲社區《JUC中的AQS底層詳細超詳解,剖析AQS設計中所需要考慮的各種問題!》,作者: breakDawn 。 java中AQS究竟是做什麼的? ...
  • Dubbo 和Zookeeper 不是SpringCloud的東西,放在這裡只是為了方便複習; 1、下載安裝Zookeeper和Dubbo 1.1 下載安裝教程 下載安裝教程 windows環境下安裝zookeeper教程詳解(單機版) 1.2 啟動頁面 1.2.1 zkServer.xmd zoo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...