【问题标题】:Java AES GCM AEAD Tag mismatchJava AES GCM AEAD 标签不匹配
【发布时间】:2021-05-12 00:32:24
【问题描述】:

我正在尝试编写一个程序来加密任何类型的文件。我已经完成了我的加密课程,当我注意到(起初它有效)每当我尝试解密我的任何文件时都会收到 AEADBadTagException。

这是我的加密/解密类:

class Encryptor {

    private static final String algorithm = "AES/GCM/NoPadding";

    private final int tagLengthBit = 128; // must be one of {128, 120, 112, 104, 96}
    private final int ivLengthByte = 12;
    private final int saltLengthByte = 64;
    protected final Charset UTF_8 = StandardCharsets.UTF_8;
    private CryptoUtils crypto = new CryptoUtils();

    // return a base64 encoded AES encrypted text
    /**
     * 
     * @param pText    to encrypt
     * @param password password for encryption
     * @return encoded pText
     * @throws Exception
     */
    protected byte[] encrypt(byte[] pText, char[] password) throws Exception {

        // 64 bytes salt
        byte[] salt = crypto.getRandomNonce(saltLengthByte);

        // GCM recommended 12 bytes iv?
        byte[] iv = crypto.getRandomNonce(ivLengthByte);

        // secret key from password
        SecretKey aesKeyFromPassword = crypto.getAESKeyFromPassword(password, salt);

        Cipher cipher = Cipher.getInstance(algorithm);

        // ASE-GCM needs GCMParameterSpec
        cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(tagLengthBit, iv));

        byte[] cipherText = cipher.doFinal(pText);

        // prefix IV and Salt to cipher text
        byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length).put(iv).put(salt)
                .put(cipherText).array();
        Main.clearArray(password, null);
        Main.clearArray(null, salt);
        Main.clearArray(null, iv);
        Main.clearArray(null, cipherText);
        aesKeyFromPassword = null;
        cipher = null;
        try {
            return cipherTextWithIvSalt;

        } finally {
            Main.clearArray(null, cipherTextWithIvSalt);
        }
    }



// für Files
    protected byte[] decrypt(byte[] encryptedText, char[] password)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {

        // get back the iv and salt from the cipher text
        ByteBuffer bb = ByteBuffer.wrap(encryptedText);

        byte[] iv = new byte[ivLengthByte];
        bb.get(iv);

        byte[] salt = new byte[saltLengthByte];
        bb.get(salt);

        byte[] cipherText = new byte[bb.remaining()];
        bb.get(cipherText);

        // get back the aes key from the same password and salt
        SecretKey aesKeyFromPassword;
        aesKeyFromPassword = crypto.getAESKeyFromPassword(password, salt);

        Cipher cipher;
        cipher = Cipher.getInstance(algorithm);

        cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(tagLengthBit, iv));

        byte[] plainText = cipher.doFinal(cipherText);
        
        Main.clearArray(password, null);
        Main.clearArray(null, iv);
        Main.clearArray(null, salt);
        Main.clearArray(null, cipherText);
        aesKeyFromPassword = null;
        cipher = null;
        bb = null;
        try {
            return plainText;
        } finally {
            Main.clearArray(null, plainText);
        }

    }

    protected void encryptFile(String file, char[] pw) throws Exception {
        Path pathToFile = Paths.get(file);

        byte[] fileCont = Files.readAllBytes(pathToFile);

        byte[] encrypted = encrypt(fileCont, pw);

        Files.write(pathToFile, encrypted);

        Main.clearArray(pw, null);
        Main.clearArray(null, fileCont);
        Main.clearArray(null, encrypted);
    }

    protected void decryptFile(String file, char[] pw)
            throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
        Path pathToFile = Paths.get(file);
        
        byte[] fileCont = Files.readAllBytes(pathToFile);
        
        byte[] decrypted = decrypt(fileCont, pw);

        Files.write(pathToFile, decrypted);

        Main.clearArray(pw, null);
        Main.clearArray(null, fileCont);
        Main.clearArray(null, decrypted);

    }

}

对应的 CryptoUtils 类:

class CryptoUtils {

    protected byte[] getRandomNonce(int numBytes) {
        byte[] nonce = new byte[numBytes];
        new SecureRandom().nextBytes(nonce);
        try {
            return nonce;

        } finally {
            Main.clearArray(null, nonce);
        }
    }


    // Password derived AES 256 bits secret key
    protected SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        // iterationCount = 65536
        // keyLength = 256
        KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
        SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
        try {
            return secret;

        } finally {
            secret = null;
        }
    }

    // hex representation
    protected String hex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }

        try {
            return result.toString();

        } finally {
            result.delete(0, result.length() - 1);
        }
    }

    // print hex with block size split
    protected String hexWithBlockSize(byte[] bytes, int blockSize) {

        String hex = hex(bytes);

        // one hex = 2 chars
        blockSize = blockSize * 2;

        // better idea how to print this?
        List<String> result = new ArrayList<>();
        int index = 0;
        while (index < hex.length()) {
            result.add(hex.substring(index, Math.min(index + blockSize, hex.length())));
            index += blockSize;
        }

        try {
            return result.toString();

        } finally {
            result.clear();
        }
    }

}

异常发生在解密方法中的byte[] plainText = cipher.doFinal(cipherText);

我不确定 tagLenthBit 是否必须是 ivLengthByte * 8,但我确实尝试过,但没有任何区别。

