用密文连接 MAC 和 salt

Concatenating MAC and salt with ciphertext

我在将 MAC 添加到基于密码的 AES encryption/decryption 程序时遇到了问题。我正在尝试将 MAC'd 明文和盐(与密码一起使用)(两个字节数组)与密文一起添加到最终数组,然后通过读取密文文件并将其拆分回来进行解密进入盐、MAC 和密文字节数组。

加密class貌似运行顺利,但解密class不顺利。我调试了 class 并发现它失败了,因为它从未进入检查计算和恢复的 MAC 是否相同的 if 语句:

if(Arrays.equals(macBytes, hmac))

直到我打印出salt、message和MAC的字节数组,我才弄明白为什么,从加密和解密中打印出来时发现它们不匹配[=31] =]是的。两个 classes 中的所有数组大小都匹配,但字节值在某处发生了变化。

两个 classes 在没有 MAC 的情况下工作得很好,但我没有直接将盐添加到加密数据中,而是将其写入一个单独的文件。将它与加密数据一起包含在内使它对我来说更便携,但这样做是不是一个糟糕的选择?将它写入一个单独的文件会更好吗?还是我只是遗漏了我的代码中一些明显的东西?

这是完整的代码。

加密class

public class AESEncryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
private final String PLAINTEXT = "/Volumes/CONNOR P/Unencrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private static final int SALT_SIZE = 64;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;

public AESEncryption()
{
    try
    {
        encrypt();
    }
    catch(Exception ex)
    {
        JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
    }
}
private void encrypt() throws Exception
{
    File encrypted = new File(ENCRYPTED);
    File plaintext = new File(PLAINTEXT);
    int encryptedSize = (int)encrypted.length();
    int plaintextSize = (int)plaintext.length();

    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(PLAINTEXT));
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(ENCRYPTED));

    //Create salt
    byte[] salt = new byte[SALT_SIZE];
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(salt);

    //Create cipher key    
    SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
    KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), ALGORITHM);

    //Create cipher
    Cipher cipher = Cipher.getInstance(TRANSFORMATION);
    cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));

    //Read plaintext file into byte array
    byte[] input = new byte[encryptedSize];
    Path path = Paths.get(PLAINTEXT);
    input = Files.readAllBytes(path);
    byte[] crypt = cipher.doFinal(input);

    //Create MAC object and apply to the byte array crypt[] containing the plaintext
    KeyGenerator keyGenerator = KeyGenerator.getInstance(MAC_ALGORITHM);
    SecretKey macKey = keyGenerator.generateKey();
    Mac mac = Mac.getInstance(MAC_ALGORITHM);
    mac.init(macKey);
    byte[] macBytes = mac.doFinal(crypt);

    //Add salt, MAC'd plaintext, and encrypted plaintext to final array
    byte[] output = new byte[SALT_SIZE + crypt.length + macBytes.length];
    System.arraycopy(salt, 0, output, 0, SALT_SIZE);
    System.arraycopy(macBytes, 0, output, SALT_SIZE, macBytes.length);
    System.arraycopy(crypt, 0, output, SALT_SIZE + macBytes.length, crypt.length);

    //Write array with encrypted data to a new file
    bufferedOutputStream.write(output);
    bufferedInputStream.close();   
    bufferedOutputStream.flush();
    bufferedOutputStream.close();
}

解密class

public class AESDecryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
private final String PLAINTEXT = "/Volumes/CONNOR P/De-Encrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 64;
private final int IV_SIZE = 16;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;

