Last active
March 14, 2024 16:24
-
-
Save mraleson/9b925cb63f6364de219b0a4993e27879 to your computer and use it in GitHub Desktop.
Untested Javascript Fernet Decrypt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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/