What Are Best Practices for Using Aes Encryption in Android

What are best practices for using AES encryption in Android?

Neither implementation you give in your question is entirely correct, and neither implementation you give should be used as is. In what follows, I will discuss aspects of password-based encryption in Android.

Keys and Hashes

I will start discussing the password-based system with salts. The salt is a randomly generated number. It is not "deduced". Implementation 1 includes a generateSalt() method that generates a cryptographically strong random number. Because the salt is important to security, it should be kept secret once it is generated, though it only needs to be generated once. If this is a Web site, it's relatively easy to keep the salt secret, but for installed applications (for desktop and mobile devices), this will be much more difficult.

The method getHash() returns a hash of the given password and salt, concatenated into a single string. The algorithm used is SHA-512, which returns a 512-bit hash. This method returns a hash that's useful for checking a string's integrity, so it might as well be used by calling getHash() with just a password or just a salt, since it simply concatenates both parameters. Since this method won't be used in the password-based encryption system, I won't be discussing it further.

The method getSecretKey(), derives a key from a char array of the password and a hex-encoded salt, as returned from generateSalt(). The algorithm used is PBKDF1 (I think) from PKCS5 with SHA-256 as the hash function, and returns a 256-bit key. getSecretKey() generates a key by repeatedly generating hashes of the password, salt, and a counter (up to the iteration count given in PBE_ITERATION_COUNT, here 100) in order to increase the time needed to mount a brute-force attack. The salt's length should be at least as long as the key being generated, in this case, at least 256 bits. The iteration count should be set as long as possible without causing unreasonable delay. For more information on salts and iteration counts in key derivation, see section 4 in RFC2898.

The implementation in Java's PBE, however, is flawed if the password contains Unicode characters, that is, those that require more than 8 bits to be represented. As stated in PBEKeySpec, "the PBE mechanism defined in PKCS #5 looks at only the low order 8 bits of each character". To work around this problem, you can try generating a hex string (which will contain only 8-bit characters) of all 16-bit characters in the password before passing it to PBEKeySpec. For example, "ABC" becomes "004100420043". Note also that PBEKeySpec "requests the password as a char array, so it can be overwritten [with clearPassword()] when done". (With respect to "protecting strings in memory", see this question.) I don't see any problems, though, with representing a salt as a hex-encoded string.

Encryption

Once a key is generated, we can use it to encrypt and decrypt text.

In implementation 1, the cipher algorithm used is AES/CBC/PKCS5Padding, that is, AES in the Cipher Block Chaining (CBC) cipher mode, with padding defined in PKCS#5. (Other AES cipher modes include counter mode (CTR), electronic codebook mode (ECB), and Galois counter mode (GCM). Another question on Stack Overflow contains answers that discuss in detail the various AES cipher modes and the recommended ones to use. Be aware, too, that there are several attacks on CBC mode encryption, some of which are mentioned in RFC 7457.)

Note that you should use an encryption mode that also checks the encrypted data for integrity (e.g., authenticated encryption with associated data, AEAD, described in RFC 5116). However, AES/CBC/PKCS5Padding doesn't provide integrity checking, so it alone is not recommended. For AEAD purposes, using a secret that's at least twice as long as a normal encryption key is recommended, to avoid related key attacks: the first half serves as the encryption key, and the second half serves as the key for the integrity check. (That is, in this case, generate a single secret from a password and salt, and split that secret in two.)

Java Implementation

The various functions in implementation 1 use a specific provider, namely "BC", for its algorithms. In general, though, it is not recommended to request specific providers, since not all providers are available on all Java implementations, whether for lack of support, to avoid code duplication, or for other reasons. This advice has especially become important since the release of Android P preview in early 2018, because some functionality from the "BC" provider has been deprecated there — see the article "Cryptography Changes in Android P" in the Android Developers Blog. See also the Introduction to Oracle Providers.

Thus, PROVIDER should not exist and the string -BC should be removed from PBE_ALGORITHM. Implementation 2 is correct in this respect.

It is inappropriate for a method to catch all exceptions, but rather to handle only the exceptions it can. The implementations given in your question can throw a variety of checked exceptions. A method can choose to wrap only those checked exceptions with CryptoException, or specify those checked exceptions in the throws clause. For convenience, wrapping the original exception with CryptoException may be appropriate here, since there are potentially many checked exceptions the classes can throw.

SecureRandom in Android

