Decoding the Evernote en-crypt field payload

2014-08-21, James Robson, http://soundly.me

Evernote uses the en-crypt field (from the ENML specification) to encrypt portions of its notes. The API does not provide a method to read and write this field, however.

The most you get is this description

Evernote derives your AES key from the passphrase you enter and does this using a well recognized method called PBKDF2 (Password Based Key Derivation Function 2). Your passphrase, along with a unique salt, runs through a HMAC/SHA-256 hashing function 50,000 times. The result is a 128 bit AES key. This key, along with an initialization vector, is used to encrypt your data in CBC (Cipher Block Chaining) mode.

found at this page here:

https://evernote.com/contact/support/kb/#/article/23480996

It would be nice though, if you could create an en-crypt field from the API. Meaning your app could create encrypted fields and users could decrypt them in the Evernote app.

Well, it turns out that the description above more or less clues us in for what to look for, and poking around in the app and its notes gives us the rest.

First, I had to create an encrypted field, which you can only do in the desktop, it seems. Then I exported it as an .enex file, which is really just XML:

fig1

Opening the .enex file in a capable text editor you will find the en-crypt field, and it's pretty straightforward, really:

<en-crypt cipher="AES" length="128">RU5DMA4hFx2T/Uy8tI7R7aGAe8r/ncNiNxGykP7VbZQMMP6fcPzyQqGegjVvYQg4MtKBr57qb6vBLEpWHIKVY9XLQzeOXkqpy/kJtLRKTP21BsZrIeSX67XuM40z02M4XAZsIF7Ixxsg8m81SWYQ1oliEnac+FiWzI5dUwKpSdFVc7WB40/f2cK5Hc6C8hN3ILkgX6VXpnPjtRcjcm6aT7sCXAXgslU6Qwrv43X1bmqMlZT0pxpKRFpKrnkPV89TvGfvGUyQGfc50B3VzqzJvF2R4pEe3mGzKramIGKei1w1aFiT2eufwDgfJghkDWgIIW7QbZUiywoMwITrwpYJh1NCbObRGMqBLAB6kZFRVKbS5JWesKwgRmnHsdHQTpX4Sd/dYI3GC4FpdeK9C+E3YcBSVcCNBGt7N2+ce3o7q/K3Ynoq+8w0qHtNi1XGV2hD+LU6IK13FoRJwyT1hhkAMiFc3mgvT83hrynhVhByhkKGPixL3s1GFy5s51NmLSTur/b8pnicF+TgG4YXz8Jxvnalmf2SMd5voxuz9Ny5EK86P54MsuzjK0y6AO/te7pkxht6UP+SYItJI2s1r5fl07Sa/xlkVH28xcL3ODpr0DTWEh/Qq2pXRNLfpcISdBJIMaIPlcJGR8567WJK0A07yhsS9c1PnVUb+cLlI9PIG3Qxtkfp</en-crypt>

It's clear from the above that all the fields mentioned in the description (iv, salt, etc) are embedded in that base64 string. The question is, where and how?

I first converted it to hex in the Terminal:

% echo RU5DMA4hFx2T/Uy8tI7R7aGAe8r/ncNiNxGykP7VbZQMMP6fcPzyQqGegjVvYQg4MtKBr57qb6vBLEpWHIKVY9XLQzeOXkqpy/kJtLRKTP21BsZrIeSX67XuM40z02M4XAZsIF7Ixxsg8m81SWYQ1oliEnac+FiWzI5dUwKpSdFVc7WB40/f2cK5HpnPjtRcjcm6aT7sCXAXgslU6Qwrv43X1bmqMlZT0pxpKRFpKrnkPV89TvGfvGUyQGfc50B3VzqzJvF2R4pEe3mGzKramIGKei1w1aFiT2eufwDgfJghkDWgIIW7QbZUiywoMwITrwpYJh1NCbObRGMqBLAB6kZFRVKbS5JWesKwgRmnHsdHQTpX4Sd/dYI3GC4FpdeK9C+E3YcBSVcCNBGt7N2+ce3o7q/K3Ynoq+8w0qHtNi1XGV2hD+LU6IK13FoRJwyT1hhkAMiFc3mgvT83hrynhVhByhkKGPixL3s1GFy5s51NmLSTur/b8pnicF+TgG4YXz8Jxvnalmf2SMd5voxuz9Ny5EK86P54MsuzjK0y6AO/te7pkxht6UP+SYItJI2s1r5fl07Sa/xlkVH28xcL3ODpr0DTWEh/Qq2pXRNLfpcISdBJIMaIPlcJGR8567WJK0A07yhsS9c1PnVUb+cLlI9PIG3Qxtkfp | base64 -D | hexdump