public AESDecryption()
{
    try
    {
        decrypt();
    }
    catch(Exception ex)
    {
        JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
    }
}
private void decrypt() throws Exception
{
    File encrypted = new File(ENCRYPTED);
    File plaintext = new File(PLAINTEXT);
    int encryptedSize = (int)encrypted.length();
    int plaintextSize = (int)plaintext.length();
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(encrypted));
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(plaintext));

    //Read in the encrypted data
    byte[] input = new byte[encryptedSize];
    Path path = Paths.get(ENCRYPTED);
    input = Files.readAllBytes(path);
    int increment = (input.length-SALT_SIZE)/2;

    if(input.length >= (SALT_SIZE + increment))
    {
        //Recover salt, MAC, and encrypted data and store in arrays
        byte[] salt = Arrays.copyOfRange(input, 0, SALT_SIZE);
        byte[] macBytes = Arrays.copyOfRange(input, SALT_SIZE, SALT_SIZE + increment);
        byte[] crypt = Arrays.copyOfRange(input, SALT_SIZE + increment, input.length);

        //Regenerate original MAC
        KeyGenerator keyGenerator = KeyGenerator.getInstance(MAC_ALGORITHM);
        SecretKey macKey = keyGenerator.generateKey();
        Mac mac = Mac.getInstance(MAC_ALGORITHM);
        mac.init(macKey);
        byte[] hmac = mac.doFinal(crypt);        

        if(Arrays.equals(macBytes, hmac)) //This is where it fails, never enters
        {
            //Regenerate cipher and decrypt data
            SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
            KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            SecretKey secret = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), ALGORITHM);

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

            //Write decrypted data to new text file
            byte[] output = cipher.doFinal(crypt);
            bufferedOutputStream.write(output);   
            bufferedInputStream.close();
            bufferedOutputStream.flush();
            bufferedOutputStream.close();
        }
    }
}

感谢您的帮助。非常感谢。

public class AESEncryption
{
    private final String ALGORITHM = "AES";
    private final String MAC_ALGORITHM = "HmacSHA256";
    private final String PRNG_ALGORITHM = "SHA1PRNG";
    private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private final String PLAINTEXT = "/Volumes/CONNOR P/Unencrypted.txt";
    private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
    private final String PASSWORD = "javapapers";
    private final String IV_FILE_NAME = "iv.enc";
    private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
    private final int SALT_SIZE = 16;
    private final int IV_SIZE = 16;
    private final int KEY_LENGTH = 128;
    private final int ITERATIONS = 100000;
    private final int START = 0;
    public AESEncryption()
    {
        try
        {
            encrypt();
        }
        catch(Exception ex)
        {
            JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
    private void encrypt() throws Exception
    {
        File encrypted = new File(ENCRYPTED);
        File plaintext = new File(PLAINTEXT);
        int plaintextSize = (int)plaintext.length();

        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(plaintext));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(encrypted));

        //Create salt for cipher key
        byte[] salt = new byte[SALT_SIZE];
        SecureRandom saltSecureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
        saltSecureRandom.nextBytes(salt);

        //Create cipher key & use to initialize cipher
        byte[] keyBytes = PBEKeyFactory.getKey(PASSWORD, salt, ITERATIONS, KEY_LENGTH);
        SecretKeySpec secret = new SecretKeySpec(keyBytes, ALGORITHM);

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

        //Create byte array of encrypted data
        byte[] input = new byte[plaintextSize];
        Path path = Paths.get(PLAINTEXT);
        input = Files.readAllBytes(path);
        byte[] crypt = cipher.doFinal(input);

        //Create salt for the MAC key for added security
        byte[] macsalt = new byte[SALT_SIZE];
        SecureRandom macsaltSecureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
        macsaltSecureRandom.nextBytes(macsalt);

        //PBEKeyFactory.getKey(password, salt, iterations, keylength)    
        //returns a byte array representation of a SecretKey.
        //Used a SecretKeyFactory instead of a KeyGenerator to make key.
        //SecretKeyFactory gives back the same key given the same specifications
        //whereas KeyGenerator gives back a new random key each time.
        byte[] macPBE = PBEKeyFactory.getKey(PASSWORD, macsalt, ITERATIONS, KEY_LENGTH);

        SecretKeySpec macKey = new SecretKeySpec(macPBE, MAC_ALGORITHM);
        Mac mac = Mac.getInstance(MAC_ALGORITHM);
        mac.init(macKey);
        byte[] macBytes = mac.doFinal(crypt);

        byte[] output = new byte[SALT_SIZE + SALT_SIZE + crypt.length + macBytes.length];
        System.arraycopy(salt, START, output, START, SALT_SIZE);
        System.arraycopy(macsalt, START, output, SALT_SIZE, SALT_SIZE);
        System.arraycopy(macBytes, START, output, SALT_SIZE + SALT_SIZE, macBytes.length);
        System.arraycopy(crypt, START, output, SALT_SIZE + SALT_SIZE + macBytes.length, crypt.length);

        bufferedInputStream.close();   
        bufferedOutputStream.write(output);
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
    }
}