As detailed in the article "Some SecureRandom Thoughts", in the Android Developers Blog, the implementation of java.security.SecureRandom in Android releases before 2013 has a flaw that reduces the strength of random numbers it delivers. This flaw can be mitigated as described in that article.

Best Practice: Which AES settings to use for Android KeyStore

TL; DR: Prefer AES/GCM/NoPadding. Do not ever use AES/ECB/*.


In your situation, you'll want to prefer authenticated symmetric encryption, and the only option that provides this in the list you link is AES/GCM/NoPadding.

The benefit for you here is that, not only is your data encrypted, it is also safe from tampering - if someone or something modifies the stored data, you'll get an exception when you attempt to decrypt it. The other listed modes do not have this property. This means that the stored ciphertext could be modified and you wouldn't know - it may or may not still decrypt (I say may or may not as it can throw an exception in other circumstances, like bad padding after modification).

The drawback (and it isn't much of one) is that you must ensure you never use the same key and nonce together. If you do - and an attacker can access or view two different sets of ciphertext - it becomes trivial for them to break and decrypt. The easiest way around this is to simply always generate a random nonce. You'll be able to encrypt 2^96 times before you run into any issues!

If you can't or don't want to use AES/GCM/NoPadding for whatever reason, select from AES/CTR/NoPadding or AES/CBC/PKCS7Padding. Both have their own drawbacks. You'll need to find a way to prevent tampering yourself (HMACs are usually used). I tend to prefer AES/CTR/NoPadding as it is very similar (at least to use) to AES/GCM/NoPadding.

Lastly, do not use anything ECB related. ECB BAD.

Data encryption on Android, AES-GCM or plain AES?

In 2012 the answer is to go for GCM, unless you have serious compatibility issues.

GCM is an Authenticated Encryption mode. It provides you with confidentiality (encryption), integrity, and authentication (MAC) in one go.

So far, the normal modes of operation have been ECB (which is the default for Java), CBC, CTR, OFB, and a few others. They all provided encryption only. Confidentiality by itself is seldom useful without integrity though; one had to combine such classic modes with integrity checks in an ad-hoc way. Since cryptography is hard to get right, often such combinations were insecure, slower than necessary or even both.

Authenticated Encryption modes have been (fairly recently) created by cryptographers to solve that problem. GCM is one of the most successful: it has been selected by NIST, it efficient, it is is patent free, and it can carry Additional Authenticated Data (that is, data which stays in the clear, but for which you can verify authenticity). For a description of other modes see this excellent article of Matthew Green.

Coming to your concerns:

  • Padding: by default, Java uses PKCS#7 padding. That works, but it is often vulnerable to padding oracle attacks which are best defeated with a MAC. GCM embeds already a MAC (called GMAC).

  • Authentication: AES-GCM only takes one AES key as input, not passwords. It will tell you if the AES key is wrong or the payload has been tampered with, but such conditions are treated as one. Instead, you should consider using an appropriate Key Derivation Algorithm like PBKDF2 or bcrypt to derive the AES key from the password. I don't think it is always possible to tell if the password is incorrect or if the payload has been modified, because the data necessary to verify the former can always be corrupted. You can encrypt a small known string (with ECB AES), send it along, and use it to verify if the password is correct.

  • Minimise overhead: At the end of the day, all modes leads to the same overhead (around 10-20 bytes) if you want authentication. Unless you are working with very small payloads, this point can be ignored.

  • Performance: GCM is pretty good in that it is an online mode (no need to buffer the whole payload, so less memory), it is parallelizable, and it requires one AES operation and one Galois multiplication per plaintext block. Classic modes like ECB are faster (one AES operation per block only), but - again - you must also factor in the integrity logic, which may end up being slower than GMAC.

Having said that, one must be aware that GCM security relies on a good random number generation for creation of the IV.

Advantages of using AES Encryption to encrypt and decrypt password vs storing password in JBOSS vault

I was thinking that, because the Secret key and salt are stored in the code, it is possible that the code can be reverse compiled to get these values.

Correct. AES encrypting that password accomplishes almost nothing, and in fact makes things worse: It looks encrypted (because it is), and one would assume the persons doing the encryption wouldn't be so incredibly dense as to leave the key right there next to the password file.

Except that it is effectively right there (they'd have to decompile the class files but that's not difficult and cannot be made difficult), so you've created the wrong impression.

Your security team needs to give you threat models to work with, they can't just say "do not read password from file", because that is impossible. Why can you not do that? What avenue of attack do they want to mitigate?

Examples:

  • I do not want a syadmin casually cat-ing that file and thus smearing the password all over their screen and in their terminal app's history buffers for anybody to just shouldersurf.

ANSWER: Just base64 it. Yes, it's not crypto at all, but at least it makes no bones about it: Folks will see its base64 and assuming they aren't idiots know that means the password is right there. But it's protected against shouldersurfing and 'accidental' recollection (where someone has seen it with their eyes and may therefore just remember it even if they don't intend to). Someone has to go out of their way to unbase64 it, and if the rules say you can't do that, at least you've now forced an employee to outright break rules and potentially be committing a crime.

  • I'm afraid someone will hack the server just barely enough to make it read files and echo them to the hacker.

Then the base64 thing does nothing, nor does the AES plan (as they can also make your webserver cat its own jars and class files, probably). One solution can be that the script that starts the server reads the file (and is root-operated, running the server under a webserver account) - that script reads the password (thus allowing you to make that file owned by root and unreadable by the webserver account), passes it as argument or environment var. Of course, this requires that you consider the risk of leaking an env var as considerably lower than a text file. Which is certainly possible. Alternatively, the script can write the password in a plain text file readable by the webserver user, and the webserver will read it, then delete the file. This isn't common, but it shows the point of threat models: Once you know what you're fighting, you can come up with a plan and execute accordingly.

  • I want to use JBoss Password Vault

That is not sensible security policy: That is not a threat model. JPV doesn't solve any of these problems, to boot.

  • I want a hacker that gains full access to the box, including root and/or write-access for the webserver user to not be able to use that as a springboard to hack the DB.

This is impossible, if the security team tells you this is the threat they need you to mitigate, you can tell them to go fetch Harry Potter's magic wand, because without it, you can't deliver. The hacker can simply rewrite your own classes/jars into sending the password to the hacker's servers, for example. This is strongly indicative your security team doesn't know how to do their job: They think of risks no matter how unlikely and demand it is 'protected against' (not really a thing; you can reduce and mitigate, security isn't black and white) without considering threat models or tradeoffs.

Get them educated, or decide to lie to them. You can't win when they act like this otherwise. Go over their heads maybe and get the boss involved.

  • I want a hacker that manages to obtain a clone of the entire disk to not be able to access the DB.

Doable, but tricky. One easy way is that the server won't know the password either and will boot in an admin-only-mode, where the admin types the db password into a form which then unlocks the server to run properly. The server can then retain this password in memory only, thus foiling any disk copies. Except, you better turn of swap or store that on a different disk!

If you don't want that manual action, there's TPM chips (windows/linux systems generally) or T2 (apple). I don't know of any java-accessible tools that can do this, or DBs that can. These kinds of algorithms require a challenge/response model, you can't just 'store a password' in these in a meaningful way.

Ask the security team for a budget of 80k or so. If they balk, well, they've learned something. Security is a game of tradeoffs.

iOS and Android AES Encryption (No UINT in Java)

You might notice a difference between the two printed arrays because java by default displays a byte as a signed value. But in reality those are actually equal. To make it more clear I'll add a little table with the last 5 values of the example IV array you provided.

|----------------------------------------|
| hex | 46 | FD | EC | 5C | 46 |
|----------------------------------------|
| unsigned | 70 | 253 | 236 | 92 | 70 |
|----------------------------------------|
| signed | 70 | -3 | -20 | 92 | 70 |
|----------------------------------------|

So they are actually the same (bit wise), only printed diffently as they are interpreted as different values. If you want to make sure things are correct, I would suggest looking at a few numbers with a calculator on programming mode. Usually there is a way to set the byte/word length so you can play around with signed vs unsigned interpretation of the same Hexadecimal value (there should also be a bit-representation of the value).

As an alternative I found a small website containing a signed vs unsigned type-bit/hex converter, which will do the trick as well. (make sure you select either char-type, otherwise the signed values will be incorrect)


So in the IV-bytes part of the code there shouldn't be any problem. There might be one however when you create your String using only a byte-array as parameter. e.i:

byte[] decryptedData = decrypt(encryptedData, aesDeviceKey.getBytes(), aesIV.getBytes());
System.out.println("ble_ Cipher Decrypt:" + new String(decryptedData));

Since most likely the used Charset is not UTF-8. (you can determine that by calling Charset#defaultCharset, and check its value). The alternative would be:

new String(decryptedData, StandardCharsets.UTF_8)

or:

new String(decryptedData, "UTF-8");


Related Topics



Leave a reply



Submit