在 Java 中加密配置檔案中的密碼
加密是使用加密演算法結合稱為加密金鑰
的引數將純文字資訊轉換為不可讀形式的過程。不可讀的格式通常稱為密文
格式。只有擁有解密金鑰
的人才能解密資料並恢復原始明文。
我們可以將對配置檔案中的密碼進行加密的問題分解為以下兩個子任務。
- 加密檔案中存在的純文字密碼。
- 解密從檔案讀取的加密密碼。
首先讓我們在 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 javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.util.Properties;
import static fileDataEncryption.Encryption.*;
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
類中使用者定義的方法的詳細說明如下。
createSecretKey
是一個函式,它接受諸如password
、salt
、iterationCount
和keyLength
之類的引數。password
是配置檔案中的實際密碼。在密碼術中,salt
是隨機資料,我們將其用作雜湊資料,密碼或密碼短語的附加輸入。salt
的使用是為了保護儲存密碼。我們將iterationCount
變數用作演算法應進行的迭代次數。減小變數的值可以縮短啟動時間,因此在測試過程中很有用,但對於暴力攻擊者來說也更容易。keyLength
變數是我們最終需要派生的金鑰的長度。引發從使用它的方法中引發的異常。getInstance
方法從最喜歡的提供者開始遍歷已註冊安全提供者的列表。它採用請求的金鑰演算法的標準名稱,並返回新的SecretKeyFactory
物件。如果指定演算法為 null,則丟擲NullPointerException
,如果沒有提供者支援指定演算法的SecretKeyFactorySpi
實現,則丟擲NoSuchAlgorithmException
。PBEKeySpec
是一個類建構函式,它使用密碼,鹽,迭代計數和要衍生的金鑰長度來生成可變金鑰大小的 PBE 密碼的PBEKey
。如果salt
為null
,則丟擲NullPointerException
,如果 salt 為空,則丟擲IllegalArgumentException
。generateSecret
根據提供的金鑰規範或金鑰材料生成一個SecretKey
物件。它採用了金鑰的規範。如果給定的規範不適合此金鑰工廠產生分類的金鑰值,則丟擲InvalidKeySpecException
。
Encryption
類中的 encrypt
方法的詳細資訊。
encrypt
方法具有兩個引數,即要加密的資料和金鑰。此方法將引發從其子方法引發的異常。getInstance
方法從最喜歡的提供者開始遍歷已註冊安全提供者的列表。它採用轉換的名稱,即 AES / CBC / PKCS5Padding。如果更改為 null,為空,且格式無效,則丟擲NoSuchAlgorithmException
,如果更改包含不可用的填充方案,則丟擲NoSuchPaddingException
。init
方法為以下四個操作之一初始化Cipher
:加密,解密,金鑰包裝或金鑰解包,具體取決於操作模式值。在我們的案例中是ENCRYPT_MODE
。如果操作模式無效,則該方法將引發UnsupportedOperationException
,如果給定金鑰不適當,則將引發InvalidKeyException
。getParameters
返回與此密碼一起使用的引數。getParameterSpec
返回引數物件的規範。paramSpec
引數標識必須在其中返回引數的規範類。例如,可能是DSAParameterSpec.class
,以指示引數必須在DSAParameterSpec
類的例項中返回。doFinal
方法在單部分工作中加密或解密資料,或完成多部分操作。資料是加密還是解密,取決於我們如何初始化密碼。base64Encode
是一個私有方法,它使用Base64
編碼方案將指定的位元組陣列編碼為字串。decrypt
方法中使用的功能與上述方法類似。唯一的區別是,它們根據功能DECRYPT_MODE
中指定的mode
的執行方式不同。
package fileDataEncryption;
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;
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;
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
Rashmi is a professional Software Developer with hands on over varied tech stack. She has been working on Java, Springboot, Microservices, Typescript, MySQL, Graphql and more. She loves to spread knowledge via her writings. She is keen taking up new things and adopt in her career.
LinkedIn