Каковы наилучшие методы использования 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

Solutions Collecting From Web of "Каковы наилучшие методы использования AES-шифрования в 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) и режим счетчика Galois (GCM).)

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

Реализация Java

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

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

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

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

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

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

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

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

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

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

Кроме того, Google In-App Billing также дает представление о безопасности, которая также проницательна

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