Каковы лучшие практики использования шифрования AES в Android?

90

Почему я задаю этот вопрос:

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

Итак, я создал этот вопрос, чтобы найти «лучшую практику». Я надеюсь, что мы сможем собрать список наиболее важных требований и создать действительно безопасную реализацию!

Я читал про векторы инициализации и соли. Не во всех найденных мной реализациях были эти функции. Так оно тебе нужно? Это сильно увеличивает безопасность? Как вы это реализуете? Должен ли алгоритм вызывать исключения, если зашифрованные данные не могут быть расшифрованы? Или это небезопасно, и он должен просто вернуть нечитаемую строку? Может ли алгоритм использовать Bcrypt вместо SHA?

Что насчет этих двух реализаций, которые я нашел? Они в порядке? Идеально или чего-то не хватает? Что из этого безопасно?

Алгоритм должен взять строку и «пароль» для шифрования, а затем зашифровать строку этим паролем. На выходе снова должна быть строка (шестнадцатеричная или base64?). Конечно, дешифрование тоже должно быть возможным.

Какова идеальная реализация AES для Android?

Реализация №1:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedCrypto implements ICrypto {

        public static final String PROVIDER = "BC";
        public static final int SALT_LENGTH = 20;
        public static final int IV_LENGTH = 16;
        public static final int PBE_ITERATION_COUNT = 100;

        private static final String RANDOM_ALGORITHM = "SHA1PRNG";
        private static final String HASH_ALGORITHM = "SHA-512";
        private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
        private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
        private static final String SECRET_KEY_ALGORITHM = "AES";

        public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
                try {

                        byte[] iv = generateIv();
                        String ivHex = HexEncoder.toHex(iv);
                        IvParameterSpec ivspec = new IvParameterSpec(iv);

                        Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
                        byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
                        String encryptedHex = HexEncoder.toHex(encryptedText);

                        return ivHex + encryptedHex;

                } catch (Exception e) {
                        throw new CryptoException("Unable to encrypt", e);
                }
        }

        public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
                try {
                        Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        String ivHex = encrypted.substring(0, IV_LENGTH * 2);
                        String encryptedHex = encrypted.substring(IV_LENGTH * 2);
                        IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
                        decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
                        byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
                        String decrypted = new String(decryptedText, "UTF-8");
                        return decrypted;
                } catch (Exception e) {
                        throw new CryptoException("Unable to decrypt", e);
                }
        }

        public SecretKey getSecretKey(String password, String salt) throws CryptoException {
                try {
                        PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
                        SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
                        SecretKey tmp = factory.generateSecret(pbeKeySpec);
                        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
                        return secret;
                } catch (Exception e) {
                        throw new CryptoException("Unable to get secret key", e);
                }
        }

        public String getHash(String password, String salt) throws CryptoException {
                try {
                        String input = password + salt;
                        MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
                        byte[] out = md.digest(input.getBytes("UTF-8"));
                        return HexEncoder.toHex(out);
                } catch (Exception e) {
                        throw new CryptoException("Unable to get hash", e);
                }
        }

        public String generateSalt() throws CryptoException {
                try {
                        SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                        byte[] salt = new byte[SALT_LENGTH];
                        random.nextBytes(salt);
                        String saltHex = HexEncoder.toHex(salt);
                        return saltHex;
                } catch (Exception e) {
                        throw new CryptoException("Unable to generate salt", e);
                }
        }

        private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
                SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                byte[] iv = new byte[IV_LENGTH];
                random.nextBytes(iv);
                return iv;
        }

}

Источник: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

Реализация №2:

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Usage:
 * <pre>
 * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
 * ...
 * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
 * </pre>
 * @author ferenc.hechler
 */
public class SimpleCrypto {

    public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String seed, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }


    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }
    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length()/2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2*buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }
    private final static String HEX = "0123456789ABCDEF";
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }

}

Источник: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

каркать
источник
Я пытаюсь реализовать решение 1, но для этого нужны классы. у вас есть полный исходный код?
albanx
1
Нет, извините. Но я заставил его работать, просто удалив implements ICryptoи изменив throws CryptoExceptionна throws Exceptionи так далее. Так что вам больше не понадобятся эти классы.
caw
Но также отсутствует класс HexEncoder? Где я могу это найти?
albanx
Я думаю, HexEncoder является частью библиотеки BouncyCastle. Вы можете просто скачать его. Или вы можете найти в Google "byte [] to hex" и наоборот в Java.
caw
Спасибо, Марко. Но я заметил , что есть 3 метода getSecretKey, getHash, generateSaltв первом варианте осуществления, которые не используются. Возможно, я ошибаюсь, но как этот класс можно использовать для шифрования строки на практике?
albanx

