Как обновить часть зашифрованных данных новыми зашифрованными данными?

Мне нужно зашифровать аудиофайл во время его создания. Я шифрую заголовок фиктивными данными (потому что не знаю фактического размера аудиоданных) при запуске и шифрую аудиоданные на лету. Мой план состоит в том, чтобы обновить заголовок в конце с фактическим размером данных аудиофайла.

Но когда я попытался перезаписать зашифрованные данные заголовка новыми зашифрованными данными заголовка того же размера, используя тот же ключ и IV, и попытался расшифровать позже, я получаю генерируемые ненужные данные.

Почему это происходит, хотя я использую тот же ключ и IV? В приведенном ниже коде я попытался смоделировать то, что я делаю. Создан зашифрованный файл размером 64 байта и создан расшифрованный файл размером 50 байт.

Без обновления: abcdabcdab0123456789012345678901234567890123456789

С обновлением заголовка: ABCDABCDAB÷‹þ@óMCKLZƒÖ^Ô234567890123456789

Ожидаемый результат: ABCDABCDAB0123456789012345678901234567890123456789.

Является ли это правильным подходом для частичного обновления уже зашифрованных данных?

protected void Encrypt()
{

    byte[] numBytes = {'0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9', '0','1','2','3','4','5','6','7','8','9', '0','1','2','3','4','5','6','7','8','9'};
    byte[] smallCase = {'a','b','c','d','a','b','c','d','a','b','c','d','a','b','c','d'};
    byte[] capitalCase = {'A','B','C','D','A','B','C','D','A','B','C','D','A','B','C','D'};

    try {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1And8BIT");
        KeySpec spec = new PBEKeySpec("junglebook".toCharArray(), "Salt".getBytes(), 65536, 256);
        SecretKey tmp = null;
        tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

        /* Encryption cipher initialization. */
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secret);

        AlgorithmParameters params = cipher.getParameters();
        byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

        Log.d("Encryption" + "iv data :", iv.toString());

        /*Open two Cipher ouput streams to the same encrypted file*/
        FileOutputStream os = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.encrypted");
        CipherOutputStream cos = new CipherOutputStream(os,cipher);

        FileOutputStream os1 = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.encrypted");
        CipherOutputStream cos1 = new CipherOutputStream(os1,cipher);

        int offset = 0;
        Log.d("Encryption", "Writing cipher text to output file");
        //Write 16 bytes header data with smallCase array
        cos.write(smallCase, offset, 16);
        // write 40 bytes actual data
        cos.write(numBytes, offset, 40);

        FileOutputStream ivStream = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/iv.dat");
        if (ivStream != null) {
            Log.d("Encryption", "Writing iv data to output file");
            ivStream.write(iv);
        }
        cos.close();

        // Overwrite header data with capitalCase array data
        cos1.write(capitalCase, offset, 16);
        cos1.close();

        ivStream.close();

    }catch (Exception e) {
        e.printStackTrace();
    }
}

