Apa praktik terbaik untuk menggunakan enkripsi AES di Android?

90

Mengapa saya mengajukan pertanyaan ini:

Saya tahu ada banyak pertanyaan tentang enkripsi AES, bahkan untuk Android. Dan ada banyak potongan kode jika Anda mencari di Web. Tetapi di setiap halaman, di setiap pertanyaan Stack Overflow, saya menemukan implementasi lain dengan perbedaan besar.

Jadi saya membuat pertanyaan ini untuk menemukan "praktik terbaik". Saya harap kami dapat mengumpulkan daftar persyaratan terpenting dan menyiapkan penerapan yang benar-benar aman!

Saya membaca tentang vektor inisialisasi dan garam. Tidak semua implementasi yang saya temukan memiliki fitur ini. Jadi, apakah Anda membutuhkannya? Apakah itu meningkatkan keamanan banyak? Bagaimana Anda menerapkannya? Haruskah algoritme meningkatkan pengecualian jika data yang dienkripsi tidak dapat didekripsi? Atau apakah itu tidak aman dan seharusnya mengembalikan string yang tidak dapat dibaca? Bisakah algoritme menggunakan Bcrypt sebagai ganti SHA?

Bagaimana dengan dua implementasi yang saya temukan ini? Apakah mereka baik-baik saja? Sempurna atau beberapa hal penting hilang? Apa dari ini yang aman?

Algoritme harus mengambil string dan "kata sandi" untuk enkripsi dan kemudian mengenkripsi string dengan kata sandi itu. Outputnya harus berupa string (hex atau base64?) Lagi. Dekripsi harus dimungkinkan juga, tentu saja.

Apa implementasi AES yang sempurna untuk Android?

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

}

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

Implementasi # 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));
    }

}

Sumber: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

gak
sumber
Saya mencoba menerapkan solusi 1 tetapi membutuhkan beberapa kelas. apakah Anda memiliki kode sumber lengkap?
albanx
1
Tidak, saya belum, maaf. Tapi saya membuatnya berfungsi hanya dengan menghapus implements ICryptodan mengubahnya throws CryptoExceptionke throws Exceptiondan seterusnya. Jadi Anda tidak membutuhkan kelas-kelas itu lagi.
gak
Tetapi juga kelas HexEncoder tidak ada? Dimana saya bisa menemukannya?
albanx
HexEncoder adalah bagian dari pustaka BouncyCastle, saya kira. Anda tinggal mendownloadnya. Atau Anda bisa google untuk "byte [] to hex" dan sebaliknya di Java.
gak
Terima kasih Marco. Tapi saya melihat bahwa ada 3 metode getSecretKey, getHash, generateSaltdalam pelaksanaan pertama yang terpakai. Mungkin saya salah, tetapi bagaimana kelas ini dapat digunakan untuk mengenkripsi string dalam praktiknya?
albanx

Jawaban:

37

Tidak ada implementasi yang Anda berikan dalam pertanyaan Anda sepenuhnya benar, dan implementasi yang Anda berikan tidak boleh digunakan apa adanya. Berikut ini, saya akan membahas aspek enkripsi berbasis kata sandi di Android.

Kunci dan Hash

Saya akan mulai membahas sistem berbasis kata sandi dengan garam. Garam adalah angka yang dibuat secara acak. Itu tidak "disimpulkan". Penerapan 1 mencakup generateSalt()metode yang menghasilkan bilangan acak kuat secara kriptografis. Karena garam penting untuk keamanan, garam harus dirahasiakan setelah dibuat, meskipun hanya perlu dibuat sekali. Jika ini adalah situs Web, relatif mudah untuk merahasiakan salt, tetapi untuk aplikasi yang diinstal (untuk desktop dan perangkat seluler), ini akan jauh lebih sulit.

Metode ini getHash()mengembalikan hash dari kata sandi dan garam yang diberikan, digabungkan menjadi satu string. Algoritme yang digunakan adalah SHA-512, yang mengembalikan hash 512-bit. Metode ini mengembalikan hash yang berguna untuk memeriksa integritas string, jadi mungkin juga digunakan dengan memanggil getHash()hanya dengan kata sandi atau hanya garam, karena itu hanya menggabungkan kedua parameter. Karena metode ini tidak akan digunakan dalam sistem enkripsi berbasis kata sandi, saya tidak akan membahasnya lebih lanjut.

