Warum ich diese Frage stelle:
Ich weiß, dass es viele Fragen zur AES-Verschlüsselung gab, auch für Android. Und es gibt viele Codefragmente, wenn Sie im Web suchen. Aber auf jeder einzelnen Seite, in jeder Frage zum Stapelüberlauf, finde ich eine andere Implementierung mit großen Unterschieden.
Also habe ich diese Frage erstellt, um eine "Best Practice" zu finden. Ich hoffe, wir können eine Liste der wichtigsten Anforderungen zusammenstellen und eine Implementierung einrichten, die wirklich sicher ist!
Ich habe über Initialisierungsvektoren und Salze gelesen. Nicht alle Implementierungen, die ich gefunden habe, hatten diese Funktionen. Also brauchst du es Erhöht es die Sicherheit erheblich? Wie setzen Sie es um? Sollte der Algorithmus Ausnahmen auslösen, wenn die verschlüsselten Daten nicht entschlüsselt werden können? Oder ist das unsicher und sollte nur eine unlesbare Zeichenfolge zurückgeben? Kann der Algorithmus Bcrypt anstelle von SHA verwenden?
Was ist mit diesen beiden Implementierungen, die ich gefunden habe? Sind sie in Ordnung? Perfekt oder fehlen einige wichtige Dinge? Was davon ist sicher?
Der Algorithmus sollte eine Zeichenfolge und ein "Passwort" zur Verschlüsselung verwenden und die Zeichenfolge dann mit diesem Passwort verschlüsseln. Die Ausgabe sollte wieder eine Zeichenfolge (hex oder base64?) Sein. Natürlich sollte auch eine Entschlüsselung möglich sein.
Was ist die perfekte AES-Implementierung für Android?
Implementierung Nr. 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;
}
}
Implementierung Nr. 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));
}
}
Quelle: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml
quelle
implements ICrypto
und geändertthrows CryptoException
habethrows Exception
und so weiter. Sie brauchen diese Klassen also nicht mehr.getSecretKey
gibtgetHash
,generateSalt
die nicht verwendet werden. Vielleicht irre ich mich, aber wie könnte diese Klasse verwendet werden, um eine Zeichenfolge in der Praxis zu verschlüsseln?Antworten:
Keine der von Ihnen in Ihrer Frage angegebenen Implementierungen ist völlig korrekt, und keine der von Ihnen angegebenen Implementierungen sollte unverändert verwendet werden. Im Folgenden werde ich Aspekte der kennwortbasierten Verschlüsselung in Android diskutieren.
Schlüssel und Hashes
Ich werde anfangen, das passwortbasierte System mit Salzen zu diskutieren. Das Salz ist eine zufällig erzeugte Zahl. Es wird nicht "abgeleitet". Implementierung 1 enthält eine
generateSalt()
Methode, die eine kryptografisch starke Zufallszahl generiert. Da das Salz für die Sicherheit wichtig ist, sollte es nach seiner Erzeugung geheim gehalten werden, obwohl es nur einmal erzeugt werden muss. Wenn es sich um eine Website handelt, ist es relativ einfach, das Salz geheim zu halten, aber für installierte Anwendungen (für Desktop- und Mobilgeräte) ist dies viel schwieriger.Die Methode
getHash()
gibt einen Hash des angegebenen Kennworts und Salt zurück, der zu einer einzelnen Zeichenfolge verkettet ist. Der verwendete Algorithmus ist SHA-512, der einen 512-Bit-Hash zurückgibt. Diese Methode gibt einen Hash zurück, der zum Überprüfen der Integrität einer Zeichenfolge nützlich ist. Sie kann daher auch verwendet werden, indemgetHash()
nur ein Kennwort oder nur ein Salt aufgerufen wird, da beide Parameter einfach verkettet werden. Da diese Methode im kennwortbasierten Verschlüsselungssystem nicht verwendet wird, werde ich nicht weiter darauf eingehen.Die Methode
getSecretKey()
leitet einen Schlüssel aus einemchar
Array des Kennworts und einem hexadezimal codierten Salt ab, wie von zurückgegebengenerateSalt()
. Der verwendete Algorithmus ist PBKDF1 (glaube ich) von PKCS5 mit SHA-256 als Hash-Funktion und gibt einen 256-Bit-Schlüssel zurück.getSecretKey()
generiert einen Schlüssel durch wiederholtes Generieren von Hashes des Kennworts, des Salt und eines Zählers (bis zu der inPBE_ITERATION_COUNT
, hier 100 angegebenen Iterationszahl ), um die Zeit zu erhöhen, die zum Ausführen eines Brute-Force-Angriffs erforderlich ist. Die Länge des Salzes sollte mindestens so lang sein wie der zu erzeugende Schlüssel, in diesem Fall mindestens 256 Bit. Die Iterationszahl sollte so lange wie möglich eingestellt werden, ohne eine unangemessene Verzögerung zu verursachen. Weitere Informationen zu Salzen und Iterationszahlen bei der Schlüsselableitung finden Sie in Abschnitt 4 in RFC2898 .Die Implementierung in Javas PBE ist jedoch fehlerhaft, wenn das Kennwort Unicode-Zeichen enthält, dh solche, für deren Darstellung mehr als 8 Bit erforderlich sind. Wie in angegeben
PBEKeySpec
, "betrachtet der in PKCS # 5 definierte PBE-Mechanismus nur die niederwertigen 8 Bits jedes Zeichens". Um dieses Problem zu umgehen, können Sie versuchen, eine Hex-Zeichenfolge (die nur 8-Bit-Zeichen enthält) aller 16-Bit-Zeichen im Kennwort zu generieren, bevor Sie sie an übergebenPBEKeySpec
. Beispielsweise wird "ABC" zu "004100420043". Beachten Sie auch, dass PBEKeySpec "das Kennwort als char-Array anfordert, damit es nach Abschluss [mitclearPassword()
] überschrieben werden kann". (Bezüglich des "Schutzes von Zeichenfolgen im Speicher" siehe diese Frage .) Ich sehe jedoch keine Probleme.Verschlüsselung
Sobald ein Schlüssel generiert wurde, können wir ihn zum Ver- und Entschlüsseln von Text verwenden.
In Implementierung 1 wird als Verschlüsselungsalgorithmus
AES/CBC/PKCS5Padding
AES im CBC-Verschlüsselungsmodus (Cipher Block Chaining) verwendet, wobei die Auffüllung in PKCS # 5 definiert ist. (Andere AES-Verschlüsselungsmodi umfassen den Zählermodus (CTR), den elektronischen Codebuchmodus (EZB) und den Galois-Zählermodus (GCM). Eine weitere Frage zum Stapelüberlauf enthält Antworten, in denen die verschiedenen AES-Verschlüsselungsmodi und die empfohlenen zu erläutern sind. Beachten Sie auch, dass es mehrere Angriffe auf die Verschlüsselung im CBC-Modus gibt, von denen einige in RFC 7457 erwähnt werden.)Beachten Sie, dass Sie einen Verschlüsselungsmodus verwenden sollten, der auch die verschlüsselten Daten auf Integrität überprüft (z. B. authentifizierte Verschlüsselung mit zugehörigen Daten , AEAD, beschrieben in RFC 5116). Bietet
AES/CBC/PKCS5Padding
jedoch keine Integritätsprüfung, sodass dies allein nicht empfohlen wird . Für AEAD-Zwecke wird die Verwendung eines Geheimnisses empfohlen, das mindestens doppelt so lang ist wie ein normaler Verschlüsselungsschlüssel, um verwandte Schlüsselangriffe zu vermeiden: Die erste Hälfte dient als Verschlüsselungsschlüssel und die zweite Hälfte als Schlüssel für die Integritätsprüfung. (In diesem Fall wird aus einem Kennwort und Salt ein einzelnes Geheimnis generiert und dieses Geheimnis in zwei Teile geteilt.)Java-Implementierung
Die verschiedenen Funktionen in Implementierung 1 verwenden einen bestimmten Anbieter, nämlich "BC", für seine Algorithmen. Im Allgemeinen wird jedoch nicht empfohlen, bestimmte Anbieter anzufordern, da nicht alle Anbieter auf allen Java-Implementierungen verfügbar sind, sei es aus Mangel an Unterstützung, um Code-Duplikationen zu vermeiden oder aus anderen Gründen. Dieser Rat ist seit der Veröffentlichung der Android P-Vorschau Anfang 2018 besonders wichtig geworden, da einige Funktionen des "BC" -Anbieters dort veraltet sind - siehe Artikel "Änderungen der Kryptografie in Android P" im Android Developers Blog. Siehe auch die Einführung in Oracle-Anbieter .
So
PROVIDER
sollte nicht existieren und die Zeichenfolge-BC
sollte entfernt werdenPBE_ALGORITHM
. Implementierung 2 ist in dieser Hinsicht korrekt.Es ist für eine Methode unangemessen, alle Ausnahmen abzufangen, sondern nur die Ausnahmen zu behandeln, die sie kann. Die in Ihrer Frage angegebenen Implementierungen können eine Vielzahl von geprüften Ausnahmen auslösen. Eine Methode kann wählen, ob nur diese geprüften Ausnahmen mit CryptoException umbrochen werden sollen, oder diese geprüften Ausnahmen in der
throws
Klausel angeben . Der Einfachheit halber kann es hier angebracht sein, die ursprüngliche Ausnahme mit CryptoException zu verpacken, da die Klassen möglicherweise viele geprüfte Ausnahmen auslösen können.SecureRandom
in AndroidWie im Artikel "Some SecureRandom Thoughts" im Android Developers Blog beschrieben, weist die Implementierung
java.security.SecureRandom
in Android-Versionen vor 2013 einen Fehler auf, der die Stärke der bereitgestellten Zufallszahlen verringert. Dieser Fehler kann wie in diesem Artikel beschrieben behoben werden.quelle
getInstance
hat eine Überladung, die nur den Namen des Algorithmus nimmt. Beispiel: Cipher.getInstance () In der Java-Implementierung sind möglicherweise mehrere Anbieter, einschließlich Bouncy Castle, registriert. Diese Art der Überlastung durchsucht die Liste der Anbieter nach einem von ihnen, der den angegebenen Algorithmus implementiert. Sie sollten es versuchen und sehen.# 2 sollte niemals verwendet werden, da nur "AES" (was bedeutet, dass die Verschlüsselung im EZB-Modus für Text ein großes Nein-Nein ist) für die Verschlüsselung verwendet wird. Ich werde nur über # 1 sprechen.
Die erste Implementierung scheint den Best Practices für die Verschlüsselung zu entsprechen. Die Konstanten sind im Allgemeinen in Ordnung, obwohl sowohl die Salzgröße als auch die Anzahl der Iterationen zur Durchführung von PBE eher kurz sind. Darüber hinaus scheint es für AES-256 zu sein, da die PBE-Schlüsselgenerierung 256 als fest codierten Wert verwendet (eine Schande nach all diesen Konstanten). Es verwendet CBC und PKCS5Padding, was zumindest Ihren Erwartungen entspricht.
Völlig fehlt ein Authentifizierungs- / Integritätsschutz, sodass ein Angreifer den Chiffretext ändern kann. Dies bedeutet, dass Padding-Orakel-Angriffe in einem Client / Server-Modell möglich sind. Dies bedeutet auch, dass ein Angreifer versuchen kann, die verschlüsselten Daten zu ändern. Dies führt wahrscheinlich irgendwo zu einem Fehler, da das Auffüllen oder der Inhalt von der Anwendung nicht akzeptiert wird. Dies ist jedoch keine Situation, in der Sie sich befinden möchten.
Die Ausnahmebehandlung und die Eingabevalidierung könnten verbessert werden. Das Abfangen von Ausnahmen ist in meinem Buch immer falsch. Außerdem implementiert die Klasse ICrypt, was ich nicht kenne. Ich weiß, dass es etwas seltsam ist, nur Methoden ohne Nebenwirkungen in einer Klasse zu haben. Normalerweise würden Sie diese statisch machen. Es gibt keine Pufferung von Verschlüsselungsinstanzen usw., sodass jedes erforderliche Objekt ad nauseum erstellt wird. Sie können ICrypto jedoch sicher aus der Definition entfernen. In diesem Fall können Sie den Code auch in statische Methoden umgestalten (oder ihn nach Ihrer Wahl objektorientierter umschreiben).
Das Problem ist, dass jeder Wrapper immer Annahmen über den Anwendungsfall macht. Zu sagen, dass ein Wrapper richtig oder falsch ist, ist daher eine Koje. Deshalb versuche ich immer zu vermeiden, Wrapper-Klassen zu generieren. Aber zumindest scheint es nicht explizit falsch zu sein.
quelle
Sie haben eine ziemlich interessante Frage gestellt. Wie bei allen Algorithmen ist der Chiffrierschlüssel die "geheime Sauce", denn sobald dies der Öffentlichkeit bekannt ist, ist auch alles andere vorhanden. Sie suchen also nach Möglichkeiten für dieses Dokument von Google
Sicherheit
Neben Google In-App Billing gibt es auch aufschlussreiche Gedanken zur Sicherheit
billing_best_practices
quelle
Verwenden Sie die BouncyCastle Lightweight-API. Es bietet 256 AES mit PBE und Salz.
Hier Beispielcode, der Dateien verschlüsseln / entschlüsseln kann.
quelle
buf
(I. hoffe wirklich, dass es keinstatic
Feld ist). Es sieht auch nach beidem ausencrypt()
unddecrypt()
kann den letzten Block nicht korrekt verarbeiten, wenn die Eingabe ein Vielfaches von 1024 Bytes ist.Ich habe hier eine nette Implementierung gefunden: http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html und https://github.com/nelenkov/android-pbe Das war auch hilfreich auf meiner Suche nach einer ausreichend guten AES-Implementierung für Android
quelle