Jakie są najlepsze praktyki dotyczące korzystania z szyfrowania AES w systemie Android?

90

Dlaczego zadaję to pytanie:

Wiem, że było wiele pytań dotyczących szyfrowania AES, nawet w przypadku Androida. A jeśli przeszukujesz Internet, jest wiele fragmentów kodu. Ale na każdej stronie, w każdym pytaniu o przepełnienie stosu, znajduję inną implementację z dużymi różnicami.

Stworzyłem więc to pytanie, aby znaleźć „najlepszą praktykę”. Mam nadzieję, że uda nam się zebrać listę najważniejszych wymagań i skonfigurować implementację, która jest naprawdę bezpieczna!

Czytałem o wektorach inicjalizacyjnych i solach. Nie wszystkie implementacje, które znalazłem, miały te funkcje. Więc potrzebujesz tego? Czy to znacznie zwiększa bezpieczeństwo? Jak to realizujesz? Czy algorytm powinien zgłaszać wyjątki, jeśli zaszyfrowanych danych nie można odszyfrować? A może jest to niezabezpieczone i powinno po prostu zwrócić nieczytelny ciąg? Czy algorytm może używać Bcrypt zamiast SHA?

A co z tymi dwoma implementacjami, które znalazłem? Czy wszystko w porządku? Idealne lub brakuje kilku ważnych rzeczy? Co z tego jest bezpieczne?

Algorytm powinien wziąć ciąg znaków i „hasło” do zaszyfrowania, a następnie zaszyfrować ciąg za pomocą tego hasła. Wynik powinien być ponownie łańcuchem (hex czy base64?). Oczywiście deszyfrowanie powinno być również możliwe.

Jaka jest idealna implementacja AES dla Androida?

Wdrożenie 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;
        }

}

Źródło: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

Wdrożenie 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));
    }

}

Źródło: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

krakanie
źródło
Próbuję wdrożyć rozwiązanie 1, ale wymagało to kilku zajęć. czy masz pełny kod źródłowy?
albanx
1
Nie, nie przepraszam. Ale udało mi się to po prostu usunąć implements ICryptoi zmienić throws CryptoExceptionna throws Exceptioni tak dalej. Więc nie będziesz już potrzebować tych zajęć.
krakaj
Ale brakuje też klasy HexEncoder? Gdzie mogę to znaleźć?
albanx
Myślę, że HexEncoder jest częścią biblioteki BouncyCastle. Możesz go po prostu pobrać. Możesz też wyszukać w wyszukiwarce „bajt [] na szesnastkowy” i na odwrót w Javie.
krakaj
Dziękuję Marco. Ale widzę, że istnieją 3 sposoby getSecretKey, getHash, generateSaltw pierwszej realizacji, które są niewykorzystane. Może się mylę, ale jak w praktyce można wykorzystać tę klasę do szyfrowania ciągu znaków?
albanx

Odpowiedzi:

37

Żadna implementacja, którą podasz w swoim pytaniu, nie jest całkowicie poprawna i żadna z podanych przez Ciebie implementacji nie powinna być używana w takiej postaci, w jakiej jest.W dalszej części omówię aspekty szyfrowania opartego na hasłach w systemie Android.

Klucze i skróty

Zacznę omawiać system oparty na hasłach z solami. Sól to losowo generowana liczba. Nie jest to „wydedukowane”. Wdrożenie 1 obejmujegenerateSalt() metodę, która generuje liczb losowych silnych pod względem kryptograficznym. Ponieważ sól jest ważna dla bezpieczeństwa, powinna być utrzymywana w tajemnicy po jej wytworzeniu, chociaż wystarczy ją wygenerować tylko raz. Jeśli jest to witryna internetowa, stosunkowo łatwo jest zachować tajemnicę, ale w przypadku zainstalowanych aplikacji (na komputery stacjonarne i urządzenia mobilne) będzie to znacznie trudniejsze.

Metoda getHash()zwraca hash podanego hasła i soli, połączone w jeden ciąg. Użyty algorytm to SHA-512, który zwraca 512-bitowy skrót. Ta metoda zwraca hash, który jest przydatny do sprawdzania integralności ciągu, więc równie dobrze można go użyć, wywołując getHash()tylko hasło lub tylko sól, ponieważ po prostu łączy oba parametry. Ponieważ ta metoda nie będzie używana w systemie szyfrowania opartym na hasłach, nie będę go dalej omawiać.

