在Android中使用AES加密的最佳做法是什么?

What are best practices for using AES encryption in Android?

我为什么问这个问题:

我知道有很多关于AES加密的问题,即使对于Android也是如此。如果你在网上搜索的话,会有很多代码片段。但是在每一个页面上,在每一个堆栈溢出问题中,我都发现了另一个有重大差异的实现。

所以我创建这个问题是为了找到一个"最佳实践"。我希望我们可以收集一个最重要的需求列表,并建立一个真正安全的实现!

我读过初始化向量和盐。我发现并不是所有的实现都有这些特性。你需要它吗?它能提高安全性吗?你如何实现它?如果加密数据无法解密,算法是否应引发异常?或者这是不安全的,它应该只返回一个不可读的字符串?算法可以使用bcrypt而不是sha吗?

我发现的这两个实现呢?他们没事吧?完美还是缺少一些重要的东西?哪些是安全的?

算法应该使用一个字符串和一个"密码"进行加密,然后用该密码对字符串进行加密。输出应该是一个字符串(十六进制还是base64?)再一次。当然,解密也应该是可能的。

Android的完美AES实现是什么?

实施1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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;
        }

}

来源:http://pocket-for-android.1047292.n5.nabble.com/encryption-method-and-reading-the-dropbox-backup-td4344194.html

实施2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Usage:
 * [cc]
 * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
 * ...
 * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
 *

号*@作者费伦克·赫克勒*/公共类单纯形加密{公共静态字符串加密(字符串种子、字符串明文)引发异常{byte[]rawkey=getrawkey(seed.getbytes());byte[]result=加密(rawkey,cleartext.getbytes());返回十六进制(结果);}公共静态字符串解密(字符串种子,字符串加密)引发异常{byte[]rawkey=getrawkey(seed.getbytes());字节[]enc=tobyte(加密);字节[]结果=解密(rawkey,enc);返回新字符串(result);}private static byte[]getrawkey(byte[]seed)引发异常{keyGenerator kgen=keyGenerator.getInstance("aes");secureRandom sr=secureRandom.getInstance("sha1prng");种子(种子);kgen.init(128,sr);//192和256位可能不可用secretkey skey=kgen.generatekey();byte[]raw=skey.getEncoded();返回原始数据;}私有静态byte[]encrypt(byte[]raw,byte[]clear)引发异常{secretkeyspec skeyspec=新secretkeyspec(原始,"aes");cipher cipher=cipher.getInstance("aes");cipher.init(cipher.encrypt_模式,skeyspec);byte[]encrypted=cipher.dofinal(clear);返回加密;}私有静态字节[]解密(字节[]原始,字节[]加密)引发异常{secretkeyspec skeyspec=新secretkeyspec(原始,"aes");cipher cipher=cipher.getInstance("aes");cipher.init(cipher.decrypt_模式,skeyspec);byte[]decrypted=cipher.dofinal(加密);返回已解密;}公共静态字符串tohex(string txt){返回到hex(txt.getbytes());}公共静态字符串FromHex(字符串Hex){返回新字符串(tobyte(hex));}公共静态字节[]tobyte(字符串hexstring){int len=hexstring.length()/2;byte[]result=new byte[len];对于(int i=0;i>4)&0x0f))。附加(hex.charat(b&0x0f));}}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<P>来源:http://www.tutorials-android.com/learn/how_to_encrypt_and_decrypt_strings.rhtml</P><div class="suo-content">[collapse title=""]<ul><li>我正在尝试实现解决方案1,但它需要一些类。你有完整的源代码吗?</li><li>不,我没有,对不起。但我只是简单地删除了<wyn>implements ICrypto</wyn>,然后将<wyn>throws CryptoException</wyn>改为<wyn>throws Exception</wyn>等等。所以你不再需要那些课程了。</li><li>但是HexEncoder类也丢失了?我在哪里能找到它?</li><li>我想,HexEncoder是BouncyCastle库的一部分。你可以下载它。或者你可以谷歌"字节[]到十六进制"和另一种方式在爪哇回合。</li><li>谢谢你,马可。但我注意到,在第一个实现中有3个方法<wyn>getSecretKey</wyn>、<wyn>getHash</wyn>、<wyn>generateSalt</wyn>未使用。也许我错了,但这个类在实践中如何用于加密字符串?</li><li>它们是未使用的,因为类似乎不完整。例如,您必须使用<wyn>getSecretKey()</wyn>来获取您需要的<wyn>SecretKey</wyn>等。它既不是静态的,也不是动态的。</li><li>此实现仅可用于存储本地数据,而不用于因随机盐而交换这些数据。</li><li>对于hexencode,可以使用这个,对于hex to bye[],可以使用这个</li><li>检查此链接codingaffairs.blogspot.com/2016/06/&hellip;</li></ul>[/collapse]</div><hr><P>您在问题中给出的两个实现都不是完全正确的,您给出的两个实现都不应按原样使用。接下来,我将讨论Android中基于密码的加密的各个方面。好的。<P>键和哈希好的。<P>我将开始用salts讨论基于密码的系统。盐是随机产生的数字。它不是"推导出来的"。实现1包括生成加密强随机数的<wyn>generateSalt()</wyn>方法。因为盐对安全很重要,一旦生成它就应该保密,尽管只需要生成一次。如果这是一个网站,那么保持SALT机密相对容易,但是对于已安装的应用程序(对于台式机和移动设备),这将更加困难。好的。<P>方法<wyn>getHash()</wyn>返回给定密码和salt的散列值,并连接到单个字符串中。使用的算法是sha-512,它返回512位散列值。此方法返回一个哈希值,该哈希值对于检查字符串的完整性很有用,因此也可以通过只使用密码或salt调用<wyn>getHash()</wyn>,因为它只连接两个参数。因为这个方法不会在基于密码的加密系统中使用,所以我不会进一步讨论它。好的。<P>方法<wyn>getSecretKey()</wyn>从密码的<wyn>char</wyn>数组和十六进制编码的salt(从<wyn>generateSalt()</wyn>返回)中派生密钥。使用的算法是pkds5中的pbkdf1(我想是sha-256作为散列函数),返回一个256位的密钥。<wyn>getSecretKey()</wyn>通过反复生成密码、salt和计数器的散列(最多可达<wyn>PBE_ITERATION_COUNT</wyn>中给出的迭代次数,此处为100)来生成密钥,以增加发动强力攻击所需的时间。salt的长度应该至少与生成密钥的长度相同,在本例中,至少为256位。应尽可能长地设置迭代计数,而不会造成不合理的延迟。有关键派生中的盐和迭代计数的更多信息,请参阅RFC2898中的第4节。好的。<P>但是,如果密码包含Unicode字符,即需要超过8位的字符,那么在Java的PBE中的实现是有缺陷的。如<wyn>PBEKeySpec</wyn>中所述,"pkcs 5中定义的pbe机制只查看每个字符的低阶8位"。为了解决这个问题,您可以在将密码传递给<wyn>PBEKeySpec</wyn>之前,尝试生成密码中所有16位字符的十六进制字符串(只包含8位字符)。例如,"abc"变为"004100420043"。还要注意,pbekeyspec"以char数组的形式请求密码,因此在完成时可以用<wyn>clearPassword()</wyn>覆盖密码"。(关于"在内存中保护字符串",请参见这个问题。)不过,我不认为将salt表示为十六进制编码的字符串有任何问题。好的。<P>加密好的。<P>一旦生成了一个密钥,我们就可以使用它来加密和解密文本。在实现1中,使用的密码算法是<wyn>AES/CBC/PKCS5Padding</wyn>,即密码块链(cbc)密码模式中的AES,填充在pkcs 5中定义。(其他AES密码模式包括计数器模式(CTR)、电子码本模式(ECB)和伽罗瓦计数器模式(GCM)。堆栈溢出的另一个问题包含详细讨论各种AES密码模式和推荐使用的模式的答案。也要注意,对CBC模式加密有几个攻击,其中一些在RFC7457中提到。)好的。<P>如果加密文本对外部人员可用,则建议对加密数据(以及可选的附加参数)应用消息验证代码或MAC,以保护其完整性(一种称为相关数据认证加密的技术,AEAD,如RFC 5116所述)。这里流行的是基于hash的macs,或hmacs,它基于sha-256或其他安全的hash函数。但是,如果使用的是Mac,为了避免相关的密钥攻击,建议使用长度至少是普通加密密钥两倍的密钥:前半部分用作加密密钥,后半部分用作Mac的密钥。(也就是说,在本例中,从密码和salt生成一个单独的秘密,然后将该秘密分为两部分。)好的。<P>Java实现好的。<P>实现1中的各种函数使用特定的提供者,即"bc"作为其算法。一般来说,不建议请求特定的提供者,因为并非所有的提供者都在所有Java实现中可用,无论是缺少支持,还是避免代码复制,或者由于其他原因。自2018年初android p preview发布以来,这一建议变得尤为重要,因为"BC"提供商的某些功能在这里已被弃用&mdash;请参阅android开发者博客中的文章"android p中的密码更改"。另请参见Oracle提供程序简介。好的。<P>因此,<wyn>PROVIDER</wyn>不应该存在,字符串<wyn>-BC</wyn>应该从<wyn>PBE_ALGORITHM</wyn>中删除。在这方面,实施2是正确的。好的。<P>一个方法捕获所有异常是不合适的,但只处理它可以处理的异常。问题中给出的实现可以抛出各种已检查的异常。方法可以选择只包装带有CryptoException的已检查异常,或者在<wyn>throws</wyn>子句中指定已检查异常。为了方便起见,在这里用CryptoException包装原始异常可能是合适的,因为类可能会抛出许多已检查的异常。好的。<P>android中的<wyn>SecureRandom</wyn>好的。<P>正如安卓开发者博客中"一些安全随机思想"一文中所详述的,2013年之前安卓版本中<wyn>java.security.SecureRandom</wyn>的实现存在一个缺陷,它降低了随机数的强度。通过将不可预测的随机数据块(如<wyn>/dev/urandom</wyn>的输出)传递给该类的<wyn>setSeed</wyn>方法,可以减轻此缺陷。好的。好啊.<div class="suo-content">[collapse title=""]<ul><li>在我看来,双重秘密的产生有点浪费,你可以很容易地将生成的秘密分成两部分,或者-如果没有足够的位可用-在秘密中添加一个计数器(第一个键是1,第二个键是2),然后执行一个哈希。不需要执行所有的迭代两次。</li><li>感谢您提供有关HMAC和盐的信息。这次我不使用HMAC,但以后可能会非常有用。总的来说,这无疑是件好事。</li><li>非常感谢所有的编辑和这个(现在)精彩介绍AES加密在爪哇!</li><li>provider参数是否需要,因为"bc"代表"bouncy castle",而这不是标准JDK的一部分?getInstance()将搜索正确的提供程序吗?</li><li>应该的。<wyn>getInstance</wyn>有一个仅使用算法名称的重载。示例:Ciff.GestStand()可以在Java实现中注册多个提供程序,包括BosiCaseCARS,并且这种重载搜索实现给定算法的其中一个提供者的列表。你应该试试看。</li><li>是的,它将按照security.getproviders()给出的顺序搜索提供程序,尽管现在它还将检查该提供程序在允许硬件辅助加密的in it()调用期间是否接受该密钥。更多详细信息请访问:docs.oracle.com/javase/6/docs/technotes/guides/security/cryp&zwnj;&8203;to/&hellip;。</li><li>感谢您的澄清!</li><li>@owlstead:谢谢你对双重秘密一代的评论。很抱歉之前没有评论,但我目前的回答包括你的想法(在最后一段"加密")。请参见编辑历史记录。</li><li>在Android中指定BC不是更好吗?</li></ul>[/collapse]</div><hr><P>#2不应该使用,因为它只使用"aes"(这意味着ECB模式的文本加密,一个大的否)作为密码。我来谈谈1。</P><P>第一个实现似乎遵循加密的最佳实践。尽管执行PBE所需的盐量和迭代次数都在短期内,但这些常数通常是可以的。此外,似乎是针对aes-256,因为pbe密钥生成使用256作为硬编码值(在所有这些常量之后都很遗憾)。它使用cbc和pkcs5padding,这至少是您所期望的。</P><P>完全缺少任何身份验证/完整性保护,因此攻击者可以更改密码文本。这意味着在客户机/服务器模型中填充Oracle攻击是可能的。这也意味着攻击者可以尝试更改加密数据。这可能会在某个地方导致一些错误,因为填充或内容不被应用程序接受,但这不是您想要的情况。</P><P>异常处理和输入验证可以得到增强,在我的书中捕捉异常总是错误的。furhtermore,这个类实现了icrypt,我不知道。我知道在一个类中只有没有副作用的方法有点奇怪。通常情况下,您会使这些静态的。没有对密码实例等的缓冲,因此每一个必需的对象都会被恶心地创建。但是,您可以从定义中安全地删除icrypto,在这种情况下,您还可以将代码重构为静态方法(或者重写为更面向对象的方法,这是您的选择)。</P><P>问题是,任何包装器总是对用例进行假设。所以说包装是对的还是错的是错误的。这就是为什么我总是尽量避免生成包装类的原因。但至少它看起来并没有明显的错误。</P><div class="suo-content">[collapse title=""]<ul><li>非常感谢您的详细回答!我知道这很遗憾,但我还不知道代码评审部分:D感谢您的提示,我会检查的。但在我看来,这个问题也很合适,因为我不想仅仅回顾一下这些代码片段。相反,我想问你在Android中实现AES加密时,哪些方面是重要的。你又是对的,这段代码是针对aes-256的。所以你会说这是一个安全的AES-256的一般实现?用例是我只想在数据库中安全地存储文本信息。</li><li>它看起来不错,但是不进行完整性检查和身份验证的想法会让我很困扰。如果你有足够的空间,我会认真考虑在密文上添加一个HMAC。也就是说,由于您可能只是想增加机密性,我认为这是一个很大的优势,但不是直接的要求。</li><li>但是如果目的只是让其他人不能访问加密信息,我不需要HMAC,对吗?如果他们改变密文,强制解密"错误"的结果,就没有真正的问题了,是吗?</li><li>如果这不在你的风险场景中,那就好了。如果他们能够在修改密码文本(填充甲骨文攻击)之后以某种方式触发系统重复解密,那么他们就可以在不知道密钥的情况下解密数据。如果他们只是在没有密钥的系统上获取数据,那么他们就不能这样做。但这就是为什么添加HMAC总是最佳做法。就我个人而言,我认为一个带有AES-128和HMAC的系统比没有AES-256的系统更安全,但如前所述,可能不需要。</li><li>好吧,现在我已经明白了一切:)再次非常感谢你的帮助!但我还有两个问题:我需要传递相同的密码和salt来进行解密和加密,对吗?否则我将无法再次解密密文。但是我怎么推断盐呢?我不能把相同的字符串用于所有加密和解密任务吗?而且:我在公共网站上找不到任何"icrypto"。所以这可能是代码的作者自己写的,对吧?没有这个表达式代码能工作吗?</li><li>如果您想要完整性,为什么不在galois/counter模式(aes-gcm)中使用aes?</li><li>谢谢,Kimvais,AES-GCM是个好主意!但不幸的是,在我的情况下,它是不可用的。因为我也不想储存盐,所以我将创建一个哈希并将其用作盐。所以硬编码盐是不安全的吗?我不知道你可以获得多少信息,如果你反编译Java/Android应用程序。</li><li>顺便问一下,gethash()的用途是什么?</li><li>这是你应该和这个班的创造者讨论的(如果有人有一个备用的水晶球,我当然感兴趣)。</li><li>…我只是认为您可以知道加密过程中在哪里需要散列,也许;)</li><li>据我所知,我对这篇文章的第二次修订是你的评论,而不是我的原文。</li></ul>[/collapse]</div><p><center>[wp_ad_camp_1]</center></p><hr><P>你问了一个很有趣的问题。就像所有的算法一样,密码密钥是"秘密酱汁",因为一旦公众知道了这一点,其他的一切都是如此。所以你可以通过谷歌来研究这个文档</P><P>安全</P><P>除了谷歌应用内计费,它还提供了对安全性的思考,这也是很有洞察力的。</P><P>计费最佳实践</P><div class="suo-content">[collapse title=""]<ul><li>感谢您提供这些链接!你所说的"当密码钥匙不在时,其他东西也不在"是什么意思?</li><li>我的意思是加密密钥必须是安全的,如果任何人都能得到,那么您加密的数据就和明文一样好。如果你觉得我的回答对你有帮助,请投赞成票:—)</li><li>当然,我觉得它很有用。我只是想解决这个问题。</li></ul>[/collapse]</div><hr><P>我在这里发现了一个很好的实现:http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html和https://github.com/nelenkov/android-pbe这也有助于我为Android寻找一个足够好的AES实现。</P><hr><P>使用BouncyCastle轻量级API。它提供256个原子发射光谱与PBE和盐。这里是示例代码,它可以加密/解密文件。</P>[cc]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();
    }
}