Які найкращі практики використання шифрування 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, я думаю. Ви можете просто завантажити його. Або ви можете погуглити "byte [] до 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 .

Однак реалізація в PBE Java є хибною, якщо пароль містить символи Unicode, тобто ті, для подання яких потрібно більше 8 бітів. Як зазначено в PBEKeySpec"механізм PBE, визначений у PKCS # 5, розглядає лише 8 біт нижчого порядку кожного символу". Щоб вирішити цю проблему, ви можете спробувати створити шістнадцятковий рядок (який буде містити лише 8-бітові символи) усіх 16-бітових символів у паролі, перш ніж передавати його PBEKeySpec. Наприклад, "ABC" стає "004100420043". Зауважте також, що PBEKeySpec "запитує пароль як масив символів, тому його можна замінити [за допомогою clearPassword()], коли закінчите". (Що стосується "захисту рядків у пам'яті", див. Це запитання .) Однак я не бачу жодних проблем,

Шифрування

Після створення ключа ми можемо використовувати його для шифрування та дешифрування тексту.

У реалізації 1 використовується алгоритм шифрування AES/CBC/PKCS5Padding, тобто AES у режимі шифрування блоків шифрування (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.SecureRandomAndroid-версій до 2013 року має недолік, який зменшує силу випадкових чисел, які він забезпечує. Цю ваду можна пом'якшити, як описано в цій статті.


На мою думку, це подвійне генерування секретів трохи марнотратне, ви можете так само легко розділити згенерований секрет на дві частини, або - якщо недостатньо бітів - додати лічильник (1 для першого ключа, 2 для другого ключа) до та виконайте один хеш. Не потрібно виконувати всі ітерації двічі.
Maarten Bodewes

Дякуємо за інформацію про HMAC та сіль. Цього разу я не буду використовувати HMAC, але пізніше це може бути дуже корисно. І взагалі, це добре, без сумніву.
кава

Щиро дякую за всі редагування та цей (зараз) чудовий вступ до шифрування AES на Java!
caw 02

1
Це повинно було. getInstanceмає перевантаження, яке приймає лише назву алгоритму. Приклад: Cipher.getInstance () У реалізації Java може бути зареєстровано кілька постачальників, включаючи Bouncy Castle, і такий тип перевантаження здійснює пошук у списку постачальників одного з них, який реалізує даний алгоритм. Спробуйте спробувати і подивіться.
Петро О.

1
Так, він буде шукати постачальників у порядку, визначеному Security.getProviders () - хоча тепер він також перевірить, чи прийнятий ключ цим постачальником під час виклику init (), що дозволяє апаратне шифрування. Детальніше тут: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Маартен Бодеус

18

№ 2 ніколи не слід використовувати, оскільки він використовує лише "AES" (що означає шифрування в режимі ЄЦБ тексту, велике ні-ні) для шифру. Я просто поговорю про №1.

Здається, перша реалізація відповідає найкращим практикам шифрування. Як правило, константи в порядку, хоча як розмір солі, так і кількість ітерацій для проведення PBE знаходяться на короткій стороні. Крім того, схоже, це стосується AES-256, оскільки генерація ключів PBE використовує 256 як жорстко закодовану величину (ганьба для всіх цих констант). Він використовує CBC та PKCS5Padding, що є принаймні тим, що ви очікували.

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

Обробка винятків та перевірка вхідних даних можуть бути покращені, оскільки в моїй книзі вилучення завжди є неправильним. Furhtermore, клас реалізує ICrypt, чого я не знаю. Я знаю, що мати у класі лише методи без побічних ефектів трохи дивно. Зазвичай ви робите ці статичні. Немає буферизації екземплярів Cipher тощо, тому кожен необхідний об'єкт створюється ad-nauseum. Тим не менш, ви можете безпечно видалити ICrypto з того визначення, яке здається, у такому випадку ви також можете переробити код на статичні методи (або переписати його, щоб він був більш об'єктно-орієнтованим, на ваш вибір).

