Альтернатива использованию SecureRandom для генерации ключа AES и IV

Я пытаюсь зашифровать текст с помощью алгоритма шифрования AES, сохранить этот зашифрованный текст в файл, а затем снова открыть его и расшифровать его. Ниже приводится моя логика шифрования и дешифрования.

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    byte[] stringBytes = clear_text.getBytes();
    byte[] raw = cipher.doFinal(stringBytes);
    return Base64.encodeBase64String(raw);

И это логика расшифровки

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    byte[] raw = Base64.decodeBase64(encText);
    byte[] stringBytes = cipher.doFinal(raw);
    String clear_text = new String(stringBytes, "UTF8");
    return clear_text;

Я получаю исключение BadPaddingSize. Я предполагаю, что при использовании класса SecureRandom оба метода используют разные ключи при шифровании или расшифровке текста. Есть ли способ использовать один и тот же ключ в обеих подпрограммах?


person user_mda    schedule 10.11.2014    source источник
comment
Вы не можете зашифровать, а затем расшифровать с разными ключами (и IV). Вы должны где-то хранить ключ (и IV). Все зависит от вас. Поскольку мы ничего не знаем о вашем приложении, вам нужно уточнить и добавить дополнительный код.   -  person Artjom B.    schedule 10.11.2014
comment
Да, мой вопрос заключался в том, как иметь общий ключ для обоих. Используя secureRandom, я меняю ключ и IV каждый раз, когда пытаюсь расшифровать. Или, другими словами, как сохранить ключ типа Key и использовать его, когда мне нужно? Каков альтернативный способ сгенерировать ключ без использования случайных битов -   -  person user_mda    schedule 10.11.2014


Ответы (1)


Да, вы можете использовать тот же ключ; даже требуется использовать тот же ключ. Однако вы никогда не должны использовать одну и ту же комбинацию клавиш / IV, так как это небезопасно. Так часто вместо этого к зашифрованному тексту добавляется префикс IV.

Обратите внимание, что следующая реализация показывает вам, как сгенерировать случайный IV без SecureRandom, но это немного неискренне, поскольку класс Cipher будет просто использовать значение по умолчанию внутри для создания IV. Для CBC IV может быть известен злоумышленнику, но злоумышленник не должен отличать его от случайных данных.

В этом примере ключевые данные просто хранятся в «константе». Хранение ключа в исходном коде может не обеспечить достаточной безопасности. Вместо этого он часто зашифрован с помощью открытого ключа, пароля, хранится на USB-ключе, хранится на смарт-карте или HSM и т.д.

Однако в Java вы должны использовать _3 _ / _ 4_ для создания ключей из известных данных и IvParameterSpec для известного IV (или Nonce).

import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESWithStaticKeyAndRandomIV {

    private static byte[] KEY = new byte[] { (byte) 0x14, (byte) 0x0b,
            (byte) 0x41, (byte) 0xb2, (byte) 0x2a, (byte) 0x29, (byte) 0xbe,
            (byte) 0xb4, (byte) 0x06, (byte) 0x1b, (byte) 0xda, (byte) 0x66,
            (byte) 0xb6, (byte) 0x74, (byte) 0x7e, (byte) 0x14 };

    public static byte[] encrypt(byte[] plaintext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey key = new SecretKeySpec(KEY, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);

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

            byte[] ciphertext = new byte[iv.length
                    + cipher.getOutputSize(plaintext.length)];
            System.arraycopy(iv, 0, ciphertext, 0, iv.length);
            cipher.doFinal(plaintext, 0, plaintext.length, ciphertext,
                    iv.length);
            return ciphertext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | InvalidParameterSpecException
                | ShortBufferException | IllegalBlockSizeException
                | BadPaddingException e) {
            throw new IllegalStateException(
                    "CBC encryption with standard algorithm should never fail",
                    e);
        }
    }

    public static byte[] decrypt(byte[] ciphertext) throws IllegalBlockSizeException,
            BadPaddingException {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec key = new SecretKeySpec(KEY, "AES");

            if (ciphertext.length < cipher.getBlockSize()) {
                throw new IllegalArgumentException(
                        "Ciphertext too small to contain IV");
            }

            IvParameterSpec ivSpec = new IvParameterSpec(ciphertext, 0,
                    cipher.getBlockSize());
            cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

            byte[] plaintext = new byte[cipher.getOutputSize(ciphertext.length
                    - cipher.getBlockSize())];
            cipher.doFinal(ciphertext, cipher.getBlockSize(), ciphertext.length
                    - cipher.getBlockSize(), plaintext, 0);
            return plaintext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | ShortBufferException
                | InvalidAlgorithmParameterException e) {
            throw new IllegalStateException(
                    "CBC decryption with standard algorithm should be available",
                    e);
        }
    }

    public static void main(String[] args) throws Exception {
           byte[] plaintext = decrypt(encrypt("owlstead".getBytes(StandardCharsets.UTF_8)));
           System.out.println(new String(plaintext, StandardCharsets.UTF_8));
    }
}

С хранилищем ключей (пока вы должны использовать JCEKS):

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESWithStaticKeyAndRandomIV {

    private static final String KEY_ALIAS = "secret";

    private static byte[] KEY = new byte[] { (byte) 0x14, (byte) 0x0b,
            (byte) 0x41, (byte) 0xb2, (byte) 0x2a, (byte) 0x29, (byte) 0xbe,
            (byte) 0xb4, (byte) 0x06, (byte) 0x1b, (byte) 0xda, (byte) 0x66,
            (byte) 0xb6, (byte) 0x74, (byte) 0x7e, (byte) 0x14 };

    private static ProtectionParameter PASSWORD = new KeyStore.PasswordProtection(
            new char[] {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'});

    private final KeyStore store;

    private AESWithStaticKeyAndRandomIV(KeyStore store) {
        this.store = store;
    }

    public byte[] encrypt(byte[] plaintext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey key;
            try {
                key = ((SecretKeyEntry) store.getEntry(KEY_ALIAS, PASSWORD))
                        .getSecretKey();
            } catch (UnrecoverableEntryException | KeyStoreException e) {
                throw new IllegalStateException("What key?", e);
            }
            cipher.init(Cipher.ENCRYPT_MODE, key);

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

            byte[] ciphertext = new byte[iv.length
                    + cipher.getOutputSize(plaintext.length)];
            System.arraycopy(iv, 0, ciphertext, 0, iv.length);
            cipher.doFinal(plaintext, 0, plaintext.length, ciphertext,
                    iv.length);
            return ciphertext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | InvalidParameterSpecException
                | ShortBufferException | IllegalBlockSizeException
                | BadPaddingException e) {
            throw new IllegalStateException(
                    "CBC encryption with standard algorithm should never fail",
                    e);
        }
    }

    public byte[] decrypt(byte[] ciphertext) throws IllegalBlockSizeException,
            BadPaddingException {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey key;
            try {
                key = ((SecretKeyEntry) store.getEntry(KEY_ALIAS, PASSWORD))
                        .getSecretKey();
            } catch (UnrecoverableEntryException | KeyStoreException e) {
                throw new IllegalStateException("What key?", e);
            }

            if (ciphertext.length < cipher.getBlockSize()) {
                throw new IllegalArgumentException(
                        "Ciphertext too small to contain IV");
            }

            IvParameterSpec ivSpec = new IvParameterSpec(ciphertext, 0,
                    cipher.getBlockSize());
            cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

            byte[] plaintext = new byte[cipher.getOutputSize(ciphertext.length
                    - cipher.getBlockSize())];
            cipher.doFinal(ciphertext, cipher.getBlockSize(), ciphertext.length
                    - cipher.getBlockSize(), plaintext, 0);
            return plaintext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | ShortBufferException
                | InvalidAlgorithmParameterException e) {
            throw new IllegalStateException(
                    "CBC decryption with standard algorithm should be available",
                    e);
        }
    }

    public static KeyStore createStoreWithSecretKey() {

        try {
            KeyStore keyStore = KeyStore.getInstance("JCEKS");
            keyStore.load(null);
            SecretKey key = new SecretKeySpec(KEY, "AES");
            keyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(key), PASSWORD);
            return keyStore;
        } catch (KeyStoreException | NoSuchAlgorithmException
                | CertificateException | IOException e) {
            throw new IllegalStateException("Unable to create key store", e);
        }
    }

    public static void main(String[] args) throws Exception {
        AESWithStaticKeyAndRandomIV crypt = new AESWithStaticKeyAndRandomIV(
                createStoreWithSecretKey());

        byte[] plaintext = crypt.decrypt(crypt.encrypt("owlstead"
                .getBytes(StandardCharsets.UTF_8)));
        System.out.println(new String(plaintext, StandardCharsets.UTF_8));
    }
}
person Maarten Bodewes    schedule 10.11.2014
comment
Большое спасибо, Могу ли я использовать ключ, сгенерированный с помощью keytool, и хранить его где-нибудь в моей системе? Если да, то как будет работать капельница? Мне нужно расшифровать эти cleartex-файлы и на других языках, поэтому мне нужен общий ключ и IV, чтобы использовать - person user_mda; 10.11.2014
comment
Добавил код. IV всегда должен быть случайным и либо вычисляться из некоторого уникального значения, либо сохраняться с зашифрованным текстом (как в примере). Он не должен храниться вместе с ключом и, следовательно, не будет находиться в KeyStore интерфейсе. - person Maarten Bodewes; 10.11.2014
comment
привет совстед, спасибо за добавление вашего кода, я пробовал с вашим кодом, но я все еще получаю исключение Illegal blockSize, как вы думаете, это потому, что я сохраняю зашифрованный текст в файле, а затем читаю его из файла и делаю это изменяет заполнение зашифрованной строки? - person user_mda; 11.11.2014
comment
Да, я считаю это вероятным. Вы пробовали использовать _ 1_? - person Maarten Bodewes; 11.11.2014
comment
Я не хочу читать все байты в файле, а только зашифрованный текст. Я предполагаю, что сохранение его в файл изменяет байты, которые при расшифровке вызывают исключение. - person user_mda; 11.11.2014
comment
Эх, нет, этот ключ был статическим, конечно, хотя хранилище ключей не было. Извините, уже забыл код :) Проверьте обработку файлов и кодировку / декодирование или задайте новый вопрос ... - person Maarten Bodewes; 11.11.2014
comment
Будет ли расшифровка терпеть неудачу с одним и тем же ключом, но каждый раз с другим IV? Пытаюсь понять, почему я не могу расшифровать строку при использовании одного и того же ключа - person user_mda; 12.11.2014
comment
Да, это не сработает, хотя при использовании только CBC будет выдано неверное сообщение, если это сообщение того же размера или больше одного блока. Вот почему я сохранил IV с зашифрованным текстом. - person Maarten Bodewes; 12.11.2014
comment
Мои модульные тесты этого решения терпели неудачу при попытке сравнить результаты шифрования / дешифрования 6-байтового значения, потому что расшифровка возвращала неожиданные 16 байтов. В этом примере я изолировал проблему от создания массива открытого текста в расшифровке, которая безуспешно пытается использовать cipher.getOutputSize (..), чтобы получить размер первоначально зашифрованного массива байтов. Он возвращает 6 байтов вместо 16 (т.е. размер блока). Чтобы решить эту проблему, я прибег к копированию всех байтов, отличных от IV, в их собственный массив, а затем использовал простой метод doFinal (byte []), который правильно вернул 6 байтов. - person LeastOne; 11.10.2016