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