What are the best practices for using AES encryption in Android?

Why am I asking this question:

I know that there were a lot of questions about AES encryption, even for Android. And there are many code snippets when searching the web. But on every page, in every question, I find another implementation with significant differences.

So I created this question to find "best practice." Hope we can put together a list of the most important requirements and create a truly secure implementation!

I read about vectors and initialization salts. Not all implementations I found had these features. So do you need it? Does security increase security? How do you implement it? Should the algorithm throw exceptions if the encrypted data cannot be decrypted? Or is it unsafe and it should just return an unreadable string? Can an algorithm use Bcrypt instead of SHA?

What about these two implementations I found? They are allright? Perfect or some important things missing? Which of them is safe?

The algorithm should take the string and password for encryption, and then encrypt the string with this password. The result should contain a string (hex or base64?). Of course, decryption should also be possible.

What is the ideal implementation of AES for Android?

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

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

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

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

+83
android encryption aes
Dec 24 '11 at 2:50
source share
5 answers

None of the implementations that you give in your question are completely correct, and none of the implementations that you give should be used as is. In the future, I will discuss aspects of password-based encryption in Android.

Keys and Hashes

I will start discussing the password system with salts. Salt is a random number. This is not a "conclusion". Implementation 1 includes a generateSalt() method that generates a cryptographically strong random number. Since salt is important for safety, it should be kept secret after its creation, although it needs to be generated only once. If this is a website, then it is relatively easy to keep a secret, but for installed applications (for desktop and mobile devices) it will be much more difficult.

The getHash() method returns the hash of the given password and salt, combined in one line. The SHA-512 algorithm is used, which returns a 512-bit hash. This method returns a hash, which is useful for checking the integrity of the string, so it can also be used by calling getHash() with only a password or just salt, since it simply combines both parameters. Since this method will not be used in a password-based encryption system, I will not discuss it further.

The getSecretKey() method extracts the key from the char array getSecretKey() from the password and the hexadecimal salt returned by getSecretKey() generateSalt() . The algorithm used is PBKDF1 (I think) from PKCS5 with SHA-256 as a hash function and returns a 256-bit key. getSecretKey() generates a key by repeatedly generating hashes of the password, salt, and counter (up to the iteration counter specified in PBE_ITERATION_COUNT , here 100) to increase the time required to mount the attack by brute force. The length of the salt should be at least the length of the generated key, in this case at least 256 bits. The iteration counter should be set as long as possible without causing undue delay. For more information on salts and the number of iterations when obtaining a key, see Section 4 in RFC2898 .

However, an implementation in Java PBE has disadvantages if the password contains Unicode characters, i.e. those that require a representation of more than 8 bits. As stated in PBEKeySpec , "the PBE mechanism defined in PKCS # 5 only considers the lower 8 bits of each character." To work around this problem, you can try to generate a hexadecimal string (which will contain only 8-bit characters) from all 16-bit characters in the password before passing it to PBEKeySpec . For example, "ABC" becomes "004100420043". Also note that PBEKeySpec "asks for the password as an array of characters, so it can be overwritten [using clearPassword() ] after completion." (Regarding “protecting strings in memory,” see this question .) However, I don't see any problems with representing the salt as a hexadecimal string.

encryption

After the key is generated, we can use it to encrypt and decrypt the text. Implementation 1 uses the AES/CBC/PKCS5Padding , i.e., AES in the Cipher Block Chaining (CBC) encryption mode with padding defined in PKCS # 5. (Other AES encryption modes include counter mode (CTR), electronic code book mode (ECB ) and Galois counter mode (GCM). Another question about stack overflows contains answers that discuss in detail the various AES encryption modes that are recommended for use. Remember also that there are several CBC mode encryption attacks, some of which are mentioned in RFC 7457 .)