Metoda getSecretKey()wyprowadza klucz z chartablicy haseł i soli zakodowanej szesnastkowo, zwróconej z generateSalt(). Użyty algorytm to PBKDF1 (chyba) z PKCS5 z SHA-256 jako funkcją skrótu i ​​zwraca 256-bitowy klucz. getSecretKey()generuje klucz poprzez wielokrotne generowanie skrótów hasła, soli i licznika (do liczby iteracji podanej w PBE_ITERATION_COUNTtutaj 100) w celu zwiększenia czasu potrzebnego do przeprowadzenia ataku siłowego. Długość soli powinna wynosić co najmniej tak długo, jak długość generowanego klucza, w tym przypadku co najmniej 256 bitów. Licznik iteracji powinien być ustawiony tak długo, jak to możliwe, bez powodowania nieuzasadnionego opóźnienia. Aby uzyskać więcej informacji na temat soli i liczby iteracji w wyprowadzaniu klucza, zobacz sekcję 4 w RFC2898 .

Implementacja w PBE Javy jest jednak błędna, jeśli hasło zawiera znaki Unicode, to znaczy takie, które wymagają reprezentacji więcej niż 8 bitów. Jak stwierdzono w PBEKeySpec, „mechanizm PBE zdefiniowany w PKCS # 5 sprawdza tylko 8 mniej znaczących bitów każdego znaku”. Aby obejść ten problem, możesz spróbować wygenerować ciąg szesnastkowy (który będzie zawierał tylko znaki 8-bitowe) wszystkich 16-bitowych znaków w haśle przed przekazaniem go do PBEKeySpec. Na przykład „ABC” zmieni się na „004100420043”. Zauważ również, że PBEKeySpec „żąda hasła jako tablicy znaków, więc można je nadpisać [z clearPassword()] po zakończeniu ”. (Jeśli chodzi o „ochronę ciągów w pamięci”, zobacz to pytanie ). Nie widzę jednak żadnych problemów,

Szyfrowanie

Po wygenerowaniu klucza możemy go użyć do zaszyfrowania i odszyfrowania tekstu.

W implementacji 1 zastosowany algorytm szyfrujący AES/CBC/PKCS5Paddingto AES w trybie szyfrowania Cipher Block Chaining (CBC) z dopełnieniem zdefiniowanym w PKCS # 5. (Inne tryby szyfrowania AES obejmują tryb licznika (CTR), tryb elektronicznej książki kodowej (ECB) i tryb licznika Galois (GCM). Kolejne pytanie dotyczące przepełnienia stosu zawiera odpowiedzi, które szczegółowo omawiają różne tryby szyfrowania AES i zalecane do użycia. Należy również pamiętać, że istnieje kilka ataków na szyfrowanie w trybie CBC, z których niektóre są wymienione w dokumencie RFC 7457).

Należy pamiętać, że należy używać trybu szyfrowania, który sprawdza również integralność zaszyfrowanych danych (np. Uwierzytelnione szyfrowanie z powiązanymi danymi , AEAD, opisane w RFC 5116). Jednak AES/CBC/PKCS5Paddingnie zapewnia sprawdzania integralności, więc samo to nie jest zalecane . Do celów AEAD zaleca się użycie klucza tajnego, który jest co najmniej dwa razy dłuższy niż normalny klucz szyfrowania, aby uniknąć powiązanych ataków kluczem: pierwsza połowa służy jako klucz szyfrowania, a druga połowa służy jako klucz do sprawdzenia integralności. (Oznacza to, że w tym przypadku należy wygenerować jeden sekret z hasła i soli i podzielić ten sekret na dwie części).

Implementacja Java

Różne funkcje w implementacji 1 używają określonego dostawcy, mianowicie „BC”, dla swoich algorytmów. Ogólnie jednak nie zaleca się proszenia o określonych dostawców, ponieważ nie wszyscy dostawcy są dostępni we wszystkich implementacjach Java, czy to z powodu braku obsługi, aby uniknąć duplikowania kodu lub z innych powodów. Ta rada stała się szczególnie ważna od czasu udostępnienia wersji zapoznawczej Androida P na początku 2018 r., Ponieważ niektóre funkcje dostawcy „BC” zostały tam wycofane - zobacz artykuł „Zmiany kryptografii w Androidzie P” na blogu deweloperów Androida. Zobacz także Wprowadzenie do dostawców Oracle .

Dlatego PROVIDERnie powinno istnieć, a ciąg -BCpowinien zostać usunięty z PBE_ALGORITHM. Implementacja 2 jest pod tym względem poprawna.

Metoda wyłapywania wszystkich wyjątków jest niewłaściwa, ale raczej obsługuje tylko te wyjątki, które może. Implementacje podane w pytaniu mogą generować różne sprawdzone wyjątki. Metoda może wybrać zawijanie tylko tych zaznaczonych wyjątków za pomocą CryptoException lub określić te zaznaczone wyjątki w throwsklauzuli. Dla wygody, zawijanie oryginalnego wyjątku za pomocą CryptoException może być tutaj odpowiednie, ponieważ istnieje potencjalnie wiele sprawdzonych wyjątków, które klasy mogą zgłaszać.

