Peter R
Peter R

Reputation: 3516

Best way to use AES encryption with HMAC for purely client side encryption/decryption

I'm just playing around with a test app to password encrypt some text, then decrypt with the password. I want everything done client-side (currently using CryptoJS).

The issue I'm having is AES has no notion of if the text is decrypted correctly or not. I could decrypt with the wrong password and not know(obviously the text would be unreadable to a human, but the code wouldn't know that). The encoding will throw an error a lot of the time, but not always.

My solution is to use an HMAC as follows.

encrypted = AES.encrypt(plaintext, password) // Example: "eNcRyPtEd"
hmac = HmacSHA256(encrypted, password)       // Example: "HMACabc"
return `${encrypted}.${hmac}`                // Example: "eNcRyPtEd.HMACabc"

Then the server gets that single string which is comprised of the encrypted text, plus the HMAC, delimited by a ..

On decryption:

# Get guess from user
password_guess = "user's input"

# Calc HMAC with the password_guess
[encrypted, hmac] = payload.split(".")
newHmac = HmacSHA256(encrypted, password_guess)

# Compare newHmac with the original HMAC
# decrypt or error based on HMAC equality
if newHmac == hmac
  AES.decrypt(encrypted, password_guess)
else
  print "wrong password"

This achieves my initial goal, to be able to determine if a password is correct or not, without having to store/send that password, or ever having it leave the client. However I'm not a cryptographer, this was cobbled together from semi-related SO answers and Wikipedia. And this is a project to learn so I'd like to know:

Upvotes: 0

Views: 1341

Answers (1)

Rob Napier
Rob Napier

Reputation: 299345

Short answer: this is probably reasonably secure, but sloppy, and hard to justify based on existing analysis. But also, probably fine.

For a fuller treatment of this approach based on sjcl, see RNCrytor-js. Your basic approach is fine, though cryptojs is a bit opaque about how it handles passwords (it does it reasonably well; it just doesn't document it very well, and some of its choices haven't been well analyzed by the crypto community, for example reusing the IV as the PBKDF2 salt).

Using the password directly as an HMAC key is also not well analyzed. It's probably reasonably fine, but it's sloppy. Generally you should never reuse a key for two purposes. (In this case, you're not, because the AES key gets stretched, while the HMAC key doesn't, but it's still sloppy).

If you want to build a format like this, you may want to look at the RNCryptor v4 spec. It's never been fully implemented (and there are several unincorporated comments from Maarten Bodewes who is better at this than I am), but it may be a useful starting point. It includes a lot of background that may be useful for building this kind of system. Crypto formats are very subtle, and difficult to build well.

Upvotes: 1

Related Questions