Pourquoi je pose cette question:
Je sais qu'il y a eu beaucoup de questions sur le cryptage AES, même pour Android. Et il existe de nombreux extraits de code si vous recherchez sur le Web. Mais sur chaque page, dans chaque question de Stack Overflow, je trouve une autre implémentation avec des différences majeures.
J'ai donc créé cette question pour trouver une "meilleure pratique". J'espère que nous pourrons rassembler une liste des exigences les plus importantes et mettre en place une implémentation vraiment sécurisée!
J'ai lu sur les vecteurs d'initialisation et les sels. Toutes les implémentations que j'ai trouvées n'avaient pas ces fonctionnalités. Alors en avez-vous besoin? Augmente-t-il beaucoup la sécurité? Comment le mettez-vous en œuvre? L'algorithme devrait-il déclencher des exceptions si les données chiffrées ne peuvent pas être déchiffrées? Ou est-ce non sécurisé et il devrait simplement renvoyer une chaîne illisible? L'algorithme peut-il utiliser Bcrypt au lieu de SHA?
Qu'en est-il de ces deux implémentations que j'ai trouvées? Est-ce qu'ils vont bien? Parfait ou il manque des choses importantes? Qu'est-ce qui est sécurisé?
L'algorithme doit prendre une chaîne et un «mot de passe» pour le cryptage, puis crypter la chaîne avec ce mot de passe. La sortie doit être à nouveau une chaîne (hexadécimale ou base64?). Le décryptage devrait également être possible, bien sûr.
Quelle est l'implémentation AES parfaite pour Android?
Mise en œuvre n ° 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;
}
}
Mise en œuvre n ° 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));
}
}
Source: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml
la source
implements ICrypto
et en changeantthrows CryptoException
enthrows Exception
et ainsi de suite. Vous n'aurez donc plus besoin de ces cours.getSecretKey
,getHash
,generateSalt
dans la première mise en œuvre qui ne sont pas utilisés. Peut-être que je me trompe, mais comment cette classe pourrait-elle être utilisée pour crypter une chaîne en pratique?Réponses:
Aucune des implémentations que vous donnez dans votre question n'est entièrement correcte, et aucune des implémentations que vous donnez ne doit être utilisée telle quelle. Dans ce qui suit, je discuterai des aspects du cryptage par mot de passe sous Android.
Clés et hachages
Je vais commencer à discuter du système basé sur les mots de passe avec des sels. Le sel est un nombre généré aléatoirement. Ce n'est pas «déduit». La mise en œuvre 1 comprend une
generateSalt()
méthode qui génère un nombre aléatoire cryptographiquement fort. Parce que le sel est important pour la sécurité, il doit être gardé secret une fois qu'il est généré, bien qu'il ne doive être généré qu'une seule fois. S'il s'agit d'un site Web, il est relativement facile de garder le secret du sel, mais pour les applications installées (pour les ordinateurs de bureau et les appareils mobiles), ce sera beaucoup plus difficile.La méthode
getHash()
renvoie un hachage du mot de passe et du sel donnés, concaténés en une seule chaîne. L'algorithme utilisé est SHA-512, qui renvoie un hachage de 512 bits. Cette méthode renvoie un hachage utile pour vérifier l'intégrité d'une chaîne, elle peut donc aussi bien être utilisée en appelantgetHash()
avec juste un mot de passe ou juste un sel, car elle concatène simplement les deux paramètres. Étant donné que cette méthode ne sera pas utilisée dans le système de cryptage basé sur un mot de passe, je n'en parlerai pas davantage.La méthode
getSecretKey()
dérive une clé d'unchar
tableau du mot de passe et d'un sel codé en hexadécimal, comme renvoyé degenerateSalt()
. L'algorithme utilisé est PBKDF1 (je pense) de PKCS5 avec SHA-256 comme fonction de hachage, et renvoie une clé de 256 bits.getSecretKey()
génère une clé en générant à plusieurs reprises des hachages du mot de passe, du sel et d'un compteur (jusqu'au nombre d'itérations donné dansPBE_ITERATION_COUNT
, ici 100) afin d'augmenter le temps nécessaire pour monter une attaque par force brute. La longueur du sel doit être au moins aussi longue que la clé générée, dans ce cas, au moins 256 bits. Le nombre d'itérations doit être défini aussi longtemps que possible sans provoquer de retard déraisonnable. Pour plus d'informations sur les sels et le nombre d'itérations dans la dérivation de clé, voir la section 4 de la RFC2898 .L'implémentation dans le PBE de Java, cependant, est imparfaite si le mot de passe contient des caractères Unicode, c'est-à-dire ceux qui nécessitent plus de 8 bits pour être représentés. Comme indiqué dans
PBEKeySpec
, "le mécanisme PBE défini dans PKCS # 5 ne regarde que les 8 bits de poids faible de chaque caractère". Pour contourner ce problème, vous pouvez essayer de générer une chaîne hexadécimale (qui contiendra uniquement des caractères 8 bits) de tous les caractères 16 bits du mot de passe avant de le transmettrePBEKeySpec
. Par exemple, "ABC" devient "004100420043". Notez également que PBEKeySpec "demande le mot de passe sous forme de tableau de caractères, afin qu'il puisse être écrasé [avecclearPassword()
] une fois terminé". (En ce qui concerne la "protection des chaînes en mémoire", voir cette question .) Je ne vois cependant aucun problème,Chiffrement
Une fois qu'une clé est générée, nous pouvons l'utiliser pour crypter et décrypter du texte.
Dans l'implémentation 1, l'algorithme de chiffrement utilisé est
AES/CBC/PKCS5Padding
, c'est-à-dire AES en mode de chiffrement Cipher Block Chaining (CBC), avec un remplissage défini dans PKCS # 5. (Les autres modes de chiffrement AES incluent le mode compteur (CTR), le mode livre de codes électronique (ECB) et le mode compteur Galois (GCM). Une autre question sur Stack Overflow contient des réponses qui discutent en détail des différents modes de chiffrement AES et de ceux recommandés à utiliser. Sachez également qu'il existe plusieurs attaques contre le cryptage en mode CBC, dont certaines sont mentionnées dans la RFC 7457.)Notez que vous devez utiliser un mode de cryptage qui vérifie également l'intégrité des données cryptées (par exemple, cryptage authentifié avec les données associées , AEAD, décrit dans la RFC 5116). Cependant,
AES/CBC/PKCS5Padding
ne fournit pas de vérification d'intégrité, donc elle seule n'est pas recommandée . Pour les besoins de l'AEAD, il est recommandé d'utiliser un secret qui est au moins deux fois plus long qu'une clé de cryptage normale, pour éviter les attaques de clé associées: la première moitié sert de clé de cryptage et la seconde moitié sert de clé pour le contrôle d'intégrité. (Autrement dit, dans ce cas, générez un seul secret à partir d'un mot de passe et d'un sel, et divisez ce secret en deux.)Implémentation Java
Les différentes fonctions de l'implémentation 1 utilisent un fournisseur spécifique, à savoir "BC", pour ses algorithmes. En général, cependant, il n'est pas recommandé de demander des fournisseurs spécifiques, car tous les fournisseurs ne sont pas disponibles sur toutes les implémentations Java, que ce soit par manque de support, pour éviter la duplication de code ou pour d'autres raisons. Ce conseil est devenu particulièrement important depuis la sortie de l'aperçu d'Android P début 2018, car certaines fonctionnalités du fournisseur «BC» y sont devenues obsolètes - voir l'article «Changements de cryptographie dans Android P» dans le blog des développeurs Android. Consultez également l' introduction aux fournisseurs Oracle .
Ainsi,
PROVIDER
ne doit pas exister et la chaîne-BC
doit être suppriméePBE_ALGORITHM
. La mise en œuvre 2 est correcte à cet égard.Il est inapproprié pour une méthode d'intercepter toutes les exceptions, mais plutôt de ne gérer que les exceptions qu'elle peut. Les implémentations données dans votre question peuvent générer diverses exceptions vérifiées. Une méthode peut choisir d'encapsuler uniquement les exceptions vérifiées avec CryptoException ou de spécifier ces exceptions vérifiées dans la
throws
clause. Pour plus de commodité, encapsuler l'exception d'origine avec CryptoException peut être approprié ici, car il existe potentiellement de nombreuses exceptions vérifiées que les classes peuvent lever.SecureRandom
sous AndroidComme détaillé dans l'article «Quelques réflexions sur le hasard sécurisé», dans le blog des développeurs Android, la mise
java.security.SecureRandom
en œuvre de dans les versions d'Android avant 2013 présente un défaut qui réduit la force des nombres aléatoires qu'elle délivre. Cette faille peut être atténuée comme décrit dans cet article.la source
getInstance
a une surcharge qui ne prend que le nom de l'algorithme. Exemple: Cipher.getInstance () Plusieurs fournisseurs, y compris Bouncy Castle, peuvent être enregistrés dans l'implémentation Java et ce type de surcharge recherche dans la liste des fournisseurs l'un d'entre eux qui implémente l'algorithme donné. Vous devriez l'essayer et voir.# 2 ne doit jamais être utilisé car il utilise uniquement "AES" (ce qui signifie un cryptage en mode ECB sur du texte, un grand non-non) pour le chiffrement. Je vais juste parler du n ° 1.
La première implémentation semble adhérer aux meilleures pratiques de chiffrement. Les constantes sont généralement correctes, bien que la taille du sel et le nombre d'itérations pour effectuer PBE soient sur le côté court. De plus, cela semble être pour AES-256 puisque la génération de clé PBE utilise 256 comme valeur codée en dur (dommage après toutes ces constantes). Il utilise CBC et PKCS5Padding, ce qui est au moins ce à quoi vous vous attendez.
Il manque complètement toute protection d'authentification / d'intégrité, de sorte qu'un attaquant peut modifier le texte chiffré. Cela signifie que les attaques d'oracle de remplissage sont possibles dans un modèle client / serveur. Cela signifie également qu'un attaquant peut essayer de modifier les données chiffrées. Cela entraînera probablement une erreur quelque part car le remplissage ou le contenu n'est pas accepté par l'application, mais ce n'est pas une situation dans laquelle vous souhaitez vous trouver.
La gestion des exceptions et la validation des entrées pourraient être améliorées, la capture d'exception est toujours erronée dans mon livre. De plus, la classe implémente ICrypt, ce que je ne sais pas. Je sais que n'avoir que des méthodes sans effets secondaires dans une classe est un peu bizarre. Normalement, vous les rendriez statiques. Il n'y a pas de mise en mémoire tampon des instances de Cipher, etc., donc chaque objet requis est créé ad nauseum. Cependant, vous pouvez supprimer ICrypto de la définition en toute sécurité, dans ce cas, vous pouvez également refactoriser le code en méthodes statiques (ou le réécrire pour qu'il soit plus orienté objet, votre choix).
Le problème est que tout wrapper émet toujours des hypothèses sur le cas d'utilisation. Dire qu'un emballage a raison ou tort est donc superposé. C'est pourquoi j'essaie toujours d'éviter de générer des classes wrapper. Mais au moins, cela ne semble pas explicitement faux.
la source
Vous avez posé une question assez intéressante. Comme pour tous les algorithmes, la clé de chiffrement est la "sauce secrète", puisqu'une fois qu'elle est connue du public, tout le reste l'est aussi. Vous cherchez donc les moyens de ce document par Google
Sécurité
Outre Google In-App Billing, donne également des réflexions sur la sécurité, ce qui est également perspicace
billing_best_practices
la source
Utilisez l'API BouncyCastle Lightweight. Il fournit 256 AES avec PBE et sel.
Voici un exemple de code, qui peut crypter / décrypter des fichiers.
la source
buf
(j'espère vraiment que ce n'est pas unstatic
champ). Il ressemble également aux deuxencrypt()
etdecrypt()
ne traitera pas correctement le bloc final si l'entrée est un multiple de 1024 octets.J'ai trouvé une belle implémentation ici: http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html et https://github.com/nelenkov/android-pbe Cela a également été utile dans ma quête d'une implémentation AES assez bonne pour Android
la source