【问题讨论】:

  • 我无法重现这个。您是否可能混淆了文件,因为您将所有内容都保存在同一个文件中?
  • @Topaco 不可能,因为对于每个文件,都会读取内容,然后立即写回。仅选择一个文件也会发生这种情况
  • 正如我所说,在我的机器上代码可以工作(使用 14 MB pdf 测试文件)。我会尝试隔离问题,例如您可以先测试encrypt()decrypt() 没有 文件的问题是否也出现。
  • 您似乎清除了finally 语句中的结果(finallyreturn 之前执行),s。 encrypt()decrypt()getRandomNonce()。我已经注释掉了这些行(因为你没有发布Main.clearArray()),所以它从一开始就在我的机器上运行。
  • 为了完整起见,请注意即使您将对象的所有引用设置为null,以便在下次运行时被GC清除,您也无法控制何时发生这种情况(以及数据是否从物理内存中完全清除,出于性能原因通常不是这种情况)。从内存中删除关键数据通常不是一个小问题,s。例如hereherehere

标签: java encryption buffer bytebuffer aes-gcm


【解决方案1】:

我正在提供我自己的示例代码,用于使用 PBKDF2 密钥派生的 AES 256 GCM 文件加密,因为我懒得检查代码的所有部分 :-)

加密是使用 CipherInput-/Outputstreams 完成的,因为这样可以避免在加密较大的文件时出现“内存不足错误”(您的代码正在读取字节数组中的完整明文/密文)。

请注意,代码没有异常处理,没有清除敏感数据/变量,加密/解密结果是一个简单的“文件存在”例程,但我相信您可以将其用作您程序的良好基础.

这是一个示例输出:

AES 256 GCM-mode PBKDF2 with SHA512 key derivation file encryption
result encryption: true
result decryption: true

代码:

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

public class AesGcmEncryptionInlineIvPbkdf2BufferedCipherInputStreamSoExample {
    public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, IOException,
            InvalidKeyException, InvalidKeySpecException, InvalidAlgorithmParameterException {
        System.out.println("AES 256 GCM-mode PBKDF2 with SHA512 key derivation file encryption");

        char[] password = "123456".toCharArray();
        int iterations = 65536;
        String uncryptedFilename = "uncrypted.txt";
        String encryptedFilename = "encrypted.enc";
        String decryptedFilename = "decrypted.txt";
        boolean result;
        result = encryptGcmFileBufferedCipherOutputStream(uncryptedFilename, encryptedFilename, password, iterations);
        System.out.println("result encryption: " + result);
        result = decryptGcmFileBufferedCipherInputStream(encryptedFilename, decryptedFilename, password, iterations);
        System.out.println("result decryption: " + result);
    }

    public static boolean encryptGcmFileBufferedCipherOutputStream(String inputFilename, String outputFilename, char[] password, int iterations) throws
            IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException {
        SecureRandom secureRandom = new SecureRandom();
        byte[] salt = new byte[32];
        secureRandom.nextBytes(salt);
        byte[] nonce = new byte[12];
        secureRandom.nextBytes(nonce);
        Cipher cipher = Cipher.getInstance("AES/GCM/NOPadding");
        try (FileInputStream in = new FileInputStream(inputFilename);
             FileOutputStream out = new FileOutputStream(outputFilename);
             CipherOutputStream encryptedOutputStream = new CipherOutputStream(out, cipher);) {
            out.write(nonce);
            out.write(salt);
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
            KeySpec keySpec = new PBEKeySpec(password, salt, iterations, 32 * 8); // 128 - 192 - 256
            byte[] key = secretKeyFactory.generateSecret(keySpec).getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, nonce);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
            byte[] buffer = new byte[8096];
            int nread;
            while ((nread = in.read(buffer)) > 0) {
                encryptedOutputStream.write(buffer, 0, nread);
            }
            encryptedOutputStream.flush();
        }
        if (new File(outputFilename).exists()) {
            return true;
        } else {
            return false;
        }
    }

    public static boolean decryptGcmFileBufferedCipherInputStream(String inputFilename, String outputFilename, char[] password, int iterations) throws
            IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException {
        byte[] salt = new byte[32];
        byte[] nonce = new byte[12];
        Cipher cipher = Cipher.getInstance("AES/GCM/NOPadding");
        try (FileInputStream in = new FileInputStream(inputFilename); // i don't care about the path as all is lokal
             CipherInputStream cipherInputStream = new CipherInputStream(in, cipher);
             FileOutputStream out = new FileOutputStream(outputFilename)) // i don't care about the path as all is lokal
        {
            byte[] buffer = new byte[8192];
            in.read(nonce);
            in.read(salt);
            SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
            KeySpec keySpec = new PBEKeySpec(password, salt, iterations, 32 * 8); // 128 - 192 - 256
            byte[] key = secretKeyFactory.generateSecret(keySpec).getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, nonce);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
            int nread;
            while ((nread = cipherInputStream.read(buffer)) > 0) {
                out.write(buffer, 0, nread);
            }
            out.flush();
        }
        if (new File(outputFilename).exists()) {
            return true;
        } else {
            return false;
        }
    }
}

【讨论】:

  • 谢谢!这个密码输出流的唯一问题是它需要一个密码,但是我得到了一个已经通过密码返回加密结果的方法:/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-12
  • 2017-05-10
  • 2013-07-17
  • 2022-01-04
  • 2015-11-23
  • 2021-09-19
  • 2014-07-14
相关资源
最近更新 更多