SecureRandom w systemie Android

Jak szczegółowo opisano w artykule „Some SecureRandom Thoughts” na blogu Android Developers Blog, implementacja java.security.SecureRandomw wersjach systemu Android przed 2013 rokiem ma wadę, która zmniejsza siłę liczb losowych, które dostarcza. Ta usterka może zostać złagodzona zgodnie z opisem w tym artykule.

Peter O.
źródło
To generowanie podwójnego sekretu jest moim zdaniem trochę marnotrawne, możesz równie łatwo podzielić wygenerowany sekret na dwie lub - jeśli nie ma wystarczającej liczby bitów - dodać licznik (1 dla pierwszego klucza, 2 dla drugiego klucza) tajne i wykonaj pojedynczy hash. Nie ma potrzeby wykonywania wszystkich iteracji dwukrotnie.
Maarten Bodewes
Dzięki za informację o HMAC i soli. Tym razem nie użyję HMAC, ale później może być bardzo przydatny. Ogólnie rzecz biorąc, jest to bez wątpienia dobra rzecz.
krakaj
Dziękuję bardzo za wszystkie zmiany i to (teraz) wspaniałe wprowadzenie do szyfrowania AES w Javie!
krakanie
1
Powinno. getInstancema przeciążenie, które przyjmuje tylko nazwę algorytmu. Przykład: Cipher.getInstance () Kilku dostawców, w tym Bouncy Castle, może być zarejestrowanych w implementacji Java i tego rodzaju przeciążenie przeszukuje listę dostawców pod kątem jednego z nich, który implementuje dany algorytm. Powinieneś spróbować i zobaczyć.
Peter O.
1
Tak, przeszuka dostawców w kolejności podanej przez Security.getProviders () - chociaż teraz będzie również sprawdzać, czy klucz jest akceptowany przez tego dostawcę podczas wywołania init (), umożliwiając szyfrowanie wspomagane sprzętowo. Więcej szczegółów tutaj: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Maarten Bodewes
18

Nr 2 nigdy nie powinien być używany, ponieważ używa tylko "AES" (co oznacza szyfrowanie tekstu w trybie EBC, duże nie-nie) do szyfrowania. Porozmawiam tylko o nr 1.

Wydaje się, że pierwsza implementacja jest zgodna z najlepszymi praktykami dotyczącymi szyfrowania. Stałe są na ogół OK, chociaż zarówno rozmiar soli, jak i liczba iteracji do wykonania PBE są krótkie. Co więcej, wydaje się, że dotyczy to AES-256, ponieważ generowanie kluczy PBE używa 256 jako wartości zakodowanej na stałe (szkoda po tych wszystkich stałych). Używa CBC i PKCS5Padding, co jest przynajmniej tym, czego można się spodziewać.

Całkowicie brakuje jakiejkolwiek ochrony uwierzytelniania / integralności, więc atakujący może zmienić zaszyfrowany tekst. Oznacza to, że ataki typu padding oracle są możliwe w modelu klient / serwer. Oznacza to również, że osoba atakująca może próbować zmienić zaszyfrowane dane. Prawdopodobnie spowoduje to gdzieś błąd, ponieważ dopełnienie lub zawartość nie są akceptowane przez aplikację, ale to nie jest sytuacja, w której chcesz się znaleźć.

Można ulepszyć obsługę wyjątków i sprawdzanie poprawności danych wejściowych, wychwytywanie wyjątków jest zawsze błędne w mojej książce. Co więcej, klasa implementuje ICrypt, którego nie znam. Wiem, że posiadanie tylko metod bez efektów ubocznych w klasie jest trochę dziwne. Normalnie zrobiłbyś te statyczne. Nie ma buforowania instancji szyfrów itp., Więc każdy wymagany obiekt jest tworzony ad-nauseum. Możesz jednak bezpiecznie usunąć ICrypto z definicji, jak się wydaje, w takim przypadku możesz również refaktoryzować kod na metody statyczne (lub przepisać go, aby był bardziej zorientowany obiektowo, według własnego uznania).

Problem polega na tym, że każdy wrapper zawsze przyjmuje założenia dotyczące przypadku użycia. Stwierdzenie, że opakowanie jest dobre lub złe, jest więc bzdurą. Dlatego zawsze staram się unikać generowania klas opakowujących. Ale przynajmniej nie wydaje się to wyraźnie błędne.

