为什么我的AES解密返回额外的字节?

时间:2018-07-11 10:40:41

标签: java encryption aes

不管文件大小如何,每个解密的文件都会附加32个字节的附加字符。我可以剪掉32个字节,但是它们来自哪里,如何避免在输出文件中出现它们?

这是我的源代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

public class EtAesCrypto {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final int KEY_LENGTH = 256;
    private static final int SALT_LENGTH = 16;
    private static final int IV_LENGTH = 12;
    private static final int AUT_TAG_LENGTH = 128;
    private static final int ITERATIONS = 100;

    private static final String ALGORITHM = "AES";
    private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA256";
    private static final String TRANSFORMATION = "AES/GCM/NoPadding";

    private String msg;

    public void encrypt(String path2Original, String path2Encrypted, String password) {
        try (FileOutputStream out = new FileOutputStream(path2Encrypted)) {
            byte[] salt = new byte[SALT_LENGTH];
            byte[] iv = new byte[IV_LENGTH];

            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(salt);
            SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
            KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), ALGORITHM);

            secureRandom.nextBytes(iv);
            logger.trace("IV length: {}", iv.length);

            out.write(salt);
            out.write(iv);

            Cipher ci = Cipher.getInstance(TRANSFORMATION);
            GCMParameterSpec parameterSpec = new GCMParameterSpec(AUT_TAG_LENGTH, iv);
            ci.init(Cipher.ENCRYPT_MODE, skey, parameterSpec);

            try (FileInputStream in = new FileInputStream(path2Original)) {
                processStream(ci, in, out);
            }
        } catch (Exception e) {
            throw new RuntimeException("Encryption of file with id failed.");
        }
    }

    public void decrypt(String path2Encrypted, OutputStream os, String password, String fileId) {
        try (FileInputStream in = new FileInputStream(path2Encrypted)) {
            doDecryption(in, os, password);
        } catch (Exception e){
            msg = String.format("Decryption of file with id '%s' failed.", fileId);
            logger.warn("Decryption of file '{}' with id '{}' failed: {}", path2Encrypted, fileId, e.getMessage(), e);
            throw new RuntimeException(msg);
        }
    }

    public void decrypt(String path2Encrypted, String path2Decrypted, String password, String fileId) {
        try (FileInputStream in = new FileInputStream(path2Encrypted)) {
            try (FileOutputStream os = new FileOutputStream(path2Decrypted)) {
                doDecryption(in, os, password);
            }
        } catch (Exception e){
            msg = String.format("Decryption of file with id '%s' failed.", fileId);
            logger.warn("Decryption of file '{}' with id '{}' failed: {}", path2Encrypted, fileId, e.getMessage(), e);
            throw new RuntimeException(msg);
        }
    }

    private void doDecryption(InputStream in, OutputStream out, String password) throws Exception {
        byte[] salt = new byte[SALT_LENGTH];
        byte[] iv = new byte[IV_LENGTH];

        int saltBytes = in.read(salt);
        int ivBytes = in.read(iv);

        SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), ALGORITHM);

        Cipher ci = Cipher.getInstance(TRANSFORMATION);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(AUT_TAG_LENGTH, iv);
        ci.init(Cipher.ENCRYPT_MODE, skey, parameterSpec);

        processStream(ci, in, out);
    }

    private void processStream(Cipher ci, InputStream in, OutputStream out) throws Exception {
        byte[] inBuffer = new byte[1024];
        int len;
        while ((len = in.read(inBuffer)) != -1) {
            byte[] outBuffer = ci.update(inBuffer, 0, len);
            if (outBuffer != null)
                out.write(outBuffer);
        }
        byte[] outBuffer = ci.doFinal();
        if (outBuffer != null)
            out.write(outBuffer);
    }
}

1 个答案:

答案 0 :(得分:2)

解密时应使用Cipher.DECRYPT_MODE

其他字节是GCM标签(MAC)。它是在加密期间创建的,并在解密期间进行了检查。

在GCM模式下,加密和解密的过程是相同的(XOR),这就是为什么使用Cipher.ENCRYPT_MODE进行解密(除了MAC部分)似乎可以正常工作的原因。