Ответы:

37

Ни одна из реализаций, которые вы даете в своем вопросе, не является полностью правильной, и ни одна из реализаций, которые вы предоставляете, не должна использоваться как есть. Далее я расскажу об аспектах шифрования на основе паролей в Android.

Ключи и хэши

Я начну обсуждать парольную систему с солей. Соль - это случайно сгенерированное число. Это не «выводится». Реализация 1 включает generateSalt()метод, который генерирует криптостойкое случайное число. Поскольку соль важна для безопасности, ее следует хранить в секрете после того, как она будет сгенерирована, хотя ее нужно сгенерировать только один раз. Если это веб-сайт, относительно легко сохранить секретную информацию, но для установленных приложений (для настольных и мобильных устройств) это будет намного сложнее.

Метод getHash()возвращает хэш заданного пароля и соли, объединенные в одну строку. Используемый алгоритм - SHA-512, который возвращает 512-битный хэш. Этот метод возвращает хэш, который полезен для проверки целостности строки, поэтому его можно также использовать, вызывая getHash()только пароль или только соль, поскольку он просто объединяет оба параметра. Поскольку этот метод не будет использоваться в системе шифрования на основе паролей, я не буду его обсуждать.

Метод getSecretKey()извлекает ключ из charмассива паролей и соли в шестнадцатеричной кодировке, возвращенной из generateSalt(). Используемый алгоритм - PBKDF1 (я думаю) от PKCS5 с SHA-256 в качестве хэш-функции и возвращает 256-битный ключ. getSecretKey()генерирует ключ путем многократной генерации хэшей пароля, соли и счетчика (до указанного здесь числа итераций PBE_ITERATION_COUNT, здесь 100), чтобы увеличить время, необходимое для проведения атаки методом перебора. Длина соли должна быть не меньше длины генерируемого ключа, в данном случае не менее 256 бит. Счетчик итераций должен быть установлен как можно дольше, не вызывая неоправданной задержки. Дополнительные сведения о солях и количестве итераций при выводе ключей см. В разделе 4 RFC2898 .

Однако реализация в Java PBE ошибочна, если пароль содержит символы Unicode, то есть те, для представления которых требуется более 8 бит. Как указано в PBEKeySpec, «механизм PBE, определенный в PKCS # 5, смотрит только на 8 младших битов каждого символа». Чтобы обойти эту проблему, вы можете попробовать сгенерировать шестнадцатеричную строку (которая будет содержать только 8-битные символы) всех 16-битных символов в пароле перед ее передачей в PBEKeySpec. Например, «ABC» становится «004100420043». Также обратите внимание, что PBEKeySpec «запрашивает пароль в виде массива символов, поэтому его можно перезаписать [на clearPassword()], когда это будет сделано». (Что касается «защиты строк в памяти», см. Этот вопрос .) Я не вижу никаких проблем,

Шифрование

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

В реализации 1 используется алгоритм шифрования AES/CBC/PKCS5Padding, то есть AES в режиме шифрования Cipher Block Chaining (CBC) с заполнением, определенным в PKCS # 5. (Другие режимы шифрования AES включают режим счетчика (CTR), режим электронной кодовой книги (ECB) и режим счетчика Галуа (GCM). Другой вопрос о переполнении стека содержит ответы, в которых подробно обсуждаются различные режимы шифрования AES и рекомендуемые для использования. Также имейте в виду, что существует несколько атак на шифрование в режиме CBC, некоторые из которых упомянуты в RFC 7457.)

Обратите внимание, что вы должны использовать режим шифрования, который также проверяет зашифрованные данные на целостность (например, аутентифицированное шифрование со связанными данными , AEAD, описанное в RFC 5116). Однако AES/CBC/PKCS5Paddingне обеспечивает проверку целостности, поэтому не рекомендуется . Для целей AEAD рекомендуется использовать секрет, который как минимум вдвое длиннее обычного ключа шифрования, чтобы избежать атак с использованием связанных ключей: первая половина служит ключом шифрования, а вторая половина служит ключом для проверки целостности. (То есть в этом случае сгенерируйте один секрет из пароля и соли и разделите этот секрет на две части.)

