Search code examples
pythonnode.jsencryptioncryptographyfernet

Encryption with NodeJS doesn't match encryption using Python (cryptography.fernet)


Cryptography noob here. I'm trying to write a script in NodeJS that encrypts a string and produces output that matches the output of my Python script that uses the cryptography.fernet library. My overall goal is to use the original key to encrypt messages in Node that will later be decrypted using Python.

Sample of my Python code:

from cryptography.fernet import Fernet

key = Fernet.generate_key() # For example: 6saGtiTFEXej729GUWSeAyQdIpRFdGhfY2XFUDpvsu8=
f = Fernet(key)
message = 'Hello World'
encoded = message.encode()
encrypted = f.encrypt(encoded)

Which produces the output: gAAAAABhJs_E-dDVp_UrLK6PWLpukDAM0OT5M6bfcqvVoCvg7r63NSi4OWOamLpABuYQG-5wsts_9h7cLbCsWmctArXcGqelXz_BXl_o2C7KM9o7_eq7VTc=

My Node script uses the built-in Crypto module and must also use the same 32-byte key that is being used in my Python program. I know that fernet uses is AES-128-CBC as its algorithm, so that's what I'm using for my Node script.

My NodeJS code:

const crypto = require("crypto");

const key = '6saGtiTFEXej729GUWSeAyQdIpRFdGhfY2XFUDpvsu8=';
const algorithm = 'aes-128-cbc';
const message = 'Hello World';
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = cipher.update(message, 'utf8', 'hex') + cipher.final('hex');

Which is giving me: Error: Invalid key length

My first problem is that I'm unsure how to convert the key so that it's the proper length. I also know from looking at fernet's source code that the key is split into two parts: the first 16 bytes are the signing_key and the last 16 bytes are the encryption_key - I haven't found much information on whether/how I need to deal with those two pieces of the original key in my Node implementation.

Since I'm new to this I'm a little confused on how to accomplish what I'm after. Any tips or advice is very much appreciated.


Solution

  • The specs for the Fernet format can be found on https://github.com/fernet/spec/blob/master/Spec.md

    There they specify both a generating and a veryfying steps, here is the generating which should give enough information for your implementation:

    • Record the current time for the timestamp field.
    • Choose a unique IV.
    • Construct the ciphertext:
      • Pad the message to a multiple of 16 bytes (128 bits) per RFC 5652, section 6.3. This is the same padding technique used in PKCS #7 v1.5 and all versions of SSL/TLS (cf. RFC 5246, section 6.2.3.2 for TLS 1.2).
      • Encrypt the padded message using AES 128 in CBC mode with the chosen IV and user-supplied encryption-key.
    • Compute the HMAC field as described above using the user-supplied signing-key.
    • Concatenate all fields together in the format above.
    • base64url encode the entire token.

    From this we can see that the signing key (first half of full key) is used in HMAC, while the second half is used in the AES128-CBC, so just dividing the key into two separate elements (with proper conversion from hex string to bytes) should be enough for using Node.js crypto module (https://nodejs.org/en/knowledge/cryptography/how-to-use-crypto-module/) to construct your own implementation.