If the encrypted text will be accessible to outsiders, it is recommended to use a message authentication code or MAC for encrypted data (and, optionally, additional parameters) to protect its integrity (a method known as authenticated encryption with corresponding data, AEAD, is described in RFC 5116). Hash-based MAC addresses or HMAC-based MACs based on SHA-256 or other secure hash functions are popular here. However, if a MAC is used, a secret is used that is at least twice as long as a normal encryption key to avoid attacks on the associated key: the first half serves as the encryption key, and the second half serves as the key for the MAC. (That is, in this case, generate one secret from the password and salt and divide this secret into two parts.)

Java implementation

The various functions in implementation 1 use a particular provider, namely "BC", for their algorithms. In general, however, it is not recommended to request specific providers, since not all providers are available in all Java implementations, whether due to lack of support, to avoid duplication of code, or for other reasons. This tip has become especially important since the release of the Android P preview in early 2018, as some of the features from the "BC" provider are outdated there - see the Android Cryptography Changes article in the Android Developers Blog. See also Introduction to Oracle Providers .

Therefore, PROVIDER should not exist, and the -BC line should be removed from PBE_ALGORITHM . Implementation 2 is correct in this regard.

A method should not catch all exceptions, but only handle those exceptions that it can. The implementations given in your question can throw various checked exceptions. The method can choose to wrap only those checked exceptions with CryptoException or specify these checked exceptions in the throws . For convenience, it may be appropriate to wrap the original exception in a CryptoException, since exception classes could potentially be thrown.

SecureRandom on Android

As described in detail in the article “Some Thoughts on SecureRandom” on the Android Developers Blog, the implementation of java.security.SecureRandom in Android releases prior to 2013 has a flaw that reduces the number of random numbers it delivers. This flaw can be eliminated by passing an unpredictable and random data block (for example, the output of /dev/urandom ) to the setSeed method of this class.

+35
Dec 30 '11 at 20:19
source share

# 2 should never be used since it only uses “AES” (which means encryption in ECB mode by text, a big no-no) for encryption. I will just talk about # 1.

The first implementation seems to adhere to the best encryption methods. Constants are generally good, although the size of the salt and the number of iterations to perform PBE are on the short side. Moreover, it seems to be for AES-256, since PBE key generation uses 256 as a hard-coded value (a shame after all these constants). It uses CBC and PKCS5Padding, which at least you expect.

Any authentication / integrity protection is completely missing, so an attacker can modify the encryption text. This means that insult-based attacks are possible in the client / server model. It also means that an attacker may try to modify encrypted data. This will probably lead to some error somewhere, because the filling or content is not accepted by the application, but this is not the situation you want to be in.

Exception handling and input validation can be improved, catching an exception is always mistaken in my book. Furhtermore, the class implements ICrypt, which I do not know. I know that having only methods with no side effects in the class is a bit strange. Usually you will become static. Buffering Cipher instances, etc. Missing, so each required object gets created by ad-nauseum. However, you can safely remove ICrypto from the definition that seems, in this case you can also reorganize the code into static methods (or rewrite it to be more object oriented, of your choice).

The problem is that any shell always makes assumptions about a use case. To say that the wrapper is right or wrong is therefore a bunch. This is why I always try to avoid generating wrapper classes. But at least this is clearly not the case.

+15
Dec 29 '11 at 3:16 a.m.
source share

You asked a rather interesting question. As with all algorithms, the encryption key is a "secret sauce", as it was once known to the public, everything else too. So you study this Google document

security

In addition, Google In-App Billing also gives thought to security, which is also insightful.

billing_best_practices

+1
Jan 01 '11 at 17:04
source share

Use the lightweight BouncyCastle API. It provides 256 AES with PBE and salt.
Here is an example of code that can encrypt / decrypt files.

 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
Dec 30 '11 at 21:29
source share

I found a nice implementation here: http://nelenkov.blogspot.fr/2012/04/using-password-based-encryption-on.html as well as https://github.com/nelenkov/android-pbe This was also useful in my search for a fairly good implementation of AES for Android.

0
Feb 11 '15 at 17:23
source share



All Articles