Функция шифрования doFinal () не записывает байты - PullRequest
0 голосов
/ 24 октября 2018

Это мой полный код:

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) увеличивает результат, я не знаю, почему это происходит, но это может помочь.

Почему это происходит и как я могу это исправить?

1 Ответ

0 голосов
/ 25 октября 2018

.update() легко;SunJCE реализует требование GCM (и CCM) о том, что аутентифицированная расшифровка не освобождает (любой) открытый текст, если аутентификация не удалась;см. Почему для помещения тега аутентификации GCM в конец потока шифрования требуется внутренняя буферизация во время расшифровки? и https://moxie.org/blog/the-cryptographic-doom-principle/.Поскольку тег находится в конце зашифрованного текста, это означает, что он должен буферизовать весь зашифрованный текст до тех пор, пока не будет вызвана одна из перегрузок doFinal().(Вот почему для большого файла ваше перераспределение от writeBuf до cipher.getOutputSize(8192) продолжает расти, поскольку вы продолжаете читать и буферизовать больше данных.)

.doFinal() труднее;это должно работать.Однако я сузил ошибку: это происходит только тогда, когда вы используете ByteBuffer s, а не необработанные byte[] массивы - что реализовано в javax.crypto.CipherSpi.bufferCrypt вместо отправки в класс реализации;и вывод ByteBuffer не имеет резервного массива (т.е. был назначен напрямую);и открытый текст более 4096 байт.Я попытаюсь глубже понять, почему это не удается, но тем временем изменение одного из первых двух исправляет это (или ограничивает ваши данные до 4096 байт, но, вероятно, вы этого не хотите).

...