0

I have an Android app that internally relies on a SQLite database that stores encrypted user data. The data is encrypted using AES-256, with a key generated by the PBKDF2-HMACSHA256 algorithm. The application has the need to detect manipulation of the data either by malicious or non-malicious methods (i.e. data corruption or someone purposely tampering with the database).

As far as I can tell I have two solid options on how to securely generate a hash to represent this data. Either, I can rely on a HMAC-SHA256 algorithm keyed with the same password used to generate the AES-256 key to generate signatures of the data in each row of the database. Alternatively, I could generate an ordinary SHA-256 hash of the decrypted data stored in memory before re-encrypting it to update the database row. So in theory, only the authenticated user with the correct key can decrypt the data and therefore only that user can generate this "signature".

Here is my question: To use HMAC-SHA256 will cause complications with exactly how to securely persist the password as the key or some way to leverage the SHA-256 key as the HMAC key. Either way, it seems un-necessarily painful and complexity is in general, bad new for secure systems. Would it be equally secure to rely on a ordinary SHA-256 hash of decrypted data as it would be to use a HMAC style hash algorithm? As in, are they equally difficult/impossible to forge a signature.

1

Ideally you'd use an integrated authenticated encryption algorithm like AES-GCM, but since you already have encrypted data it's worth looking at options that don't require you to reencrypt everything.

The safest composition of encryption and authentication is encrypt-then-authenticate. Your candidate solution to apply HMAC to the ciphertexts is therefore best. Make sure however that you also include all the relevant context in the HMAC calculation:

  1. Whatever IV you're passing to the encryption should be part of the input to the MAC.
  2. It may also make sense to include other contextual data in the MAC input. For example, if there's a natural key on the row where the decrypted data is stored, including that in the input to the MAC defends against attacks where somebody swaps the encrypted values across different records. (See this answer of mine to a different question.)

To derive the HMAC key, you have a few options:

  1. PBDKF2 supports generating long outputs, with a user-supplied output length. You can just ask for a longer output and use the first part for encryption (will have the same value as you already get) and the extra bits for the MAC. Serious downside of this: it may double the time your PBKDF2 derivations run.
  2. You could use HKDF-HMAC-SHA256 to derive subkeys from the key you're deriving from PBKDF2. Ideally you'd want to derive both the encryption and MAC keys from the PBKDF2 key, but for backward compatibility you could cheap out and use the PBDKF2 master key for encryption as you do now, and an HKDF-derived key off it for the MAC. HKDF is a very simple algorithm built on HMAC so even if your libraries don't have it you shouldn't feel too scared to implement it. (If you're feeding it the output of PBKDF2, you only need the HKDF-expand function, and can skip the HKDF-extract.)
  3. Cheap out and use the same PBKDF2-derived key for both AES and HMAC. Using the same key for both purposes is bad in principle (as @Scovetta has pointed out), but it's likely safe in this one exceptional context because you're encrypting with AES but authenticating with HMAC-SHA256, which are very different algorithms. This counts a bit like "living on the edge" but there's much more reason to suspect it's OK than to suspect it's bad. (If on the other hand your cipher and MAC were both based on AES, or if there is ever a possibility that you might switch to an AES-based MAC later, using the same key for both would be very scary.)

Of these I'd go with #2. It's the most principled and it's only a few extra HMAC-SHA256 calls, which you're already calling thousands of times with PBKDF2 anyway.

Hashing the plaintext is not a very good idea. The biggest problem is that it enables an attacker to test guesses as to what plaintexts are—it's exactly the same sort of attack as cracking passwords hashed with fast hash functions, GPUs can just breeze through guesses at high speeds.

2

There are algorithms available that provide tamper protection. AES-GCM is one such algorithm. Per https://en.wikipedia.org/wiki/Galois/Counter_Mode

GCM is an authenticated encryption algorithm designed to provide both data authenticity (integrity) and confidentiality.

AES-GCM is available on Android API 26+. https://developer.android.com/reference/javax/crypto/Cipher.html I recommend using an algorithm that already has authenticity checking built into it over trying to implement this sort of thing on your own.

0

First, what are you trying to protect against? Random bit flips, or intentional modification by an attacker? If it's random bit flips, then Android full-disk-encryption might suffice to detect this, if enabled. If it's an intentional attack, then you should ask whether that same attacker would be capable of getting the same key and re-MAC'ing the row correctly?

Assuming you still need integrity protection, can you delegate this out to the driver instead of doing it yourself? SQLCipher might be a viable solution, and includes page-level MACs for integrity protection.

While GCM provides authentication, there are some disadvantages that you should be aware of.

You shouldn't use the same key for both encryption and HMACing. Instead, create one key (securely) and then derive both the encryption and HMAC from that. There are lots of ways to do this-- for example:

x = <encryption key>
enc_key = HMAC-SHA-256(x, 'encryption key')
hmac_key = HMAC-SHA-256(x, 'hmac key')

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.