Проблема полягає в тому, що будь-яка обгортка завжди робить припущення щодо варіанту використання. Отже, говорити, що обгортка правильна чи неправильна, - отже, двоярусне. Ось чому я завжди намагаюся уникати створення класів обгортки. Але принаймні це не здається явно неправильним.


Щиро дякую за цю детальну відповідь! Я знаю, що це ганьба, але я ще не знав розділу огляду коду: D Дякую за цю підказку, я перевірю це. Але це питання також міститься тут, на мій погляд, оскільки я не просто хочу переглянути огляд цих фрагментів коду. Натомість я хочу запитати вас усіх, які аспекти важливі при впровадженні шифрування AES в Android. І ви знову праві, цей фрагмент коду призначений для AES-256. Отже, ви б сказали, що це загалом безпечна реалізація AES-256? Варіант використання полягає в тому, що я просто хочу безпечно зберігати текстову інформацію в базі даних.
caw

1
Це виглядає добре, але думка про відсутність перевірок цілісності та автентифікації мене б турбувала. Якщо у вас достатньо місця, я б серйозно задумався над додаванням HMAC над зашифрованим текстом. Тим не менш, оскільки ви, мабуть, намагаєтесь просто додати конфіденційність, я вважав би це великим плюсом, але не безпосередньо вимогою.
Maarten Bodewes

Але якщо намір полягає лише в тому, щоб інші не мали доступу до зашифрованої інформації, мені не потрібен HMAC, так? Якщо вони змінюють зашифрований текст і примушують "неправильний" результат дешифрування, справжньої проблеми немає, чи не так?
caw

Якщо це не у вашому сценарії ризику, то це нормально. Якщо вони можуть як-небудь викликати повторне розшифрування системою після зміни тексту шифру (атака оракула padding), тоді вони можуть розшифрувати дані, ніколи не знаючи ключа. Вони не можуть цього зробити, якщо просто отримують дані в системі, яка не має ключа. Але тому додати HMAC завжди найкраще. Особисто я вважав би систему з AES-128 та HMAC безпечнішою, ніж AES-256 без - але, як уже було сказано, ймовірно, не потрібно.
Maarten Bodewes

1
Чому б не використовувати AES у режимі Galois / Counter-mode (AES-GCM), якщо ви хочете цілісність?
Кімвейс

1

Ви задали досить цікаве питання. Як і у всіх алгоритмах, ключ шифру є "секретним соусом", оскільки, як тільки це стане відомо громадськості, і все інше теж. Отже, ви шукаєте шляхи до цього документа від Google

безпеки

Крім того, Google In-App Billing також висловлює думки про безпеку, яка також глибока

billing_best_practices


Дякую за ці посилання! Що саме ви маєте на увазі під словом "коли ключ шифру вийшов, все інше теж вийшов"?
caw

Я маю на увазі, що ключ шифрування повинен бути захищеним, якщо хтось зможе його отримати, тоді ваші зашифровані дані добрі як відкритий текст. Будь ласка, проголосуйте, якщо ви знайшли мою відповідь корисною до певної міри :-)
the100rabh 02

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();
    }
}

Дякую! Це, мабуть, хороше та безпечне рішення, але я не хочу використовувати сторонні програми. Я впевнений, що повинна бути можлива безпечна реалізація AES самостійно.
caw

2
Залежить від того, чи хочете ви включити захист від атак бічних каналів. Як правило, слід вважати, що реалізовувати криптографічні алгоритми самостійно досить небезпечно . Оскільки AES CBC доступний у бібліотеках Java для виконання Oracle, можливо, найкраще використовувати їх і використовувати бібліотеки Bouncy Castle, якщо алгоритм недоступний.
Maarten Bodewes

У ньому відсутнє визначення bufдуже сподіваюся, це не staticполе). Він також виглядає як обидва, encrypt()і decrypt()не зможе правильно обробити остаточний блок, якщо вхідні дані кратні 1024 байтам.
тк.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.