Posted by Sergio Giro, software engineer
If your Android app derives keys using the SHA1PRNG algorithm from the Crypto
provider, you must start using a real key derivation function and possibly re-encrypt your data.
The Java Cryptography Architecture allows developers to create an instance of a class like a cipher, or a pseudo-random number generator, using calls like:
SomeClass.getInstance("SomeAlgorithm", "SomeProvider");
Or simply:
SomeClass.getInstance("SomeAlgorithm");
For instance,
Cipher.getInstance(“AES/CBC/PKCS5PADDING”);
SecureRandom.getInstance(“SHA1PRNG”);
On Android, we don’t recommend specifying the provider. In general, any call to
the Java Cryptography Extension (JCE) APIs specifying a provider should only be
done if the provider is included in the application or if the application is
able to deal with a possible ProviderNotFoundException.
Unfortunately, many apps depend on the now removed “Crypto” provider for an
anti-pattern of key derivation.
This provider only provided an implementation of the algorithm “SHA1PRNG” for
instances of SecureRandom. The problem is that the SHA1PRNG algorithm is not
cryptographically strong. For readers interested in the details, On
statistical distance based testing of pseudo random sequences and experiments
with PHP and Debian OpenSSL,Section 8.1, by Yongge Want and Tony Nicol,
states that the “random” sequence, considered in binary form, is biased towards
returning 0s, and that the bias worsens depending on the seed.
As a result, in Android N we are deprecating the
implementation of the SHA1PRNG algorithm and the Crypto provider altogether.
We’d previously covered the issues with using SecureRandom for key derivation a
few years ago in href=http://android-developers.blogspot.com/2013/02/using-cryptography-to-store-credentials.html>Using
Cryptography to Store Credentials Safely. However, given its continued use,
we will revisit it here.
A common but incorrect usage of this provider was to derive keys for encryption
by using a password as a seed. The implementation of SHA1PRNG had a bug that
made it deterministic if setSeed() was called before obtaining output. This bug
was used to derive a key by supplying a password as a seed, and then using the
"random" output bytes for the key (where “random” in this sentence means
“predictable and cryptographically weak”). Such a key could then be used to
encrypt and decrypt data.
In the following, we explain how to derive keys correctly, and how to decrypt
data that has been encrypted using an insecure key. There’s also a
full example, including a helper class to use the deprecated SHA1PRNG
functionality, with the sole purpose of decrypting data that would be otherwise
unavailable.
Keys can be derived in the following way:
- If you're reading an AES key from disk, just store the actual key and don't go through this weird dance. You can get a SecretKey for AES usage from the bytes by doing:
SecretKey key = new SecretKeySpec(keyBytes, "AES");
- If you're using a password to derive a key, follow href="http://nelenkov.blogspot.com/2012/04/using-password-based-encryption-on.html">Nikolay Elenkov's excellent tutorial with the caveat that a good rule of thumb is the salt size should be the same size as the key output. It looks like this:
/* User types in their password: */
String password = "password";
/* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size
as the output (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
byte[] salt; // Should be of saltLength
/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);
/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
That's it. You should not need anything else.
To make transitioning data easier, we covered the case of developers that have
data encrypted with an insecure key, which is derived from a password every
time. You can use the helper class InsecureSHA1PRNGKeyDerivator
in
the example app to derive the key.
private static SecretKey deriveKeyInsecurely(String password, int
keySizeInBytes) {
byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
return new SecretKeySpec(
InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(
passwordBytes, keySizeInBytes),
"AES");
}
You can then re-encrypt your data with a securely derived key as explained
above, and live a happy life ever after.
Note 1: as a temporary measure to keep apps working, we decided to still create
the instance for apps targeting SDK version 23, the SDK version for Marshmallow,
or less. Please don't rely on the presence of the Crypto provider in the Android
SDK, our plan is to delete it completely in the future.
Note 2: Because many parts of the system assume the existence of a SHA1PRNG
algorithm, when an instance of SHA1PRNG is requested and the provider is not
specified we return an instance of OpenSSLRandom, which is a strong source of
random numbers derived from OpenSSL.