it-swarm.com.de

Was sind die Best Practices für die Verwendung der AES-Verschlüsselung in Android?

Warum ich diese Frage stelle:

Ich weiß, dass es viele Fragen zur AES-Verschlüsselung gab, auch für Android. Und es gibt viele Code-Schnipsel, wenn Sie im Web suchen. Aber auf jeder einzelnen Seite, in jeder Stapelüberlauf-Frage, 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 wirklich sichere Implementierung aufbauen!

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 sehr? Wie setzen Sie das 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? Geht es ihnen gut Perfekt oder einige wichtige Dinge fehlen? Was davon ist sicher?

Der Algorithmus sollte eine Zeichenfolge und ein "Kennwort" zur Verschlüsselung verwenden und die Zeichenfolge dann mit diesem Kennwort verschlüsseln. Die Ausgabe sollte wieder ein String sein (hex oder base64?). Natürlich sollte auch eine Entschlüsselung möglich sein.

Was ist die perfekte AES-Implementierung für Android?

Implementierung # 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;
        }

}

Quelle: http://pocket-for-Android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

Implementierung # 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

84
caw

Keine der Implementierungen, die Sie in Ihrer Frage angeben, ist völlig korrekt, und keine der Implementierungen, die Sie angeben, sollte unverändert verwendet werden. Im Folgenden werde ich Aspekte des Kennworts erörtern Verschlüsselung in Android.

Schlüssel und Hashes

Ich werde anfangen, das kennwortbasierte System mit Salzen zu besprechen. Das Salz ist eine zufällig generierte 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 Salt geheim zu halten, bei installierten Anwendungen (für Desktop- und Mobilgeräte) ist dies jedoch 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 nützlich ist, um die Integrität eines Strings zu überprüfen. Sie kann daher auch verwendet werden, indem Sie getHash() mit nur einem Kennwort oder nur einem Salt aufrufen, 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 einem char -Array des Kennworts und einem hexadezimalen Salt ab, wie von generateSalt() zurückgegeben. 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 Passworts, Salt und eines Zählers (bis zu der in PBE_ITERATION_COUNT, hier 100, angegebenen Iterationsanzahl), um die zum Einhängen erforderliche Zeit zu erhöhen ein Brute-Force-Angriff. Die Länge des Salt sollte mindestens so lang sein, wie der erzeugte Schlüssel, in diesem Fall mindestens 256 Bit. Die Iterationszahl sollte so lange wie möglich eingestellt werden, ohne dass dies zu einer unangemessenen Verzögerung führt. 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, d. H. Diejenigen, für deren Darstellung mehr als 8 Bits erforderlich sind. Wie in PBEKeySpec angegeben, "betrachtet der in PKCS # 5 definierte PBE-Mechanismus nur die 8 Bits niedriger Ordnung 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 PBEKeySpec übergeben. Beispielsweise wird "ABC" zu "004100420043". Beachten Sie auch, dass PBEKeySpec "das Kennwort als Zeichen-Array anfordert, damit es nach Abschluss mit clearPassword()] überschrieben werden kann". (In Bezug auf "Strings im Speicher schützen", siehe diese Frage .) Ich sehe jedoch keine Probleme, ein Salt als hexadezimal codierten String darzustellen.

Verschlüsselung

Sobald ein Schlüssel generiert wurde, können wir ihn zum Ver- und Entschlüsseln von Text verwenden. In Implementierung 1 ist der verwendete Verschlüsselungsalgorithmus AES/CBC/PKCS5Padding, D. H. AES im CBC-Verschlüsselungsmodus (Cipher Block Chaining), wobei die Auffüllung in PKCS # 5 definiert ist. (Andere AES-Verschlüsselungsmodi umfassen den Zählermodus (CTR), den elektronischen Codebuchmodus (ECB) und den Galois-Zählermodus (GCM). Eine weitere Frage zum Stapelüberlauf enthält Antworten, in denen die verschiedenen AES-Verschlüsselungsmodi ausführlich erläutert werden Beachten Sie auch, dass es mehrere Angriffe auf die Verschlüsselung im CBC-Modus gibt, von denen einige in RFC 7457 erwähnt werden.)

Wenn der verschlüsselte Text für Außenstehende verfügbar gemacht wird, wird empfohlen, einen Nachrichtenauthentifizierungscode (MAC) auf die verschlüsselten Daten (und optional zusätzliche Parameter) anzuwenden, um deren Integrität zu schützen (eine als authentifizierte Verschlüsselung mit zugehörigem Verfahren) data, AEAD, beschrieben in RFC 5116). Beliebt sind hier Hash-basierte MACs oder HMACs, die auf SHA-256 oder anderen sicheren Hash-Funktionen basieren. Wenn ein MAC verwendet wird, wird jedoch 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 MAC. (In diesem Fall wird also ein einzelnes Geheimnis aus einem Passwort und Salt generiert und dieses Geheimnis in zwei Teile geteilt.)

