【问题标题】:Cipher's doFinal() doesn't write bytes密码的 doFinal() 不写入字节
【发布时间】:2019-03-28 23:10:26
【问题描述】:

这是我的完整代码:

import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Test {

    public static void main(String[] args) throws Exception {
        encrypt();
        decrypt();
    }

    void encrypt() throws Exception {
        Path file = Paths.get("path/to/file");
        Path backupFile = file.getParent().resolve(file.getFileName().toString() + ".bak");
        Files.deleteIfExists(backupFile);
        Files.copy(file, backupFile);

        SecureRandom secureRandom = new SecureRandom();
        byte[] initializeVector = new byte[96 / Byte.SIZE];
        secureRandom.nextBytes(initializeVector);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec p = new GCMParameterSpec(128, initializeVector);

        try (FileChannel src = FileChannel.open(backupFile, READ);
             FileChannel dest = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {

            SecretKeySpec secretKeySpec =
                new SecretKeySpec(MessageDigest.getInstance("MD5").digest(new byte[]{0x00}), "AES");

            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, p);

            ByteBuffer ivBuffer = ByteBuffer.allocate(Integer.BYTES + cipher.getIV().length);
            ivBuffer.putInt(cipher.getIV().length);
            ivBuffer.put(cipher.getIV());
            ivBuffer.flip();
            dest.write(ivBuffer);

            ByteBuffer readBuf = ByteBuffer.allocateDirect(8192);
            ByteBuffer writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
            while (src.read(readBuf) >= 0) {
                if (cipher.getOutputSize(8192) > writeBuf.capacity()) {
                    writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
                }
                readBuf.flip();

                cipher.update(readBuf, writeBuf);
                writeBuf.flip();
                dest.write(writeBuf);

                readBuf.clear();
                writeBuf.clear();
            }

            if (cipher.getOutputSize(0) > writeBuf.capacity()) {
                writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(0));
            }

            cipher.doFinal(ByteBuffer.allocate(0), writeBuf);

            writeBuf.flip();
            dest.write(writeBuf);

            Files.delete(backupFile);
        } catch (ShortBufferException e) {
            //Should not happen!
            throw new RuntimeException(e);
        }
    }

    void decrypt() throws Exception {
        Path file = Paths.get("path/to/file");
        Path backupFile = file.getParent().resolve(file.getFileName().toString() + ".bak");
        Files.deleteIfExists(backupFile);
        Files.copy(file, backupFile);

        try (FileChannel src = FileChannel.open(backupFile, READ);
             FileChannel dest = FileChannel.open(file, WRITE, TRUNCATE_EXISTING)) {

            ByteBuffer ivLengthBuffer = ByteBuffer.allocate(Integer.BYTES);
            src.read(ivLengthBuffer);
            ivLengthBuffer.flip();
            int ivLength = ivLengthBuffer.getInt();

            ByteBuffer ivBuffer = ByteBuffer.allocate(ivLength);
            src.read(ivBuffer);
            ivBuffer.flip();
            byte[] iv = new byte[ivBuffer.limit()];
            ivBuffer.get(iv);

            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            GCMParameterSpec p = new GCMParameterSpec(128, iv);

            SecretKeySpec secretKeySpec =
                new SecretKeySpec(MessageDigest.getInstance("MD5").digest(new byte[]{0x00}), "AES");

            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, p);

            ByteBuffer readBuf = ByteBuffer.allocateDirect(8192);
            ByteBuffer writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
            while (src.read(readBuf) >= 0) {
                if (cipher.getOutputSize(8192) > writeBuf.capacity()) {
                    writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(8192));
                }
                readBuf.flip();
                cipher.update(readBuf, writeBuf);

                writeBuf.flip();
                dest.write(writeBuf);

                readBuf.clear();
                writeBuf.clear();
            }

            if (cipher.getOutputSize(0) > writeBuf.capacity()) {
                writeBuf = ByteBuffer.allocateDirect(cipher.getOutputSize(0));
            }
            cipher.doFinal(ByteBuffer.allocate(0), writeBuf);
            writeBuf.flip();
            dest.write(writeBuf);

            Files.deleteIfExists(backupFile);
        }
    }

}

我发现一个奇怪的问题:如果原始文件(未加密)大于4KB,在解密时,cipher.update(readBuf, writeBuf) 不会写入缓冲区,cipher.doFinal(ByteBuffer.allocate(0), writeBuf) 也不会写入,最后我的数据丢失了.每次调用cipher.getOutputSize(8192) 都会增加结果,我不知道为什么会这样,但它可能会有所帮助。

为什么会发生,我该如何解决?

【问题讨论】:

  • 您能否将代码扩展为可重现该问题的可运行 sn-p?例如。使用重现问题的数据将srcdest 初始化为ByteArray(Input|Output)Streams。
  • @MarkPeters OK.

标签: java aes-gcm


【解决方案1】:

.update() 很简单; SunJCE 实现了 GCM(和 CCM)要求,即如果身份验证失败,经过身份验证的解密不会释放(任何)明文;请参阅 How come putting the GCM authentication tag at the end of a cipher stream require internal buffering during decryption?https://moxie.org/blog/the-cryptographic-doom-principle/ 。因为标签位于密文的末尾,这意味着它必须缓冲所有密文,直到调用doFinal()(的重载之一)。 (这就是为什么对于一个大文件,writeBufcipher.getOutputSize(8192) 的重新分配会随着您不断读取和缓冲更多数据而不断增长。)

.doFinal() 更难;它应该可以工作。但是,我已经缩小了故障范围:它仅在您使用 ByteBuffers 而不是原始 byte[] 数组时发生——这是在 javax.crypto.CipherSpi.bufferCrypt 中实现的,而不是分派到实现类;并且输出 ByteBuffer 没有后备数组(即直接分配);明文超过4096字节。我将尝试更深入地研究为什么会失败,但同时更改前两个中的任何一个都可以修复它(或将您的数据限制为 4096 字节,但大概您不希望这样)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-01
    • 2014-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多