0

I'm trying to implement AES encryption ,in Android, which uses a pass phrase to generate the SecretKey. I'm passing the same byte[] as initialization vector to the ciphers and as salt when generating the SecretKey with PBKDF2.

The passphrase is supplied by the user each time an encryption/decryption is needed.

As of now, I only need to encrypt one value in my database (if that makes any difference).

Questions:

  1. I'm wondering if using the same byte[] as IV and salt weakens the encryption?
  2. Is there a reason to switch from CBC to GCM other then the data integrity functionality GCM provides?
  3. I've read about CBC being prone to BEAST attack, is using a new random IV per message, as demonstrated bellow, mitigates BEAST attack?

Current source code:

public class AesEncryption {
    private static final int KEY_SIZE = 16;
    private static final int OUTPUT_KEY_LENGTH = 256;
    private static final int ITERATIONS = 1000;

    private String mPassphraseOrPin;

    public AesEncryption(String passphraseOrPin) {
        mPassphraseOrPin = passphraseOrPin;
    }

    public void encrypt(String id, String textToEncrypt) throws Exception {
        byte[] iv = getIv();
        SecretKey secretKey = generateKey(iv);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));

        byte[] cipherText = cipher.doFinal(textToEncrypt.getBytes("utf-8"));
        byte[] ivCipherText = arrayConcat(iv, cipherText);
        String encryptedText = Base64.encodeToString(ivCipherText, Base64.NO_WRAP);

        storeEncryptedTextInDb(id, encryptedText);
    }

    public String decrypt(String id) throws Exception {
        String encryptedText = getEncryptedTextFromDb(id);

        byte[] ivCipherText = Base64.decode(encryptedText, Base64.NO_WRAP);
        byte[] iv = Arrays.copyOfRange(ivCipherText, 0, KEY_SIZE);
        byte[] cipherText = Arrays.copyOfRange(ivCipherText, KEY_SIZE, ivCipherText.length);

        SecretKey secretKey = generateKey(iv);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
        String decrypted = new String(cipher.doFinal(cipherText), "utf-8");

        return decrypted;
    }

    public SecretKey generateKey(byte[] salt) throws Exception {
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec keySpec = new PBEKeySpec(mPassphraseOrPin.toCharArray(), salt, ITERATIONS, OUTPUT_KEY_LENGTH);
        SecretKey tmp = secretKeyFactory.generateSecret(keySpec);
        return new SecretKeySpec(tmp.getEncoded(), "AES");
    }

    private byte[] getIv() {
        byte[] salt = new byte[KEY_SIZE];
        new SecureRandom().nextBytes(salt);
        return salt;
    }

    private byte[] arrayConcat(byte[] one, byte[] two) {
        byte[] combined = new byte[one.length + two.length];
        for (int i = 0; i < combined.length; ++i) {
            combined[i] = i < one.length ? one[i] : two[i - one.length];
        }
        return combined;
    }
}
guy.gc
  • 3,359
  • 2
  • 24
  • 39
  • I'm presuming a local Android database? – Maarten Bodewes Dec 29 '15 at 12:21
  • @MaartenBodewes yes. I'm thinking of saving the encrypted text into SharedPreferences. – guy.gc Dec 29 '15 at 12:30
  • OK, in that case there is no chance of attack with regards to data in transit (e.g. CBC padding oracle attacks). Unfortunately I cannot perform a full security review - the answer isn't that. In general it pays off to look at the protection options offered by the platform itself. – Maarten Bodewes Dec 29 '15 at 12:48

1 Answers1

2

I'm wondering if using the same byte[] as IV and salt weakens the encryption?

Yes it does.

For the salt: if you don't randomize the salt then an attacker can pre-calculate a table with passwords and password hashes. This is called a rainbow table. Furthermore, if anybody has the same password it would result in the same key. It's strongly recommended to generate a salt per user and - if feasible - a new salt each time the value is re-encrypted.

For the IV: if you re-encrypt starting blocks containing the same plaintext then the ciphertext will repeat blocks. An attacker can use this to extract information from this. Simple example: encrypting "Yes" or "No" twice will clearly be distinguishable from first encrypting "Yes" and then "No". Generally you should generate a random IV and store it with the ciphertext. This is recommended even if the salt (and thus the key) is randomized. It of course depends on your threat model if this makes a difference in the real world.

Is there a reason to switch from CBC to GCM other then the data integrity functionallity GCM provides?

GCM provides integrity and authenticity of the plaintext. Functionally it's just AES in CTR mode with an authentication tag. It depends on your threat model if you need integrity and authenticity of the plaintext (and possibly Additional Authenticated Data or AAD). It won't add any functionality otherwise.

If you're just after keeping your data confidential then you may not need GCM. If you want to protect it against changes made by an attacker then you do need it. In that case however you also need to protect against replay attacks.

I've read about CBC being prone to BEAST attack, is using a new random IV per message, as demonstrated bellow, mitigates BEAST attack?

The BEAST attack is a browser based attack against SSL/TLS. By definition it doesn't apply against database encryption, especially with regards to data at rest. A whole slew of attacks can possibly be raised, but BEAST depends on dynamic data within a TLS connection.


Notes:

  • Length based attacks are often forgotten as ciphers / cipher modes do not protect against them. They may be applicable none-the-less. GCM leaks slightly more information about the length of the plaintext compared to CBC.

  • It may also be interesting for an attacker to see if a value is re-encrypted or not.

  • 1000 is not considered a secure iteration count / work factor anymore. You may want to upgrade it (and create a upgrade strategy).

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Thanks a lot @Maarten for the highly informative answer. So I should use a random salt per encrypted value and store it along side the encrypted text? – guy.gc Dec 29 '15 at 13:18
  • 2
    Yes. I would recommend that you also encode something of a version indicator in case you want to upgrade later (e.g. use a higher work factor). Either derive the IV (just like the key) or store it alongside as well if you have room for it. – Maarten Bodewes Dec 29 '15 at 13:20
  • Adding versioning seems like a good idea. Encrypting with the following code on an Old Samsung S3 device and work factor of 1000 took around 1200 millis. Is there a significant benefit to increasing the work factor? If I increase it, I would most definitely have to come up with some strategy dependent on device specifications and that would add "extra logic" to an already complicated algorithm. Is there an upper limit to the work factor, after which the gain is regarded as minimal? – guy.gc Dec 29 '15 at 13:30
  • 2
    The work factor is the same for the user and the adversary, although the adversary can use highly targeted systems. The work factor is linear for PBKDF2, so there really isn't an upper limit. You can use other measures such as making sure that the password provided by the user is secure enough if you don't want to up the work factor / iteration count. A strong password is still the best line of defense. Currently the minimum is seen as about 40K iterations, but I can see that such a high count may be an issue on Android devices. A maximum retry count is also great but not for data at rest. – Maarten Bodewes Dec 29 '15 at 13:36