Java-Implementierung

Die verschiedenen Funktionen in Implementierung 1 verwenden für ihre Algorithmen einen bestimmten Anbieter, nämlich "BC". Im Allgemeinen wird jedoch nicht empfohlen, bestimmte Anbieter anzufordern, da nicht alle Anbieter für alle Java - Implementierungen verfügbar sind, sei es aus Mangel an Unterstützung, zur Vermeidung von Code-Duplikaten oder aus anderen Gründen. Dieser Hinweis ist seit der Veröffentlichung von Android P preview im Frühjahr 2018 besonders wichtig geworden, da einige Funktionen des "BC" -Anbieters dort veraltet sind - siehe den Artikel "Cryptography Changes in Android P "im Android Developers Blog. Siehe auch Introduction to Oracle Providers .

Daher sollte PROVIDER nicht existieren und der String -BC Sollte aus PBE_ALGORITHM Entfernt werden. Implementierung 2 ist in dieser Hinsicht korrekt.

Eine Methode kann nicht alle Ausnahmen abfangen, sondern nur die Ausnahmen behandeln, die sie kann. Die in Ihrer Frage angegebenen Implementierungen können eine Vielzahl von überprüften Ausnahmen auslösen. Eine Methode kann festlegen, dass nur die aktivierten Ausnahmen in CryptoException eingeschlossen werden, oder diese aktivierten 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 aktivierte Ausnahmen auslösen können.

SecureRandom in Android

Wie im Artikel "Some SecureRandom Thoughts" im Android Developers Blog, die Implementierung von Java.security.SecureRandom In Android Releases vor 2013) beschrieben Ein Fehler, der die Stärke der von ihm gelieferten Zufallszahlen verringert.Dieser Fehler kann behoben werden, indem ein unvorhersehbarer und zufälliger Datenblock (z. B. die Ausgabe von /dev/urandom) an die setSeed -Methode dieser Klasse übergeben wird.

35
Peter O.

# 2 sollte niemals verwendet werden, da es nur "AES" (was ECB-Verschlüsselung für Text bedeutet, ein großes Nein-Nein) für die Verschlüsselung verwendet. 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 dies für AES-256 der Fall 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 das ist, was Sie erwarten würden.

Es fehlt jeglicher Authentifizierungs-/Integritätsschutz, sodass ein Angreifer den Chiffretext ändern kann. Dies bedeutet, dass Padding-Oracle-Angriffe in einem Client/Server-Modell möglich sind. Dies bedeutet auch, dass ein Angreifer versuchen kann, die verschlüsselten Daten zu ändern. Dies wird wahrscheinlich irgendwo zu einem Fehler führen, 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 Behandlung von Ausnahmen und die Validierung von Eingaben könnten verbessert werden. Das Abfangen von Ausnahmen ist in meinem Buch immer falsch. Außerdem implementiert die Klasse ICrypt, das 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 Cipher-Instanzen usw., sodass jedes erforderliche Objekt ad nauseum erstellt wird. Sie können ICrypto jedoch ohne Bedenken aus der Definition entfernen. In diesem Fall können Sie den Code auch in statische Methoden umgestalten (oder ihn neu schreiben, um ihn objektorientierter zu gestalten, wie Sie möchten).

Das Problem ist, dass jeder Wrapper immer Annahmen über den Anwendungsfall trifft. Zu sagen, dass ein Wrapper richtig oder falsch ist, ist daher eine Koje. Aus diesem Grund versuche ich immer zu vermeiden, Wrapper-Klassen zu generieren. Aber zumindest scheint es nicht ausdrücklich falsch.

15
Maarten Bodewes

Sie haben eine ziemlich interessante Frage gestellt. Wie bei allen Algorithmen ist der Chiffrierschlüssel die "geheime Soße", da, sobald dies der Öffentlichkeit bekannt ist, alles andere auch ist. Sie suchen nach Wegen, um dieses Dokument von Google zu erhalten

Sicherheit

Neben Google In-App Billing gibt es auch Gedanken zur Sicherheit, die auch aufschlussreich sind

billing_best_practices

1
the100rabh

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 bei meiner Suche nach einer ausreichend guten AES-Implementierung für Android

0

Verwenden Sie die BouncyCastle Lightweight API. Es bietet 256 AES mit PBE und Salz.
Hier Beispielcode, der Dateien verschlüsseln/entschlüsseln kann.

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();
    }
}
0
kelheor