After a bit of trial and error, javascript debugging etc, I finally determined the layout of the payload. I'll describe how I deciphered it below for those who are interested, but for the impatient, here's what it looks like:

fig2

Given this layout you can create an en-crypt field which can be added to a note via the API, as long as you get the crypto functions right. Here's how to create the field in Python. This field, once created, can then be decrypted normally in the browser on the Evernote site:

import hashlib
import binascii
import os
from pbkdf2 import pbkdf2 # locally, pbkdf2.py
from aeshelper import AESCipher128CBC # locally, aeshelper.py
import base64
import hmac

payload = bytes()
payload += b'ENC0'

password = 'password'
iters = 50000

# Three  random values
salt = os.urandom(16)
salthmac = os.urandom(16)
iv = os.urandom(16)

payload += salt
payload += salthmac
payload += iv # we'll append the ciphertext last

print 'salt: ' + binascii.hexlify(salt)
print 'salthmac: ' + binascii.hexlify(salthmac)
print 'iv: ' + binascii.hexlify(iv)

# Derive key
key = pbkdf2( 'password', salt, 50000, 16, hashlib.sha256 )
print 'Derived key: ' + binascii.hexlify(key)

cleartext = '<h2>helowrld</h2>'

ac = AESCipher128CBC(key)

# Cipher hands us base64 by default
ciphertext = base64.b64decode(ac.encrypt(cleartext, iv))

payload += ciphertext

# Derive key for HMAC validation
keyhmac = pbkdf2( 'password', salthmac, 50000, 16, hashlib.sha256 )

hd = hmac.new(keyhmac, None, hashlib.sha256)
hd.update(payload)
digest = hd.digest()

payload += digest

print 'keyhmac: ' + binascii.hexlify(keyhmac)
print 'digest: ' + binascii.hexlify(digest)

# Money:
ncfield = '<en-crypt cipher="AES" length="128">' + base64.b64encode(payload) + '</en-crypt>'
print ncfield

# Now you can add the field to your note via the Evernote API/SDK, as you would normally...

Note: you'll need a couple of Python sources for the above to work: pbkdf2.py | aeshelper.py

If you add the above field to a note, you should then be able to select and decrypt the above message from your browser, on the Evernote site.

This does not do decryption of an existing en-crypt field in a note, but I think it should be pretty obvious how you would be able to do that with the calls above.

Figuring it out

Or, a lot of trial and error, a lot of Javascript debugging in Chrome on the Evernote site, and a lot of signed integer to hex conversions...

Having the hex encoding of the payload (minus the nice annotations above), I had to figure out what parts of it were what. I sort of figured the salt(s) and IV would be prepended to the payload, but you still have to know which one comes first, etc.

Having created the note above, I loaded up the evernote.com site in Chrome with Developer Tools running. I ran through the decryption, and noticed a javascript file that was getting loaded whenever I did those steps: aes-crypto.js.

Turns out that Evernote is using the SJCL javascript crypto library. While the Evernote code is pretty heavily obfuscated/minified, the SJCL library they're loading is not, which made this task much easier.

Remember Evernote said in their description how many iterations, what mode of encryption, etc they used? Well so it was just a matter of hunting through that source for the obvious candidates, like aes and hmac and pbkdf2 and setting some breakpoints.

When the breakpoints hit, I had some valuable data to compare to my hex output.

The salt:

fig3

The IV:

fig4

You can see that these values are represented in Javascript as arrays of signed integers. So I had to convert them into their 4-byte hexidecimal values, and compare the hex output above to find their location.

And since we found the code where these values are used, we also get to reason about what is being checked. For instance, I don't recall Evernote saying in their description above that they also created a message digest of the salt(s), IV, and ciphertext and appended that to the end of the ciphertext in the payload. Like I said, trial and error, and finally the picture became clear.

That's pretty much it. This isn't a hack or compromise of Evernote's security, not in the least. From what I can tell they're doing the encryption right, and having other parties knowing how it works is actually a part of good crypto.

This is really just a way to access a portion of their service that, in my opinion, should be available in the API, because it provides opportunities to integrate with their workflow while securing cloud data. In fact, I would like to see a whole set of functionality around crypto in their API.