使用基于密码的加密查看此 AES-128 CBC 示例

Review of this AES-128 CBC example with password-based encryption

我需要一些帮助来验证以下代码片段 Java 使用 CBC、PKCS5Padding 和 IV 进行 AES 加密。

我测试了代码,能够加密和解密。我有一些疑问,如下所述。

  1. 密码应该存储在哪里作为一个好的约定?
  2. appending/retrieving加盐和IV字节到密文的方式可以吗?
  3. 非常感谢任何其他评论,谢谢!
public class Encryption {

    private static int iterations = 65536;
    private static int keySize = 128;
    private static char[] password = "password".toCharArray();
    private static String algorithm= "PBKDF2WithHmacSHA1";


    private static final String SEPARATOR = "~";


     public static void main(String []args) throws Exception {

         String filePath = "test.xml";

         String fileContent = new String(Files.readAllBytes(Paths.get(filePath)));

         String encrMesg = encrypt(fileContent);

         System.out.println("Encrypted: " + encrypt(encrMesg)); 

         System.out.println("Decrypted: " + decrypt(encrMesg)); 
     }


    public static String encrypt(String plaintext) throws Exception {


        byte[] saltBytes = getSalt().getBytes();

        SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
        PBEKeySpec spec = new PBEKeySpec(password, saltBytes, iterations, keySize);
        SecretKey secretKey = skf.generateSecret(spec);
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretSpec);
        AlgorithmParameters params = cipher.getParameters();

        byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
        byte[] cipherText = cipher.doFinal(String.valueOf(plaintext).getBytes("UTF-8"));

        return DatatypeConverter.printBase64Binary(ivBytes)+SEPARATOR+DatatypeConverter.printBase64Binary(saltBytes)
        +SEPARATOR+DatatypeConverter.printBase64Binary(cipherText);
    }

    public static String decrypt(String encryptedText) throws Exception {

        System.out.println(encryptedText);

        String[] encryptedArr = encryptedText.split(SEPARATOR);

        byte[] ivBytes = DatatypeConverter.parseBase64Binary(new String(encryptedArr[0]));

        byte[] salt = DatatypeConverter.parseBase64Binary(new String(encryptedArr[1]));

        byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedArr[2]));

        SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
        PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
        SecretKey secretKey = skf.generateSecret(spec);
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));

        byte[] decryptedTextBytes = null;

        try {
            decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }

        return new String(decryptedTextBytes);

    }

    public static String getSalt() throws Exception {

        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[20];
        sr.nextBytes(salt);
        return new String(salt);
    }

}

查询

Where should the password be stored as a good convention?

对称密钥最好存放在保险库中。否则他们应该进入密钥库,但是您会遇到保护密钥库密码的问题。

Is the way of appending/retrieving Salt and IV bytes to the Cipher text is fine?

盐应生成:

SecureRandom random = SecureRandom.getInstanceStrong();

否则您使用的是较弱的熵池(即 linux 中的 /dev/urandom)来生成您的安全号码,这会导致更容易被破解的弱密钥。

Any other comments highly appreciated, thanks!

在处理字符串转换时,您应该始终使用相同的编码,即 .getBytes("UTF-8") 以避免出现问题。例如,在转换盐时不使用它。