public class AESDecryption
{
    private final String ALGORITHM = "AES";
    private final String MAC_ALGORITHM = "HmacSHA256";
    private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private final String PLAINTEXT = "/Volumes/CONNOR P/De-Encrypted.txt";
    private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
    private final String PASSWORD = "javapapers";
    private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
    private final int SALT_SIZE = 16;
    //MAC key size is 256 bits (32 bytes) since it is created with
    //the HmacSHA256 algorithm
    private final int MAC_SIZE = 32;
    private final int IV_SIZE = 16;
    private final int START = 0;
    private final int KEY_LENGTH = 128;
    private final int ITERATIONS = 100000;
    public AESDecryption()
    {
        try
        {
            decrypt();
        }
        catch(Exception ex)
        {
            JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
    private void decrypt() throws Exception
    {
        File encrypted = new File(ENCRYPTED);
        File plaintext = new File(PLAINTEXT);
        int encryptedSize = (int)encrypted.length();
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(encrypted));
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(plaintext));

        //Read in encrypted data
        byte[] input = new byte[encryptedSize];
        Path path = Paths.get(ENCRYPTED);
        input = Files.readAllBytes(path);

        if(input.length >= (SALT_SIZE*2 + MAC_SIZE))
        {
            byte[] cryptSalt = Arrays.copyOfRange(input, START, SALT_SIZE);
            byte[] macSalt = Arrays.copyOfRange(input, SALT_SIZE, SALT_SIZE*2);
            byte[] macBytes = Arrays.copyOfRange(input, SALT_SIZE*2, (SALT_SIZE*2 + MAC_SIZE));
            byte[] cryptBytes = Arrays.copyOfRange(input, (SALT_SIZE*2 + MAC_SIZE), input.length);

            //This generates the same MAC key from encryption.
            //Before, the KeyGenerator created a new random key
            //meaning the derived and computed MAC keys were never the same
            byte[] macKeyBytes = PBEKeyFactory.getKey(PASSWORD, macSalt, ITERATIONS, KEY_LENGTH);
            SecretKeySpec macKey = new SecretKeySpec(macKeyBytes, MAC_ALGORITHM);
            Mac mac = Mac.getInstance(MAC_ALGORITHM);
            mac.init(macKey);
            byte[] compMacBytes = mac.doFinal(cryptBytes);
            //Check if computed and derived MAC's are the same
            if(Arrays.equals(macBytes, compMacBytes))
            {
                //Creates same key from encryption 
                byte[] cryptKeyBytes = PBEKeyFactory.getKey(PASSWORD, cryptSalt, ITERATIONS, KEY_LENGTH);
                SecretKeySpec cryptKey = new SecretKeySpec(cryptKeyBytes, ALGORITHM);

                //Creates cipher and reads decrypted data to array
                Cipher cipher = Cipher.getInstance(TRANSFORMATION);
                cipher.init(Cipher.DECRYPT_MODE, cryptKey, new IvParameterSpec(new byte[16]));
                byte[] output = cipher.doFinal(cryptBytes);

                bufferedInputStream.close();
                bufferedOutputStream.write(output);   
                bufferedOutputStream.flush();
                bufferedOutputStream.close();
            }
        }
    }
}
//This class has only one method, getKey(), which returns a byte array
//of a SecretKey of the corresponding parameters
public class PBEKeyFactory
{
    private static final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
    public static byte[] getKey(String password, byte[] salt, int iterations, int length) throws Exception
    {
        KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, length);
        SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
        return factory.generateSecret(keySpec).getEncoded();
    }
}