protected void Decrypt()
{
    byte[] dBytes = new byte[200];

    try {

        Log.d("Decryption", "Reading iv data ");
        File f1 = new File(sdCard.getAbsolutePath()+"/Notes/iv.dat");
        byte[] newivtext = new byte[(int)f1.length()];
        FileInputStream readivStream = new FileInputStream(sdCard.getAbsolutePath()+"/Notes/iv.dat");
        if(readivStream != null) {
            readivStream.read(newivtext);
        }

        // Generate the secret key from same password and salt used in encryption
        SecretKeyFactory dfactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1And8BIT");
        KeySpec dspec = new PBEKeySpec("junglebook".toCharArray(), "Salt".getBytes(), 65536, 256);
        SecretKey dtmp = dfactory.generateSecret(dspec);
        SecretKey dsecret = new SecretKeySpec(dtmp.getEncoded(), "AES");

        // Initialize dcipher
        Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        dcipher.init(Cipher.DECRYPT_MODE, dsecret, new IvParameterSpec(newivtext));

        FileInputStream inputStream = new FileInputStream(sdCard.getAbsolutePath()+"/Notes/sample.encrypted");
        CipherInputStream cis = new CipherInputStream(inputStream,dcipher);
        FileOutputStream os = new FileOutputStream(sdCard.getAbsolutePath() + "/Notes/sample.decrypted");

        int b = cis.read(dBytes);
        while(b != -1) {
            Log.d("Decryption","Bytes decrypted" + b);
            os.write(dBytes, 0, b);
            b = cis.read(dBytes);
        }
        cis.close();
        os.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

person Hithendra Nath    schedule 27.11.2017    source источник
comment
Вы используете CBC. Если вы посмотрите на страницу википедии, то заметите, что каждый блок зависит от предыдущего блока. Если вы обновляете блок, вам необходимо повторно шифровать каждый последующий блок.   -  person Phylogenesis    schedule 27.11.2017
comment
Спасибо за быстрый ответ Филогенез. Какой режим подходит для таких ситуаций? Какие-либо предложения? Кстати, я попробовал режим GCM с NOPADDING. Но в итоге я получил AEADBadTagException.   -  person Hithendra Nath    schedule 27.11.2017
comment
возможно, вы могли бы использовать режим CTR, и тогда вам нужно будет обновить только один блок   -  person gusto2    schedule 27.11.2017
comment
@gusto2 Спасибо за ответ. Я попробовал режим CTR с PKCS5Padding. На этот раз я получаю другой результат, например ABCDABCDAB♠♠♠♠♠♠6789012345678901234567890123456789. Дополнительные 6 байтов кажутся заполненными байтами. Я не понимаю, почему эти дополнительные байты генерируются. Эти дополненные байты перезаписывают фактические данные.   -  person Hithendra Nath    schedule 28.11.2017


Ответы (1)


Я предлагаю вам обновить несколько вещей:

  1. вы открываете несколько потоков вывода в один и тот же файл, что очень странно, среда выполнения не должна позволять вам это делать. Так что - пишите только с одним выходом, если хотите каких-то предсказуемых результатов.

  2. Вы можете прочитать о режиме работы, см. в режиме CRT не используется padding и позволяет обновить только часть зашифрованного текста (при условии, что вы не используете аутентифицированное шифрование). Так что AES/CTR/NoPadding может решить вашу проблему. (и лишних байтов быть не должно, если вы все делаете правильно)

  3. вы можете обновить часть файла с помощью RandomAccessFile и перезапишите часть зашифрованного текста, что необходимо.

person gusto2    schedule 28.11.2017
comment
Спасибо... AES/CRT/NoPadding не генерирует лишних байтов. Теперь я могу правильно расшифровать. - person Hithendra Nath; 28.11.2017
comment
Я хочу записать обновленный заголовок в начале файла, перезаписав фиктивный заголовок. Итак, я использовал несколько выходных потоков. Это действительно сработало. но, возможно, это неправильная практика. Если я использую один CipherOutputStream, я не могу обновить в начале файла. Невозможно использовать CipherOutputStream с RandomAccessFile. Из вашего комментария вы хотите, чтобы я сгенерировал зашифрованные байты, используя doFinal, и перезаписал заголовок, используя RandomAccessFile. Верно ли это понимание? - person Hithendra Nath; 28.11.2017
comment
@HithendraNath действительно, это идея. Перезапишите содержимое файла зашифрованным массивом байтов. Примечание - для начала файла все должно быть в порядке. Если вы хотите перезаписать другую часть файла, это может быть сложнее - person gusto2; 28.11.2017
comment
Я понимаю. Мне до сих пор не ясно, почему GCM с NOPADDING не работает. Из предоставленного списка режимов я понял, что лучше всего использовать GCM. использование режима CTR обеспечит достаточную безопасность? - person Hithendra Nath; 28.11.2017
comment
CTR обеспечивает достаточную безопасность в том, что касается конфиденциальности, но не целостности. Режим GCM обеспечивает встроенную целостность, поэтому, если вы перезаписываете часть зашифрованного текста, расшифровка не удалась. В других режимах (CTR, CBC, OFC, ..) вы должны обеспечить целостность самостоятельно (например, вычисление HMAC зашифрованного текста) - person gusto2; 28.11.2017
comment
Таким образом, всякий раз, когда требуется модифицировать шифрованные данные, лучше всего использовать режим CTR. или есть какие-то способы добиться того же? - person Hithendra Nath; 28.11.2017
comment
Режим CTR позволяет изменять зашифрованный текст без необходимости повторного шифрования остальных данных. Это очень эффективно, т.е. для шифрования диска. Проблема в том, как предотвратить вредоносные модификации? (поиск authenticated encryption). Это зависит от вас, но обычной практикой является наличие некоторого тега аутентификации (например, HMAC зашифрованного текста). Это нужно пересчитать. - person gusto2; 28.11.2017
comment
Большое спасибо за ваше драгоценное время и предложения. - person Hithendra Nath; 28.11.2017
comment
В этом примере я использовал один IV. Но я получаю сообщение об ошибке IV, уже использованное при повторном шифровании обновленного заголовка? Любая помощь была бы отличной? - person Hithendra Nath; 20.12.2017
comment
Как мы можем использовать несколько IV в этом сценарии? - person Hithendra Nath; 21.12.2017
comment
@HithendraNath, вы изменили код, но мы понятия не имеем, что / где вы получаете исключение ... и - зачем вам использовать несколько IV? - person gusto2; 21.12.2017
comment
Ваши предложения по этому вопросу работают нормально. Но когда я попытался реализовать то же самое на Android (генерация ключей с использованием поставщика AndroidKeyStore), я столкнулся с проблемой. Вот почему я сомневаюсь, что для этого необходимо несколько IV. Я провел этот эксперимент здесь > stackoverflow.com/questions/47917005/ В случае необходимости нескольких IV, как достичь моей цели по обновлению заголовка? потому что я перезаписываю первый IV. - person Hithendra Nath; 21.12.2017