Skip to content

Instantly share code, notes, and snippets.

@mraleson
Last active March 14, 2024 16:24
Show Gist options
  • Save mraleson/9b925cb63f6364de219b0a4993e27879 to your computer and use it in GitHub Desktop.
Save mraleson/9b925cb63f6364de219b0a4993e27879 to your computer and use it in GitHub Desktop.
Untested Javascript Fernet Decrypt
const crypto = require('crypto');
function base64UrlDecode(value) {
const translated = value.replace(/-/g, '+').replace(/_/g, '/');
const padding = translated.length % 4;
const padded = translated + '='.repeat(padding);
return Buffer.from(padded, 'base64');
}
function decrypt(secret, token, ttl) {
// parse secret key into parts
// secret = signingKey(16 bytes) + encryptionKey(16 bytes)
const decoded = base64UrlDecode(secret);
if (decoded.length !== 32) {
throw new Error('Invalid secret key: incorrect length');
}
const signingKey = decoded.slice(0, 16);
const encryptionKey = decoded.slice(16);
// parse token (encrypted data) into parts
// token = version(1 byte) + timestamp(8 bytes) + iv(16 bytes) + data(X bytes) + hmac(32 bytes)
const data = base64UrlDecode(token);
const version = data.slice(0, 1).toString('hex');
if (version !== '80') {
throw new Error('Invalid token: unexpected first byte / fernet version');
}
const timestamp = data.readBigUInt64BE(1); // next 8 bytes should be a timestamp 64 uint bigendian
const iv = data.slice(9, 25);
const ciphertext = data.slice(25, data.length - 32);
const hmac = data.slice(data.length - 32);
// verify ttl (expiration) if ttl is specified
if (ttl) {
const maxClockSkew = 60n; // borrowed this constant from python implementation
const currentTime = BigInt(Math.floor(new Date() / 1000));
const expiration = timestamp + BigInt(ttl);
if (expiration < currentTime) {
throw new Error(`Invalid token: setting ttl parameter to ${ttl} makes token expired`);
}
if (currentTime + maxClockSkew < timestamp) {
throw new Error('Invalid token: message timestamp is in the future, clocks must be off');
}
}
// verify hmac signature (hash makes sure no one tampered with message)
const hasher = crypto.createHmac('sha256', signingKey);
hasher.update(data.slice(0, data.length - 32));
if (hmac.toString('hex') !== hasher.digest().toString('hex')) {
throw new Error('Invalid HMAC');
}
// decrypt encrypted data
const decipher = crypto.createDecipheriv('aes-128-cbc', encryptionKey, iv);
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
return decrypted;
}
exports.decrypt = decrypt;
@mraleson
Copy link
Author

License: MIT
Warning! This code is untested.
This is written to decrypt data encrypted by python cryptography's Fernet encryption. https://cryptography.io/en/latest/fernet/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment