在Android中使用AESencryption的最佳做法是什么?

为什么我问这个问题:

我知道关于AESencryption的问题很多,即使是Android也是如此。 如果您searchnetworking,则会有很多代码片段。 但是在每一个页面上,在每个堆栈溢出的问题中,我发现另一个有重大差异的实现。

所以我创造了这个问题来find一个“最佳实践”。 我希望我们可以收集最重要的要求清单,并build立一个真正安全的实施!

我读了初始化载体和盐。 并不是我发现的所有实现都有这些function。 那么你需要它吗? 它增加了很多安全吗? 你如何实现它? 如果encryption的数据不能被解密,algorithm是否应该引发exception? 或者是不安全的,它应该只是返回一个不可读的string? algorithm可以使用Bcrypt而不是SHA?

那么我发现这两个实现呢? 他们好吗? 完美或缺less一些重要的东西? 这些是安全的?

该algorithm应该采取一个string和一个“密码”encryption,然后用该密码encryptionstring。 输出应该是一个string(hex或base64?)了。 当然,解密也是可能的。

Android的完美AES实现是什么?

实施#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; } } 

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

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

资料来源: http : //www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

键和哈希

我将开始讨论以盐为基础的密码系统。 盐是一个随机生成的数字。 这不是“推论”。 实现1包括一个generateSalt()方法,可以生成一个密码强的随机数。 因为盐对安全很重要,一旦生成就应该保密,虽然只需要生成一次。 如果这是一个网站,保持盐的秘密相对容易,但是对于已安装的应用程序(对于台式机和移动设备)来说,这将变得更加困难,因为这样的应用程序被认为不会保守秘密。

方法getHash()返回给定密码和salt的散列,并连接成单个string。 使用的algorithm是SHA-512,它返回一个512位散列。 这个方法返回一个散列,这对于检查string的完整性非常有用,所以也可以通过调用getHash()只用一个密码或一个salt来使用,因为它只是简单地连接两个参数。 由于这个方法不会在基于密码的encryption系统中使用,所以我不会再讨论它。

方法getSecretKey()从密码的char数组和一个hex编码的盐generateSalt()generateSalt()返回getSecretKey()派生一个密钥。 使用的algorithm是PKCS5中的PBKDF1(我认为),SHA-256作为散列函数,并返回一个256位的密钥。 getSecretKey()通过重复生成密码,salt和计数器的散列(直到在PBE_ITERATION_COUNT给出的迭代计数,这里是100)来PBE_ITERATION_COUNT ,以便增加进行powershell攻击所需的时间。 盐的长度至less要和生成的密钥一样长,在这种情况下至less要有256位。 迭代次数应尽可能长,而不会造成不合理的延迟。 有关密钥派生中salt和迭代计数的更多信息,请参阅RFC2898中的第4 节 。

但是,如果密码包含Unicode字符,那么在Java的PBE中的实现是有缺陷的,也就是说,那些需要表示8位以上的字符。 如PBEKeySpec ,“PKCS#5中定义的PBE机制只查看每个字符的低8位”。 要解决此问题,可以尝试在将密码传递给PBEKeySpec之前,生成密码中所有16位字符的hexstring(仅包含8位字符)。 例如,“ABC”变成“004100420043”。 实际上,你也可以使用char数组作为密码的参数,因为出于安全考虑,PBEKeySpec“以密码数组的forms请求密码,所以在完成时可以使用clearPassword() ]覆盖它。 尽pipe如此,我没有看到任何问题,将salt表示为一个hex编码的string。

encryption

一旦生成密钥,我们可以使用它来encryption和解密文本。 在实现1中,所使用的密码algorithm是AES/CBC/PKCS5Padding ,即在密码块链接(CBC)密码模式中的AES,其中在PKCS#5中定义了填充。 (其他AESencryption模式包括计数器模式(CTR),电子码本模式(ECB)和伽罗瓦计数器模式(GCM))。

如果encryption文本将被提供给外部人员,则build议将encryption数据(和可选的附加参数)应用于消息authentication代码或MAC,以保护其完整性。 这里stream行的是基于散列的MAC或HMAC,这些基于SHA-1,SHA-256或其他安全散列函数。 但是,如果使用MAC,则推荐使用至less是普通encryption密钥两倍的秘密,以避免相关的密钥攻击:前半部分用作encryption密钥,后半部分用作密钥苹果电脑。 (也就是说,在这种情况下,从密码和盐生成一个单独的秘密,并将这个秘密分成两部分。)

Java实现

实现1中的各种function使用特定的提供者,即“BC”作为其algorithm。 一般来说,不build议请求特定的提供者,因为并非所有的提供者都可用于所有Java实现,请参阅Oracle提供者简介 。

因此, PROVIDER不应存在​​,stringPBE_ALGORITHM应该可能从PBE_ALGORITHM删除。 实施2在这方面是正确的。

一个方法捕捉所有的exception是不合适的,而只是处理它可以的exception。 在你的问题中给出的实现可以抛出各种检查exception。 一个方法可以select只用CryptoException封装那些检查过的exception,或者在throws子句中指定那些检查过的exception。 为了方便起见,在这里用CryptoException封装原始的exception可能是合适的,因为这些类可能会抛出许多被检查的exception。

#2不应该被使用,因为它只使用“AES”(这意味着对文本进行ECB模式encryption,这是一个很大的禁止)的密码。 我会谈谈#1。

第一个实现似乎遵循encryption的最佳实践。 常量一般没问题,尽pipe执行PBE的salt大小和迭代次数都很短。 此外,它似乎是AES-256,因为PBE密钥生成使用256作为硬编码值(在所有这些常数之后是一个耻辱)。 它使用CBC和PKCS5Padding至less是你所期望的。

完全缺失的是任何authentication/完整性保护,因此攻击者可以更改密文。 这意味着在客户端/服务器模型中可以使用填充oracle攻击。 这也意味着攻击者可以尝试更改encryption的数据。 这可能会导致某些错误,因为填充或内容不被应用程序接受,但这不是您想要的。

exception处理和inputvalidation可以被增强,捕捉Exception在我的书中总是错误的。 Furhtermore,类实现了ICrypt,我不知道。 我知道只有在课堂上没有副作用的方法有点奇怪。 通常情况下,你会使这些静态。 密码实例没有caching,所以每个需要的对象都被创build为附录。 但是,你可以安全地从看起来的定义中删除ICrypto,在这种情况下,你也可以重构代码到静态方法(或重写它为更多的面向对象,你的select)。

问题是任何包装器都会对用例做出假设。 因此,说一个包装是对还是错是因为铺垫。 这就是为什么我总是试图避免生成包装类。 但至less这似乎不是明显的错误。

你问了一个非常有趣的问题。 与所有的algorithm一样,密钥是“秘密”,因为一旦公众所知,其他的一切都是如此。 所以你看看谷歌的这个文件的方式

安全

除此之外,Google应用内结算function还提供了深入的安全意见

billing_best_practices

使用BouncyCastle轻量级API。 它提供256与AES和PBE和盐。
这里是示例代码,它可以encryption/解密文件。

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

我在这里find了一个很好的实现: http : //nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html和https://github.com/nelenkov/android-pbe这也是有帮助的在我的追求一个足够好的AES实施Android