Passing Encrypted Tokens: The Fernet Way

Why don’t we pass everything in an encrypted form? Well, we can have problems with the format of our encryption, especially if we use salt…

Photo by Markus Spiske on Unsplash

Passing Encrypted Tokens: The Fernet Way

Why don’t we pass everything in an encrypted form? Well, we can have problems with the format of our encryption, especially if we use: salted ciphers; hash signatures (HMAC); and different formats for our key. And for our Web applications, how do we keep compatibility with HTTP, and pass values in a URL-safe way?

Well, Fernet tokens come to our rescue, and are defined as a way which integrates the best practice in encryption and integrity checking. The format of the token is:

For this we have 128-bit AES in a CBC (Cipher Block Chaining) mode and with an HMAC signature to prove the integrity of the message. The format of the token has a version number of 8 bits, a 64-bit timestamp, a 128-bit Initialisation Vector (IV), and a 256-bit HMAC signature. The cipher is then created in multiples of 128 bits, as AES uses a 128-bit block size. In this way we can check the date that the data was encrypted, and where the cipher text will change each time based on the IV. The token can then be send, and used with an encryption key. We also standardise the padding method with PCKS#7 [here].

Here is an example using a SHA-256 hash of a password [here]:

67 4141414141426346 3745716c4c45323343566871445a6447 48623743347a6477395f5034643730634c796a434e485a42534d396b79724e526d4743325a573030433862 57355364776348447731673178636c4d704c7953674764416c626b6a53773d3d

This is:

67 - Version (8 bits)
4141414141426346 - Date (64 bits)
3745716c4c45323343566871445a6447 - Salt (128 bits)
48623743347a6477395f5034643730634c796a434e485a42534d396b79724e526d4743325a573030433862 - Ciphered message
57355364776348447731673178636c4d704c7953674764416c626b6a53773d3d - HMAC

Fernet is used to define best practice cryptography methods, and Hazmat supports core cryptographical primitives:

A sample run for “hello” is:

Key: 4c504a4e756c2d776f77346d3644737178626e696e687357486c776670304a656377517a59704f4c6d43513d
Cipher: 674141414141426346375567792d36663450495159425a797632525a54643953544c48723278372d657965472d7a68597a50556535584c494f416b614c79674f6a44715841556d54464b674675617a6e4e475f5f4e76416874496652375a7a5468673d3d
Version:	67
Time stamp: 4141414141426346
IV: 375567792d36663450495159425a7976
Cipher: 32525a54643953544c48723278372d657965472d7a68597a50556535584c494f416b614c79674f6a447158
HMAC: 41556d54464b674675617a6e4e475f5f4e76416874496652375a7a5468673d3d
Plain text: hello

A sample run for “hello world” is:

Key: 4c504a4e756c2d776f77346d3644737178626e696e687357486c776670304a656377517a59704f4c6d43513d
Cipher: 674141414141426346375673794b7150413743372d332d6179344e7838504f4c49625359512d4b556546554751386e595841706d30785678696f76756c45323430427033766d575f5062626d524c366c505a314334355863596a4c5731786f3935513d3d
Version:	67
Time stamp: 4141414141426346
IV: 375673794b7150413743372d332d6179
Cipher: 344e7838504f4c49625359512d4b556546554751386e595841706d30785678696f76756c45323430427033
HMAC: 766d575f5062626d524c366c505a314334355863596a4c5731786f3935513d3d
Plain text: hello world

The reason the cipher text is longer as we have a block size of 128 bits (16 ASCII characters). We can improve on the hashing method by using PBKDF2 [here].

Creating a safe token

In order to pass the token we can convert this into a token with a Base-64 format [here]:

gAAAAABWC9P7–9RsxTz_dwxh9-O2VUB7Ih8UCQL1_Zk4suxnkCvb26Ie4i8HSUJ4caHZuiNtjLl3qfmCv_fS3_VpjL7HxCz7_Q==

A sample run gives [here]:

Token:  gAAAAABWC9P7-9RsxTz_dwxh9-O2VUB7Ih8UCQL1_Zk4suxnkCvb26Ie4i8HSUJ4caHZuiNt
jLl3qfmCv_fS3_VpjL7HxCz7_Q==
Current time: Wed Sep 30 13:30:32 2015
Token Details
=============
Decoded data: 8000000000560bd3fbfbd46cc53cff770c61f7e3b655407b221f140902f5fd993
8b2ec67902bdbdba21ee22f0749427871a1d9ba236d8cb977a9f982bff7d2dff5698cbec7c42cfbf
d
======Analysis====
Version: 80
Date created: 00000000560bd3fb
IV: fbd46cc53cff770c61f7e3b655407b22
Cipher: 1f140902f5fd9938b2ec67902bdbdba2
HMAC: 1ee22f0749427871a1d9ba236d8cb977a9f982bff7d2dff5698cbec7c42cfbfd
======Converted====
Time stamp: 1443615739
Date created: Wed Sep 30 13:22:19 2015
IV: fbd46cc53cff770c61f7e3b655407b22
Decoded: password

Decrypting

When used with a key we can decode [here]. Here are some sample Fernet tokens. Can you determine the messages and when they were created?

  • Token=”gAAAAABWC9itaE9nFdqmI48kW6eTxUgTN4QRMHztr9buhiJVlRxxlDcdSdWuD7zb9apkYdeJ1LNh_DBM4WgiZW1WlejIJdUzWQ==”, key=”JGMZNCmpDjpN2Jz10wMcF9kXc1vM8QC1nuxHB2gjIgY=”. Try.
  • Token=”gAAAAABWC9mx6EZuXA4_903Vw01LpmxxeQsfFmiXUC3arejQ4NTExSYER2NGjXnoaN051qIpbZaHqzMvz9BJT8FmwjWCoHW3Iy-KFLnEVZaznVfa5Yvplzg=”, key=”ZocYZaBsHlv7qm_uRh7BOa_KBYvsQRsVv-e7oarTGUA=”. Try.
  • Token=”gAAAAABWC9sLMfNlyVGFBXjoDkju9X0MQolE9IRHfDF1-UL2x8ZQQMSEkXRfAFNzdnsF0tupW7rxpu9Jw1MqEpQahDwV6m24azVdn0Mctu_v4-VXFqi9cGQy_DmfZK_CuKxoGkaUElw_”, key=”d3tqBFPIAjF9Sa12E0GjWefSfcIAEXxi6Y64EY7fmlY=”. Try.
  • Token=”gAAAAABWDDX4bYvLWNMcYDJjT_D5EzQps7i4FW6RlbF50s7b9BqFpBuvlhxdI3UeO8mbje-NfxSir-invL1F_CKipGCH-jXH4VlB6k0AOqjdsfmuLoCxjlJkvJcs_J1YXMzZDY4nQG35oxm8bNzHcr8ZPMpZIvWXUs3lKe2h8BymZiWBZOoxkFxETvEJ2-bbm8c9ODa6XkTZ6E70621BXTKSTPM5OXuBmSL3h2sCbv6WXreux6yUyD0=”, Key=tkk5Ot6O34drSnyPE1HHBd6C7G1TjLW9ypBz3g854HE=” Try.

Autokeying — creating a lock-out

The key generated is a URL-safe base64-encoded key with 32 bytes. When the message is encrypted it contains the time it was generated in plaintext. If we use:

decrypt(token,TTL)

then an exception is raised is the token was created more that TTL seconds ago. A example of autokeying is here.

Key rotation

Many systems use a key rotation method, and where Bob and Alice agree on a range of keys to use, and then on a regular basis change the keys.

This approach can be setup with the following additional code [here]:

(key1,salt) = get_key(password)
(key2,salt) = get_key(password)
(key3,salt) = get_key(password)


print "Password:\t"+password
print "Key1: "+binascii.hexlify(bytearray(key1))
print "Key2: "+binascii.hexlify(bytearray(key2))
print "Key3: "+binascii.hexlify(bytearray(key3))

cipher_suite=MultiFernet([key1, key2,key3])

cipher_suite = Fernet(key2)

cipher_text = cipher_suite.encrypt(val)
cipher=binascii.hexlify(bytearray(cipher_text))
print "\nCipher: "+cipher

plain_text = cipher_suite.decrypt(cipher_text)
print "\nPlain text: "+plain_text

Conclusions

So there you go … if you’re struggling to choose your crypto in your apps … then Fernet might be a good selection. For some reasons, our industry still struggles to use encryption properly. With Fernet, we have, at least, a standardise token library which is fairly easy to integrate into many applications. The autokeying function, too, limits the usage of brute force attempts.