Java AES加密和静态密钥解密

Posted

技术标签:

【中文标题】Java AES加密和静态密钥解密【英文标题】:Java AES encryption and decryption with static secret 【发布时间】:2015-09-23 03:46:43 【问题描述】:

我有一个应用程序需要在配置文件中存储一些秘密密码,例如数据库和 ftp 密码/详细信息。我环顾四周,发现了很多使用 AES 的加密/解密解决方案,但我似乎无法弄清楚如何在不更改密钥的情况下使其工作。这意味着我可以加密和解密(使用相同的 SecretKey),但要在重启等过程中保持持久性。我似乎无法让 SecretKey 保持不变。下面的示例显示了我的方法的工作原理:

String secret = Encryptor.encrpytString("This is secret");
String test = Encryptor.decrpytString(secret);
System.out.println(test); //This is secret is printed

到目前为止一切顺利。但是,如果我运行它一次,我可能会得到“2Vhht/L80UlQ184S3rlAWw==”的值作为我的秘密,下一次它是“MeC4zCf9S5wUUKAu8rvpCQ==”,所以大概密钥正在改变。我假设我正在对问题应用一些反直觉的逻辑,如果有人能阐明a)我做错了什么,或者b)允许我存储加密密码信息的解决方案,我将不胜感激并且可以使用提供的信息进行检索。

我的方法如下:

private static final String salt = "SaltySalt";

private static byte [] ivBytes = null;

private static byte[] getSaltBytes() throws Exception 
    return salt.getBytes("UTF-8");


private static char[] getMasterPassword() 
    return "SuperSecretPassword".toCharArray();


private static byte[] getIvBytes() throws Exception 
    if (ivBytes == null) 
        //I don't have the parameters, so I'll generate a dummy encryption to create them
        encrpytString("test");
    
    return ivBytes;


public static String encrpytString (String input) throws Exception 
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256);
    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
    byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8"));
    return DatatypeConverter.printBase64Binary(encryptedTextBytes);        


public static String decrpytString (String input) throws Exception 
    byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(input);
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256);
    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(getIvBytes()));
    byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
    return new String(decryptedTextBytes);

感谢您的帮助!

【问题讨论】:

【参考方案1】:

好的,看来我找到了问题的答案。我的信息来自this *** post。 据我了解,IV(初始化向量)用于将熵添加到加密过程中。每次创建新密码时,Java 都会创建一个稍微不同的 IV。因此有两种解决方案:

    使用固定的 IV,或 将 IV 与加密数据一起存储。

根据我的阅读,选项 1 不是很好的做法;所以选项2是。我知道应该可以简单地将 IV 附加到加密字符串(因为仍然需要秘密),因此可以在解密时重建 IV。

这是几乎完整的解决方案。我在解密时仍然遇到一些填充错误(请参阅我的评论)。我现在没有时间花在上面,所以作为临时措施,我立即尝试解密加密字符串并继续尝试(迭代)直到它起作用。它似乎有大约 50% 的命中率 + 我加密的频率不够高,不足以成为性能问题。如果有人可以提出修复建议会很好(只是为了完整起见)。

private static final String salt = "SaltySalt";

private static final int IV_LENGTH = 16;

private static byte[] getSaltBytes() throws Exception 
    return salt.getBytes("UTF-8");


private static char[] getMasterPassword() 
    return "SuperSecretPassword".toCharArray();


public static String encrpytString (String input) throws Exception 
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256);
    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secret);
    byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
    byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8"));
    byte[] finalByteArray = new byte[ivBytes.length + encryptedTextBytes.length]; 
    System.arraycopy(ivBytes, 0, finalByteArray, 0, ivBytes.length);
    System.arraycopy(encryptedTextBytes, 0, finalByteArray, ivBytes.length, encryptedTextBytes.length);
    return DatatypeConverter.printBase64Binary(finalByteArray);        


public static String decrpytString (String input) throws Exception 
    if (input.length() <= IV_LENGTH) 
        throw new Exception("The input string is not long enough to contain the initialisation bytes and data.");
    
    byte[] byteArray = DatatypeConverter.parseBase64Binary(input);
    byte[] ivBytes = new byte[IV_LENGTH];
    System.arraycopy(byteArray, 0, ivBytes, 0, 16);
    byte[] encryptedTextBytes = new byte[byteArray.length - ivBytes.length];
    System.arraycopy(byteArray, IV_LENGTH, encryptedTextBytes, 0, encryptedTextBytes.length);
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256);
    SecretKey secretKey = factory.generateSecret(spec);
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
    byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
    return new String(decryptedTextBytes);

【讨论】:

这正是我在第三部分集成界面中所拥有的。 XML 输入数据字段 Base64toBytes,字节 0..15 为 IV 值,使用密钥解密字节 16..n。 对于 AES/CBC/PKCS7Padding,大多数人选择在密文前添加 IV。所以在解密时,从密文中剪下前 16 个字节,它们是 IV,初始化密码,然后 doFinal 与其余部分。这有一些不错的属性,最值得注意的是不遵循此约定但仅以 0 IV 开头并丢弃第一个块的实现仍然可以解密文本。 感谢大家的确认! 情节变厚了...仍然不是 100% 得到一些“给定最终块没有正确填充”错误... 您的方法不够安全。我强烈建议您阅读以下内容:***.com/a/53015144/1235935【参考方案2】:

使用静态初始化向量,例如零 IV:

cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));

cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));

由于您要存储密码,因此您可能希望使用随机 IV 和/或随机盐并将它们与密文一起存储,这样相同的密码就不会加密为相同的密文。

【讨论】:

同意使用静态 iv 可以解决问题,但也同意这不是好的做法,除非在存储之前对文本进行外部加盐。将 IV 与加密消息一起存储似乎是可行的方法。【参考方案3】:

之前需要setSeed()

class Encryptor 

    static final String salt = "SaltSalt";

    public static byte[] encryptString(String input) throws Exception 
        byte[] bytes = input.getBytes("UTF-8");

        Cipher cipher = Cipher.getInstance("AES");
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(salt.getBytes("UTF-8"));
        keyGenerator.init(256, secureRandom);
        Key key = keyGenerator.generateKey();
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] a = cipher.doFinal(bytes);
        return a;
    

    public static String decryptString(byte[] input) throws Exception 
        Cipher cipher = Cipher.getInstance("AES");
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(salt.getBytes("UTF-8"));
        keyGenerator.init(256, secureRandom);
        Key key = keyGenerator.generateKey();
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decrypted = cipher.doFinal(input);
        String result = new String(decrypted, "UTF-8");
        return result;
    

【讨论】:

我正要对此投反对票。这是非常脆弱的。您正在使用默认的 ECB 模式。我强烈建议你把这个红起来:***.com/a/53015144/1235935 它也不适用于任何具有不错默认 SecureRandom 的 Java 实例,这似乎是 Windows 上的 j

以上是关于Java AES加密和静态密钥解密的主要内容,如果未能解决你的问题,请参考以下文章

java AES加密解密

java AES加密

如何使用java对密码加密 加密方式aes

AES加密和解密Java

AES加解密 对称加密

Java使用AES加解密