【问题标题】:Java AES encryption/decryption procedure and usage of Initialization VectorJava AES加密/解密过程和初始化向量的使用
【发布时间】:2015-04-22 00:55:38
【问题描述】:

我想学习 AES 加密的基础知识,所以我开始制作一个非常简单的 Java 程序。该程序将一个文本文件加载到String 并要求用户提供密钥。然后,该程序使用 AES 加密文本,使用加密文本创建一个新的文本文件。程序将初始化向量 (IV) 打印给用户。

该程序还具有解密功能。用户指定加密的文本文件以及初始化向量和将其解密回新文本文件中的原始文本的密钥。

但是我认为我做错了什么。用户需要同时拥有密钥和IV才能解密文件是AES加密的正常程序吗?我浏览了互联网,几乎在每个示例中,加密数据都可以由用户仅指定密钥来解密,但在我的情况下,用户需要同时拥有密钥和 IV。该程序运行良好,但我认为它效率不高。

那么我应该使用用于所有加密和解密的常量、已知 IV 还是什么?还有一些教程使用“盐”,它是什么,我应该使用它吗?

这是我的加密和解密方法:

public String encrypt(String stringToEncrypt, String userKey)
        throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

    // User gives string key which is formatted to 16 byte and to a secret
    // key
    byte[] key = userKey.getBytes();
    MessageDigest sha = MessageDigest.getInstance("SHA-1");
    key = sha.digest(key);
    key = Arrays.copyOf(key, 16);
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");

    // Cipher initialization
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    // Encryption and encoding
    String encryptedData = new BASE64Encoder().encode(cipher
            .doFinal(stringToEncrypt.getBytes()));

    // IV is printed to user
    System.out.println("\nENCRYPTION IV: \n"
            + new BASE64Encoder().encode(cipher.getIV()) + "\n");

    // Function returns encrypted string which can be writed to text file
    return encryptedData;

}

public String decrypt(String stringToDecrypt, String userKey, String userIv)
        throws NoSuchAlgorithmException, IOException,
        NoSuchPaddingException, InvalidKeyException,
        InvalidAlgorithmParameterException, IllegalBlockSizeException,
        BadPaddingException {

    // User gives the same string key which was used for encryption
    byte[] key = userKey.getBytes();
    MessageDigest sha = MessageDigest.getInstance("SHA-1");
    key = sha.digest(key);
    key = Arrays.copyOf(key, 16);
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");

    // Decode string iv to byte
    byte[] iv = new BASE64Decoder().decodeBuffer(userIv);

    // Cipher initialization
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));

    // Decryption and decoding
    String decryptedData = new String(cipher.doFinal(new BASE64Decoder()
            .decodeBuffer(stringToDecrypt)));

    // Function returns decrypted string which can be writed to text file
    return decryptedData;

}

更新

我现在更新了我的代码以使用带有盐等的“PBKDF2WithHmacSHA256”算法。我还将初始化向量 (IV) 字节数组作为前缀组合到密文字节数组中,以便我可以在解密方法中拆分它们并获得 IV那里(工作正常)。

但是现在密钥存在问题,因为我也在解密方法中生成新的加密密钥,这当然是加密数据的错误密钥。我希望能够关闭程序,所以我不能将密钥存储为类变量。很难解释这个问题,但我希望你能理解这个问题......

public static byte[] getEncryptedPassword(String password, byte[] salt,
        int iterations, int derivedKeyLength)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    KeySpec mKeySpec = new PBEKeySpec(password.toCharArray(), salt,
            iterations, derivedKeyLength);

    SecretKeyFactory mSecretKeyFactory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA256");

    return mSecretKeyFactory.generateSecret(mKeySpec).getEncoded();

}

public String encrypt(String dataToEncrypt, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {

    byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
            16384, 128);

    SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");


    Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

    mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec);

    byte[] ivBytes = mCipher.getIV();
    byte[] encryptedTextBytes = mCipher.doFinal(dataToEncrypt.getBytes());

    byte[] combined = new byte[ivBytes.length+encryptedTextBytes.length];       
    System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
    System.arraycopy(encryptedTextBytes, 0, combined, ivBytes.length, encryptedTextBytes.length);

    return Base64.getEncoder().encodeToString(combined);

}

public String decrypt(String dataToDecrypt, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {


    byte[] encryptedCombinedBytes = Base64.getDecoder().decode(dataToDecrypt);
    byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
            16384, 128);

    byte[] ivbytes = Arrays.copyOfRange(encryptedCombinedBytes,0,16);

    SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");

    Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

    mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, new IvParameterSpec(ivbytes));    

    byte[] encryptedTextBytes = Arrays.copyOfRange(encryptedCombinedBytes, 16, encryptedCombinedBytes.length);

    System.out.println(encryptedTextBytes.length);
    byte[] decryptedTextBytes = mCipher.doFinal(encryptedTextBytes);



    return Base64.getEncoder().encodeToString(decryptedTextBytes);

}

public byte[] generateSalt() {
    SecureRandom random = new SecureRandom();
    byte saltBytes[] = new byte[16];
    random.nextBytes(saltBytes);
    return saltBytes;
}}

我希望有人知道如何使它变得更好。谢谢!

【问题讨论】:

  • 随机盐用于从密码派生密钥或从密码创建安全哈希以进行身份​​验证。盐和 IV 通常是不同的东西。
  • 那么我应该实现 salt 以从 userKey 字符串派生密钥还是使用我正在使用的 MessageDigest 足够安全?
  • 您应该明确地研究 Java 中的 PBKDF2,它具有像 SHA256 这样的强大加密哈希函数。 MD5 不再那么好了。
  • @ArtjomB。我不明白你为什么首先提到MD5。源代码使用 SHA-1 哈希函数,此外,SHA-2 系列(256 和 512)的函数应该已经在标准中实现(虽然我现在找不到该信息的来源)

标签: java encryption cryptography aes


【解决方案1】:

只需将IV保存在加密数据之前的文件中即可。

您永远不应该多次使用同一个 IV(没关系,如果您每次都滚动一个新的 IV,并且碰巧您滚动了两次相同的 IV,因此您不必存储和检查那)。多次使用相同的 IV 会带来很大的安全风险,因为对相同的内容进行两次加密表明它 - 实际上 - 是相同的内容。

将 IV 与加密数据一起存储是一种常见且安全的过程,因为它的作用是为加密方案引入“随机性”,它不应该是秘密的,只是安全地(在某些方案中是随机地)生成的。

【讨论】:

  • 所以我基本上可以把它放到我的加密函数的返回中: return new BASE64Encoder().encode(cipher.getIV()) + " " + encryptedData 然后在解密时我将它拆分回加密数据和IV
  • @LeeviLehtonen IV 的大小始终相同(block size)。将IV前缀到密文之前以base 64编码会更好,更有效...
  • 我更新了它以组合字节数组,然后我在解密方法中将它们拆分回来。谢谢。虽然我还更新了现在不起作用的密钥功能......
猜你喜欢
  • 2014-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-03
  • 1970-01-01
  • 1970-01-01
  • 2016-10-18
  • 1970-01-01
相关资源
最近更新 更多