Каковы рекомендации по использованию шифрования 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;
}
}
реализация #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 ответов:
ключи и хэши
я начну обсуждать систему на основе паролей с солями. Соль-это случайно сгенерированное число. Это не "выводится". Осуществления 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 в приложении Биллинг также дает мысли о безопасности, которая является проницательным, а также
используйте 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