Metodenya getSecretKey(), mendapatkan kunci dari chararray kata sandi dan garam yang dikodekan hex, seperti yang dikembalikan dari generateSalt(). Algoritma yang digunakan adalah PBKDF1 (menurut saya) dari PKCS5 dengan SHA-256 sebagai fungsi hash, dan mengembalikan kunci 256-bit. getSecretKey()menghasilkan kunci dengan membuat hash berulang kali dari kata sandi, garam, dan penghitung (hingga jumlah iterasi yang diberikan PBE_ITERATION_COUNT, di sini 100) untuk meningkatkan waktu yang diperlukan untuk memasang serangan brute force. Panjang garam setidaknya harus selama kunci yang dihasilkan, dalam hal ini, setidaknya 256 bit. Hitungan iterasi harus disetel selama mungkin tanpa menyebabkan penundaan yang tidak wajar. Untuk informasi lebih lanjut tentang garam dan jumlah iterasi dalam penurunan kunci, lihat bagian 4 di RFC2898 .

Implementasi di PBE Java, bagaimanapun, cacat jika kata sandi berisi karakter Unicode, yaitu yang membutuhkan lebih dari 8 bit untuk diwakili. Seperti yang dinyatakan dalam PBEKeySpec, "mekanisme PBE yang didefinisikan dalam PKCS # 5 hanya melihat 8 bit orde rendah dari setiap karakter". Untuk mengatasi masalah ini, Anda dapat mencoba membuat string hex (yang hanya akan berisi karakter 8-bit) dari semua karakter 16-bit dalam kata sandi sebelum meneruskannya PBEKeySpec. Misalnya, "ABC" menjadi "004100420043". Perhatikan juga bahwa PBEKeySpec "meminta kata sandi sebagai array karakter, sehingga dapat ditimpa [dengan clearPassword()] setelah selesai". (Sehubungan dengan "melindungi string dalam memori", lihat pertanyaan ini .) Namun, saya tidak melihat ada masalah,

Enkripsi

Setelah kunci dibuat, kita dapat menggunakannya untuk mengenkripsi dan mendekripsi teks.

Dalam implementasi 1, algoritma cipher yang digunakan adalah AES/CBC/PKCS5PaddingAES dalam mode cipher Cipher Block Chaining (CBC), dengan padding yang ditentukan dalam PKCS # 5. (Mode penyandian AES lainnya termasuk mode penghitung (CTR), mode buku kode elektronik (ECB), dan mode penghitung Galois (GCM). Pertanyaan lain tentang Stack Overflow berisi jawaban yang membahas secara rinci berbagai mode penyandian AES dan yang direkomendasikan untuk digunakan. Ketahuilah juga bahwa ada beberapa serangan pada enkripsi mode CBC, beberapa di antaranya disebutkan di RFC 7457.)

Perhatikan bahwa Anda harus menggunakan mode enkripsi yang juga memeriksa data terenkripsi untuk integritas (misalnya, enkripsi terotentikasi dengan data terkait , AEAD, dijelaskan dalam RFC 5116). Namun, AES/CBC/PKCS5Paddingtidak memberikan pemeriksaan integritas, jadi itu sendiri tidak disarankan . Untuk tujuan AEAD, disarankan menggunakan rahasia yang setidaknya dua kali lebih panjang dari kunci enkripsi normal, untuk menghindari serangan kunci terkait: bagian pertama berfungsi sebagai kunci enkripsi, dan bagian kedua berfungsi sebagai kunci untuk pemeriksaan integritas. (Yaitu, dalam hal ini, menghasilkan satu rahasia dari kata sandi dan garam, dan membagi rahasia itu menjadi dua.)

Implementasi Java

Berbagai fungsi dalam implementasi 1 menggunakan provider tertentu, yaitu "BC", untuk algoritmanya. Secara umum, tidak disarankan untuk meminta penyedia tertentu, karena tidak semua penyedia tersedia di semua implementasi Java, baik karena kurangnya dukungan, untuk menghindari duplikasi kode, atau karena alasan lain. Saran ini menjadi penting terutama sejak rilis pratinjau Android P pada awal 2018, karena beberapa fungsi dari penyedia "BC" sudah tidak digunakan lagi di sana - lihat artikel "Perubahan Kriptografi di Android P" di Blog Pengembang Android. Lihat juga Pengantar Penyedia Oracle .

Jadi, PROVIDERseharusnya tidak ada dan string -BCharus dihilangkan PBE_ALGORITHM. Implementasi 2 benar dalam hal ini.

Metode tidak sesuai untuk menangkap semua pengecualian, melainkan hanya menangani pengecualian yang bisa. Penerapan yang diberikan dalam pertanyaan Anda bisa memunculkan berbagai pengecualian yang dicentang. Sebuah metode dapat memilih untuk hanya membungkus pengecualian yang dicentang dengan CryptoException, atau menentukan pengecualian yang dicentang dalam throwsklausa. Untuk kenyamanan, menggabungkan pengecualian asli dengan CryptoException mungkin sesuai di sini, karena ada banyak kemungkinan pengecualian yang dicentang yang dapat dilontarkan kelas.

SecureRandom di Android

Seperti yang dijelaskan dalam artikel "Some SecureRandom Thoughts", di Android Developers Blog, penerapan java.security.SecureRandomdi rilis Android sebelum tahun 2013 memiliki kekurangan yang mengurangi kekuatan bilangan acak yang dihasilkannya. Cacat ini dapat diatasi seperti yang dijelaskan di artikel itu.

Peter O.
sumber
Pembuatan rahasia ganda itu agak boros menurut saya, Anda dapat dengan mudah membagi rahasia yang dihasilkan menjadi dua, atau - jika tidak tersedia cukup bit - tambahkan penghitung (1 untuk kunci pertama, 2 untuk kunci kedua) ke rahasia dan melakukan satu hash. Tidak perlu melakukan semua iterasi dua kali.
Maarten Bodewes
Terima kasih atas informasi tentang HMAC dan garamnya. Saya tidak akan menggunakan HMAC kali ini tetapi nanti mungkin akan sangat berguna. Dan secara umum, ini adalah hal yang baik, tidak diragukan lagi.
gak
Terima kasih banyak untuk semua pengeditan dan (sekarang) perkenalan yang luar biasa untuk enkripsi AES di Java ini!
gak
1
Itu harus. getInstancememiliki kelebihan beban yang hanya membutuhkan nama algoritme. Contoh: Cipher.getInstance () Beberapa penyedia, termasuk Bouncy Castle, mungkin terdaftar dalam implementasi Java dan jenis overload ini mencari daftar penyedia untuk salah satu penyedia yang mengimplementasikan algoritma yang diberikan. Anda harus mencobanya dan melihatnya.
Peter O.
1
Yup, itu akan mencari penyedia dalam urutan yang diberikan oleh Security.getProviders () - meskipun sekarang juga akan memeriksa apakah kunci diterima oleh penyedia itu selama panggilan init () memungkinkan untuk enkripsi yang dibantu perangkat keras. Detail selengkapnya di sini: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Maarten Bodewes
18

# 2 tidak boleh digunakan karena hanya menggunakan "AES" (yang berarti enkripsi mode ECB pada teks, sangat tidak-tidak) untuk penyandian. Saya hanya akan berbicara tentang # 1.

Penerapan pertama tampaknya mematuhi praktik terbaik untuk enkripsi. Konstanta umumnya OK, meskipun ukuran garam dan jumlah iterasi untuk melakukan PBE memiliki kekurangan. Selanjutnya, tampaknya untuk AES-256 karena generasi kunci PBE menggunakan 256 sebagai nilai kode keras (sayang sekali setelah semua konstanta tersebut). Ini menggunakan CBC dan PKCS5Padding yang setidaknya seperti yang Anda harapkan.

Yang benar-benar hilang adalah perlindungan otentikasi / integritas, sehingga penyerang dapat mengubah teks sandi. Ini berarti bahwa serangan oracle padding dimungkinkan dalam model klien / server. Ini juga berarti bahwa penyerang dapat mencoba dan mengubah data yang dienkripsi. Ini kemungkinan akan menghasilkan beberapa kesalahan di suatu tempat karena padding atau konten tidak diterima oleh aplikasi, tetapi itu bukan situasi yang Anda inginkan.

Penanganan pengecualian dan validasi input bisa ditingkatkan, menangkap Exception selalu salah di buku saya. Selanjutnya, kelas mengimplementasikan ICrypt, yang saya tidak tahu. Saya tahu bahwa hanya memiliki metode tanpa efek samping di kelas agak aneh. Biasanya, Anda akan membuat itu statis. Tidak ada buffering untuk instance Cipher dll., Jadi setiap objek yang diperlukan akan dibuat ad-nauseum. Namun, Anda dapat dengan aman menghapus ICrypto dari definisi tampaknya, dalam hal ini Anda juga dapat mengubah kode ke metode statis (atau menulis ulang menjadi lebih berorientasi objek, pilihan Anda).

Masalahnya adalah setiap pembungkus selalu membuat asumsi tentang kasus penggunaan. Untuk mengatakan bahwa pembungkus benar atau salah adalah omong kosong. Inilah sebabnya mengapa saya selalu berusaha menghindari menghasilkan kelas pembungkus. Tapi setidaknya hal itu tampaknya tidak salah secara eksplisit.

