在 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 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
类中用户定义的方法的详细说明如下。
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 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
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