Maarten Bodewes
źródło
Bardzo dziękuję za tę szczegółową odpowiedź! Wiem, że to wstyd, ale nie znałam jeszcze sekcji przeglądu kodu: D Dzięki za tę wskazówkę, sprawdzę to. Ale to pytanie również pasuje tutaj, moim zdaniem, ponieważ nie chcę tylko przeglądu tych fragmentów kodu. Zamiast tego chcę zapytać o wszystkie aspekty, które są ważne przy wdrażaniu szyfrowania AES w systemie Android. I znowu masz rację, ten fragment kodu dotyczy AES-256. Więc można powiedzieć, że jest to ogólnie bezpieczna implementacja AES-256? Przykład użycia jest taki, że chcę po prostu bezpiecznie przechowywać informacje tekstowe w bazie danych.
krakaj
1
Wygląda dobrze, ale niepokoi mnie pomysł braku kontroli integralności i uwierzytelniania. Jeśli masz wystarczająco dużo miejsca, poważnie rozważę dodanie HMAC do zaszyfrowanego tekstu. To powiedziawszy, ponieważ prawdopodobnie próbujesz po prostu dodać poufność, uznałbym to za duży plus, ale nie bezpośrednio za wymóg.
Maarten Bodewes
Ale jeśli intencją jest, aby inni nie mieli dostępu do zaszyfrowanych informacji, nie potrzebuję HMAC, prawda? Jeśli zmienią zaszyfrowany tekst i wymuszą „zły” wynik odszyfrowania, nie ma prawdziwego problemu, prawda?
krakaj
Jeśli nie ma tego w twoim scenariuszu ryzyka, to w porządku. Jeśli w jakiś sposób mogą wywołać ponowne odszyfrowanie przez system po zmianie zaszyfrowanego tekstu (atak wypełniający wyrocznię), mogą odszyfrować dane bez znajomości klucza. Nie mogą tego zrobić, jeśli po prostu zdobędą dane w systemie, który nie ma klucza. Ale właśnie dlatego dodawanie HMAC zawsze jest najlepszą praktyką. Osobiście uważałbym, że system z AES-128 i HMAC jest bezpieczniejszy niż AES-256 bez - ale jak powiedziałem, prawdopodobnie nie jest wymagany.
Maarten Bodewes
1
Dlaczego nie użyć AES w trybie Galois / Counter (AES-GCM), jeśli chcesz zachować integralność?
Kimvais
1

Zadałeś całkiem interesujące pytanie. Jak w przypadku wszystkich algorytmów, klucz szyfrujący jest „tajnym sosem”, ponieważ skoro jest znany opinii publicznej, wszystko inne też jest. Zastanawiasz się więc, jak uzyskać dostęp do tego dokumentu przez Google

bezpieczeństwo

Oprócz rozliczeń w aplikacji Google przedstawia również przemyślenia na temat bezpieczeństwa, które są również wnikliwe

billing_best_practices

the100rabh
źródło
Dzięki za te linki! Co dokładnie masz na myśli, mówiąc „kiedy klucz szyfrujący jest wyjęty, wszystko inne też jest wyłączone”?
krakanie
Chodzi mi o to, że klucz szyfrowania musi być bezpieczny, jeśli ktoś może go zdobyć, zaszyfrowane dane są tak dobre, jak zwykły tekst. Proszę o głosowanie, jeśli uznasz, że moja odpowiedź była do pewnego stopnia pomocna :-)
the100rabh
0

Użyj BouncyCastle Lightweight API. Zapewnia 256 AES z PBE i solą.
Tutaj przykładowy kod, który może szyfrować / odszyfrowywać pliki.

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();
    }
}
kelheor
źródło
Dziękuję Ci! To prawdopodobnie dobre i bezpieczne rozwiązanie, ale nie chcę korzystać z oprogramowania innych firm. Jestem pewien, że wdrożenie AES musi być możliwe we własnym zakresie.
krakaj
2
Zależy, czy chcesz włączyć ochronę przed atakami z kanału bocznego. Ogólnie rzecz biorąc, należy założyć, że samodzielne wdrażanie algorytmów kryptograficznych jest dość niebezpieczne . Ponieważ AES CBC jest dostępny w bibliotekach wykonawczych Java firmy Oracle, prawdopodobnie najlepiej jest z nich korzystać i korzystać z bibliotek Bouncy Castle, jeśli algorytm jest niedostępny.
Maarten Bodewes
Brakuje definicji buf(I naprawdę nadzieję, że nie jest to staticpole). Wygląda również na to, że oba encrypt()i decrypt()nie będą poprawnie przetwarzać końcowego bloku, jeśli dane wejściowe są wielokrotnością 1024 bajtów.
tc.