Maarten Bodewes
sumber
Terima kasih banyak atas jawaban mendetail ini! Saya tahu ini memalukan tapi saya belum tahu bagian review kode: D Terima kasih untuk petunjuk ini, saya akan memeriksanya. Tetapi pertanyaan ini juga cocok di sini menurut pendapat saya karena saya tidak hanya ingin meninjau cuplikan kode ini. Sebaliknya, saya ingin menanyakan semua aspek apa yang penting saat menerapkan enkripsi AES di Android. Dan Anda benar lagi, potongan kode ini untuk AES-256. Jadi, Anda akan mengatakan bahwa ini adalah implementasi yang aman dari AES-256? Kasus penggunaannya adalah saya hanya ingin menyimpan informasi teks dengan aman dalam database.
gak
1
Kelihatannya bagus, tetapi gagasan untuk tidak memiliki pemeriksaan integritas dan otentikasi akan mengganggu saya. Jika Anda memiliki cukup ruang, saya akan secara serius mempertimbangkan untuk menambahkan HMAC di atas ciphertext. Yang mengatakan, karena Anda mungkin mencoba untuk hanya menambahkan kerahasiaan, saya akan menganggapnya sebagai nilai tambah yang besar, tetapi tidak secara langsung merupakan persyaratan.
Maarten Bodewes
Tapi jika tujuannya hanya agar orang lain tidak memiliki akses ke informasi terenkripsi, saya tidak memerlukan HMAC, bukan? Jika mereka mengubah ciphertext dan memaksakan hasil dekripsi yang "salah", tidak ada masalah nyata, bukan?
gak
Jika itu tidak ada dalam skenario risiko Anda, maka tidak masalah. Jika mereka entah bagaimana dapat memicu dekripsi berulang oleh sistem setelah mengubah teks sandi (serangan oracle padding) maka mereka dapat mendekripsi data tanpa pernah mengetahui kuncinya. Mereka tidak dapat melakukan ini jika mereka hanya mendapatkan data pada sistem yang tidak memiliki kunci. Namun itulah mengapa selalu merupakan praktik terbaik untuk menambahkan HMAC. Secara pribadi, saya akan menganggap sistem dengan AES-128 dan HMAC lebih aman daripada AES-256 tanpa - tetapi seperti yang dikatakan, mungkin tidak diperlukan.
Maarten Bodewes
1
Mengapa tidak menggunakan AES dalam Galois / Counter-mode (AES-GCM) jika Anda menginginkan integritas?
Kimvais
1

Anda telah mengajukan pertanyaan yang cukup menarik. Seperti semua algoritma, kunci sandi adalah "saus rahasia", karena begitu diketahui publik, yang lainnya juga. Jadi Anda mencari cara untuk dokumen ini oleh Google

keamanan

Selain itu Google In-App Billing juga memberikan pemikiran tentang keamanan yang berwawasan juga

billing_best_practices

the100rabh
sumber
Terima kasih untuk tautan ini! Apa sebenarnya yang Anda maksud dengan "ketika kunci sandi keluar, yang lainnya juga keluar"?
gak
Yang saya maksud adalah bahwa kunci enkripsi harus diamankan, jika ada yang bisa memegangnya, maka data terenkripsi Anda sama baiknya dengan teks biasa. Harap
beri suara positif
0

Gunakan BouncyCastle Lightweight API. Ini menyediakan 256 AES Dengan PBE dan Garam.
Berikut kode contoh, yang dapat mengenkripsi / mendekripsi file.

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
sumber
Terima kasih! Ini mungkin solusi yang baik dan aman, tetapi saya tidak ingin menggunakan perangkat lunak pihak ketiga. Saya yakin AES harus dapat diimplementasikan dengan cara yang aman sendiri.
gak
2
Tergantung apakah Anda ingin menyertakan perlindungan terhadap serangan saluran samping. Umumnya, Anda harus menganggap itu cukup aman untuk mengimplementasikan algoritma kriptografi pada Anda sendiri. Karena AES CBC tersedia di pustaka runtime Java Oracle, mungkin yang terbaik adalah menggunakan itu dan menggunakan pustaka Bouncy Castle jika algoritme tidak tersedia.
Maarten Bodewes
Tidak ada definisi buf(saya sangat berharap ini bukan staticbidang). Ini juga terlihat seperti keduanya encrypt()dan decrypt()akan gagal memproses blok terakhir dengan benar jika inputnya adalah kelipatan 1024 byte.
tc.