Каковы рекомендации по использованию шифрования AES в Android?


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

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

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

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

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

алгоритм должен взять строку и" пароль " для шифрования, а затем зашифровать строку с этим паролем. Выход должен быть строкой (hex или 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

5 83

5 ответов:

ключи и хэши

я начну обсуждать систему на основе паролей с солями. Соль-это случайно сгенерированное число. Это не "выводится". Осуществления 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, однако, является ошибка, если пароль содержит символы Юникода, то есть те, которые требуют представления более 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 и рекомендуемые для использования.)

если зашифрованный текст будет доступен посторонним, то примените код аутентификации сообщения или MAC к зашифрованным данным (и опционально дополнительные параметры) рекомендуется для защиты его целостности. Здесь популярны хэш-маки, или общего компьютеров, которые основаны на алгоритме SHA-1, SHA-256 и или других безопасных хэш-функций. Однако если используется MAC, используя секрет, который по крайней мере в два раза длиннее, чем обычный ключ шифрования, чтобы избежать связанных ключевых атак: первая половина служит ключом шифрования, а вторая половина служит ключом для MAC. (То есть в этом случае создайте один секрет из пароля и посолите и разделите этот секрет надвое.)

Реализация Java

различные функции в реализации 1 использует определенный поставщик, а именно " BC", для своих алгоритмов. В целом, однако, не рекомендуется запрашивать конкретные провайдеры, так как не все провайдеры доступны на всех реализациях Java, смотрите введение в поставщиков Oracle.

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

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

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

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

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

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

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

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

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

кроме того, Google в приложении Биллинг также дает мысли о безопасности, которая является проницательным, а также

billing_best_practices

используйте 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();
    }
}

Я нашел хорошую реализацию здесь : http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html и https://github.com/nelenkov/android-pbe Это также было полезно в моих поисках достаточно хорошей реализации AES для Android