HOWTO · Java

在 Java 中加密配置檔案中的密碼

本文介紹如何在 Java 中加密配置檔案中的密碼。

本頁內容

加密是使用加密演算法結合稱為加密金鑰的引數將純文字資訊轉換為不可讀形式的過程。不可讀的格式通常稱為密文格式。只有擁有解密金鑰的人才能解密資料並恢復原始明文。

我們可以將對配置檔案中的密碼進行加密的問題分解為以下兩個子任務。

  1. 加密檔案中存在的純文字密碼。
  2. 解密從檔案讀取的加密密碼。

首先讓我們在 src/conf/路徑下建立一個名為 config.properties 的配置檔案。

password = TestPassword123

現在,要讀取配置檔案,請例項化 Properties 類。我們可以使用其建構函式建立 FileInputStream 類的例項。它以配置檔案的路徑作為輸入。現在,使用屬性類的例項來載入屬性。使用 load 方法在類中載入屬性檔案,這將 InputStreamReader 例項作為引數。如果此輸入流包含格式錯誤的 Unicode 轉義序列,則丟擲 IllegalArgumentException;如果從輸入流中讀取時發生錯誤,則丟擲 IOException。

成功載入屬性後,使用 getProperty() 方法在屬性列表中使用指定的鍵搜尋屬性。如果找不到屬性,該方法將返回 null。如果發現檔案中的密碼為空,則進行外部檢查以處理這種情況,並丟擲 IllegalArgumentException。

salt 是使用任意隨機字串建立的,以新增到密碼字串中。

createSecretKey 是使用者定義的方法,該方法返回 SecretKeySpec 金鑰,該金鑰的使用是對密碼進行加密和解密。encrypt 和 decrypt 方法是 Encryption 類中已使用的,定義的靜態方法。

下面是演示相同的示例程式碼。

package fileDataEncryption;

import static fileDataEncryption.Encryption.*;

import java.io.FileInputStream;
import java.util.Properties;
import javax.crypto.spec.SecretKeySpec;

public class ConfigFileEncryption {
  public static void main(String[] args) throws Exception {
    Properties properties = new Properties();
    FileInputStream inputStream = new FileInputStream("src/conf/config.properties");
    properties.load(inputStream);
    String password = properties.getProperty("password");

    if (password == null) {
      throw new IllegalArgumentException("No such parameter present in config file");
    }

    byte[] salt = new String("12345678").getBytes();
    int iterationCount = 40000;
    int keyLength = 128;
    SecretKeySpec key = createSecretKey(password.toCharArray(), salt, iterationCount, keyLength);

    String originalPassword = password;
    System.out.println("Original password: " + originalPassword);
    String encryptedPassword = encrypt(originalPassword, key);
    System.out.println("Encrypted password: " + encryptedPassword);
    String decryptedPassword = decrypt(encryptedPassword, key);
    System.out.println("Decrypted password: " + decryptedPassword);
  }
}

Encryption 類中使用者定義的方法的詳細說明如下。

  1. createSecretKey 是一個函式,它接受諸如 password、salt、iterationCount 和 keyLength 之類的引數。password 是配置檔案中的實際密碼。在密碼術中,salt 是隨機資料,我們將其用作雜湊資料,密碼或密碼短語的附加輸入。salt 的使用是為了保護儲存密碼。我們將 iterationCount 變數用作演算法應進行的迭代次數。減小變數的值可以縮短啟動時間,因此在測試過程中很有用,但對於暴力攻擊者來說也更容易。keyLength 變數是我們最終需要派生的金鑰的長度。引發從使用它的方法中引發的異常。
  2. getInstance 方法從最喜歡的提供者開始遍歷已註冊安全提供者的列表。它採用請求的金鑰演算法的標準名稱,並返回新的 SecretKeyFactory 物件。如果指定演算法為 null,則丟擲 NullPointerException,如果沒有提供者支援指定演算法的 SecretKeyFactorySpi 實現,則丟擲 NoSuchAlgorithmException。
  3. PBEKeySpec 是一個類建構函式,它使用密碼,鹽,迭代計數和要衍生的金鑰長度來生成可變金鑰大小的 PBE 密碼的 PBEKey。如果 salt 為 null,則丟擲 NullPointerException,如果 salt 為空,則丟擲 IllegalArgumentException。
  4. generateSecret 根據提供的金鑰規範或金鑰材料生成一個 SecretKey 物件。它採用了金鑰的規範。如果給定的規範不適合此金鑰工廠產生分類的金鑰值,則丟擲 InvalidKeySpecException。

Encryption 類中的 encrypt 方法的詳細資訊。

  1. encrypt 方法具有兩個引數,即要加密的資料和金鑰。此方法將引發從其子方法引發的異常。
  2. getInstance 方法從最喜歡的提供者開始遍歷已註冊安全提供者的列表。它採用轉換的名稱,即 AES / CBC / PKCS5Padding。如果更改為 null,為空,且格式無效,則丟擲 NoSuchAlgorithmException,如果更改包含不可用的填充方案,則丟擲 NoSuchPaddingException。
  3. init 方法為以下四個操作之一初始化 Cipher:加密,解密,金鑰包裝或金鑰解包,具體取決於操作模式值。在我們的案例中是 ENCRYPT_MODE。如果操作模式無效,則該方法將引發 UnsupportedOperationException,如果給定金鑰不適當,則將引發 InvalidKeyException。
  4. getParameters 返回與此密碼一起使用的引數。
  5. getParameterSpec 返回引數物件的規範。paramSpec 引數標識必須在其中返回引數的規範類。例如,可能是 DSAParameterSpec.class,以指示引數必須在 DSAParameterSpec 類的例項中返回。
  6. doFinal 方法在單部分工作中加密或解密資料,或完成多部分操作。資料是加密還是解密,取決於我們如何初始化密碼。
  7. base64Encode 是一個私有方法,它使用 Base64 編碼方案將指定的位元組陣列編碼為字串。decrypt 方法中使用的功能與上述方法類似。唯一的區別是,它們根據功能 DECRYPT_MODE 中指定的 mode 的執行方式不同。
package fileDataEncryption;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryption {
  public static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount,
      int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
    SecretKey keyTmp = keyFactory.generateSecret(keySpec);
    return new SecretKeySpec(keyTmp.getEncoded(), "AES");
  }

  public static String encrypt(String dataToEncrypt, SecretKeySpec key)
      throws GeneralSecurityException, UnsupportedEncodingException {
    Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    pbeCipher.init(Cipher.ENCRYPT_MODE, key);
    AlgorithmParameters parameters = pbeCipher.getParameters();
    IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
    byte[] cryptoText = pbeCipher.doFinal(dataToEncrypt.getBytes("UTF-8"));
    byte[] iv = ivParameterSpec.getIV();
    return base64Encode(iv) + ":" + base64Encode(cryptoText);
  }

  private static String base64Encode(byte[] bytes) {
    return Base64.getEncoder().encodeToString(bytes);
  }

  public static String decrypt(String string, SecretKeySpec key)
      throws GeneralSecurityException, IOException {
    String iv = string.split(":")[0];
    String property = string.split(":")[1];
    Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
    return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
  }

  private static byte[] base64Decode(String property) throws IOException {
    return Base64.getDecoder().decode(property);
  }
}

以下是編寫的用於加密和解密配置檔案中密碼的程式碼的輸出。

Original password: TestPassword123
Encrypted password: Hy7fbIwpyKgp0oileu+oLg==:WNRknMJz/8u8GmWlCZFPFA==
Decrypted password: TestPassword123