Реализация Java

Различные функции в реализации 1 используют для своих алгоритмов конкретного поставщика, а именно «BC». В целом, однако, не рекомендуется запрашивать конкретных поставщиков, поскольку не все поставщики доступны во всех реализациях Java, будь то из-за отсутствия поддержки, во избежание дублирования кода или по другим причинам. Этот совет стал особенно важным после выпуска предварительной версии Android P в начале 2018 года, поскольку некоторые функции от поставщика BC устарели - см. Статью «Изменения криптографии в Android P» в блоге разработчиков Android. См. Также Введение в поставщиков Oracle .

Таким образом, PROVIDERне должно существовать, и строку -BCследует удалить из PBE_ALGORITHM. В этом отношении реализация 2 верна.

Методу неуместно перехватывать все исключения, а скорее обрабатывать только те исключения, которые он может. Реализации, указанные в вашем вопросе, могут вызывать множество проверенных исключений. Метод может заключить в CryptoException только отмеченные исключения или указать эти отмеченные исключения в throwsпредложении. Для удобства здесь может быть уместно обернуть исходное исключение с помощью CryptoException, поскольку классы могут генерировать потенциально много проверенных исключений.

SecureRandom в Android

Как подробно описано в статье «Некоторые мысли о SecureRandom» в блоге разработчиков Android, реализация java.security.SecureRandomв версиях Android до 2013 года имеет недостаток, который снижает вероятность предоставляемых случайных чисел. Этот недостаток можно устранить, как описано в этой статье.

Питер О.
источник
Такое создание двойного секрета, на мой взгляд, немного расточительно, вы могли бы так же легко разделить сгенерированный секрет на два или, если доступно недостаточно битов, добавить счетчик (1 для первого ключа, 2 для второго ключа) к секрет и выполнить один хеш. Нет необходимости выполнять все итерации дважды.
Maarten Bodewes
Спасибо за информацию о HMAC и соли. В этот раз я не буду использовать HMAC, но позже он может быть очень полезен. И в целом это, несомненно, хорошо.
caw
Большое спасибо за все правки и это (сейчас) прекрасное введение в шифрование AES в Java!
caw
1
Должно. getInstanceимеет перегрузку, которая принимает только имя алгоритма. Пример: Cipher.getInstance () Несколько провайдеров, включая Bouncy Castle, могут быть зарегистрированы в реализации Java, и этот вид перегрузки ищет в списке провайдеров один из них, реализующий данный алгоритм. Вы должны попробовать и посмотреть.
Питер О.
1
Да, он будет искать поставщиков в порядке, заданном Security.getProviders () - хотя теперь он также будет проверять, принят ли ключ этим поставщиком во время вызова init (), что позволяет использовать аппаратное шифрование. Подробнее здесь: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Maarten Bodewes 02
18

№2 никогда не должен использоваться, поскольку он использует только «AES» (что означает шифрование текста в режиме ECB, большое «нет-нет») для шифра. Я просто расскажу о №1.

Первая реализация, похоже, соответствует лучшим методам шифрования. С константами в целом все в порядке, хотя и размер соли, и количество итераций для выполнения PBE невелики. Более того, похоже, что это для AES-256, поскольку генерация ключа PBE использует 256 в качестве жестко закодированного значения (позор после всех этих констант). Он использует CBC и PKCS5Padding, что, по крайней мере, вы ожидаете.

Полностью отсутствует какая-либо защита аутентификации / целостности, поэтому злоумышленник может изменить зашифрованный текст. Это означает, что в модели клиент / сервер возможны атаки оракула заполнения. Это также означает, что злоумышленник может попытаться изменить зашифрованные данные. Это, скорее всего, приведет к какой-то ошибке, потому что заполнение или контент не принимаются приложением, но это не та ситуация, в которой вы хотите оказаться.

Обработка исключений и проверка ввода могут быть улучшены, перехват исключений в моей книге всегда неправильный. Более того, класс реализует ICrypt, о котором я не знаю. Я знаю, что иметь в классе только методы без побочных эффектов - это немного странно. Обычно вы делаете их статичными. Буферизация экземпляров Cipher и т. Д. Отсутствует, поэтому каждый требуемый объект создается до безумия. Однако вы можете безопасно удалить ICrypto из определения, как кажется, в этом случае вы также можете реорганизовать код для статических методов (или переписать его, чтобы он был более объектно-ориентированным, на ваш выбор).

Проблема в том, что любая оболочка всегда делает предположения о варианте использования. Следовательно, говорить, что обертка правильная или неправильная, - бессмыслица. Вот почему я всегда стараюсь избегать создания классов-оболочек. Но, по крайней мере, это не кажется явно неправильным.

Maarten Bodewes
источник
Большое спасибо за подробный ответ! Я знаю, что это позор, но я еще не знал раздела проверки кода: D Спасибо за этот совет, я проверю это. Но, на мой взгляд, этот вопрос также подходит сюда, поскольку я хочу не просто просматривать эти фрагменты кода. Вместо этого я хочу спросить всех, какие аспекты важны при реализации шифрования AES в Android. И вы снова правы, этот фрагмент кода предназначен для AES-256. Вы бы сказали, что это вообще безопасная реализация AES-256? Сценарий использования состоит в том, что я просто хочу безопасно хранить текстовую информацию в базе данных.
caw
1
Выглядит неплохо, но идея отсутствия проверок целостности и аутентификации меня бы беспокоила. Если у вас достаточно места, я бы серьезно подумал о добавлении HMAC поверх зашифрованного текста. Тем не менее, поскольку вы, вероятно, пытаетесь просто добавить конфиденциальности, я считаю это большим плюсом, но не прямым требованием.
Maarten Bodewes
Но если цель состоит только в том, чтобы другие не имели доступа к зашифрованной информации, мне не нужен HMAC, верно? Если они изменяют зашифрованный текст и вынуждают получить «неправильный» результат дешифрования, реальной проблемы нет, не так ли?
caw
Если это не входит в ваш сценарий риска, тогда все в порядке. Если они каким-то образом могут инициировать повторную дешифровку системой после изменения зашифрованного текста (атака оракула с заполнением), они могут расшифровать данные, даже не зная ключа. Они не смогут этого сделать, если просто получат данные в системе, у которой нет ключа. Но именно поэтому всегда лучше добавлять HMAC. Лично я считаю систему с AES-128 и HMAC более безопасной, чем AES-256 без - но, как уже говорилось, вероятно, не требуется.
Maarten Bodewes
1
Почему бы не использовать AES в режиме Galois / Counter-mode (AES-GCM), если вам нужна целостность?
Kimvais
1

Вы задали довольно интересный вопрос. Как и во всех алгоритмах, ключ шифрования является «секретным соусом», поскольку, когда он известен широкой публике, все остальное тоже. Итак, вы посмотрите, как Google может найти этот документ.

безопасность

Помимо того, что Google In-App Billing также дает важные мысли о безопасности

billing_best_practices

100раб
источник
Спасибо за эти ссылки! Что именно вы имеете в виду, говоря «когда ключ шифрования отсутствует, все остальное тоже не работает»?
caw
Я имею в виду, что ключ шифрования должен быть безопасным, и если кто-то может его получить, ваши зашифрованные данные ничем не хуже обычного текста. Пожалуйста, проголосуйте, если вы нашли мой ответ в какой-то мере полезным :-)
the100rabh
0

Используйте BouncyCastle Lightweight API. Он обеспечивает 256 AES с PBE и солью.
Вот пример кода, который может шифровать / расшифровывать файлы.

public void encrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(true, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(true, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public void decrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(false, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(false, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                // int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Kelheor
источник
Спасибо! Вероятно, это хорошее и безопасное решение, но я не хочу использовать стороннее программное обеспечение. Я уверен, что AES можно безопасно реализовать самостоятельно.
caw
2
Зависит от того, хотите ли вы включить защиту от атак по побочным каналам. Как правило, вы должны предполагать, что самостоятельно реализовывать криптографические алгоритмы довольно небезопасно . Поскольку AES CBC доступен в библиотеках среды выполнения Java Oracle, вероятно, лучше всего использовать их и использовать библиотеки Bouncy Castle, если алгоритм недоступен.
Maarten Bodewes 01
Отсутствует определение bufочень надеюсь, что это не staticполе). Также похоже, что и то, encrypt()и другое decrypt()не сможет правильно обработать последний блок, если входные данные кратны 1024 байтам.
тк.