Stanford JavaScript Crypto Library providing comprehensive cryptographic operations including AES encryption, hash functions, key derivation, and elliptic curve cryptography.
—
SJCL provides HMAC (Hash-based Message Authentication Code) for message authentication and integrity verification, ensuring that data has not been tampered with and comes from an authenticated source.
Provides message authentication using cryptographic hash functions, combining a secret key with hash algorithms for secure authentication.
/**
* HMAC constructor
* @param {BitArray} key - Secret key for HMAC (any length)
* @param {Function} [Hash] - Hash function to use (default: SHA-256)
* @throws {sjcl.exception.invalid} If key or hash function is invalid
*/
new sjcl.misc.hmac(key, Hash);Instance Methods:
/**
* Compute HMAC of data (alias for mac method)
* @param {BitArray|string} data - Data to authenticate
* @returns {BitArray} HMAC authentication tag
* @throws {sjcl.exception.invalid} If called after update without reset
*/
sjcl.misc.hmac.prototype.encrypt(data);
/**
* Compute HMAC of data
* @param {BitArray|string} data - Data to authenticate
* @returns {BitArray} HMAC authentication tag
* @throws {sjcl.exception.invalid} If called after update without reset
*/
sjcl.misc.hmac.prototype.mac(data);
/**
* Reset HMAC state for reuse
* @returns {sjcl.misc.hmac} This HMAC instance for chaining
*/
sjcl.misc.hmac.prototype.reset();
/**
* Add data to HMAC computation (streaming interface)
* @param {BitArray|string} data - Data to add to HMAC
* @returns {sjcl.misc.hmac} This HMAC instance for chaining
*/
sjcl.misc.hmac.prototype.update(data);
/**
* Complete HMAC computation and return result
* @returns {BitArray} Final HMAC authentication tag
*/
sjcl.misc.hmac.prototype.digest();Usage Examples:
const sjcl = require('sjcl');
// Basic HMAC with SHA-256 (default)
const key = sjcl.random.randomWords(8); // 256-bit key
const hmac = new sjcl.misc.hmac(key);
const data = "Hello, World!";
const authTag = hmac.encrypt(data);
console.log("HMAC:", sjcl.codec.hex.fromBits(authTag));
// Verify HMAC
const hmacVerify = new sjcl.misc.hmac(key);
const computedTag = hmacVerify.encrypt(data);
const isValid = sjcl.bitArray.equal(authTag, computedTag);
console.log("HMAC valid:", isValid);
// Using different hash functions
const hmacSHA1 = new sjcl.misc.hmac(key, sjcl.hash.sha1);
const hmacSHA512 = new sjcl.misc.hmac(key, sjcl.hash.sha512);
const tagSHA1 = hmacSHA1.encrypt(data);
const tagSHA512 = hmacSHA512.encrypt(data);For large data or when data arrives in chunks, use the streaming interface:
const sjcl = require('sjcl');
// Streaming HMAC computation
const key = sjcl.random.randomWords(8);
const hmac = new sjcl.misc.hmac(key);
// Add data in chunks
hmac.update("First chunk ");
hmac.update("Second chunk ");
hmac.update("Final chunk");
// Get final result
const streamingTag = hmac.digest();
// Compare with one-shot computation
const hmacOneShot = new sjcl.misc.hmac(key);
const oneShotTag = hmacOneShot.encrypt("First chunk Second chunk Final chunk");
console.log("Tags equal:", sjcl.bitArray.equal(streamingTag, oneShotTag));
// Reset for reuse
hmac.reset();
const newTag = hmac.encrypt("New data");Proper key management is crucial for HMAC security:
const sjcl = require('sjcl');
// Generate HMAC keys
function generateHMACKey(keySize = 256) {
// Key should be at least as long as hash output
const minSize = 256; // SHA-256 output size
const actualSize = Math.max(keySize, minSize);
return sjcl.random.randomWords(actualSize / 32);
}
// Derive HMAC key from password
function deriveHMACKey(password, salt, iterations = 100000) {
return sjcl.misc.pbkdf2(password, salt, iterations, 256);
}
// Multiple HMAC keys from master key
function deriveHMACKeys(masterKey, purposes) {
const keys = {};
purposes.forEach(purpose => {
const info = sjcl.codec.utf8String.toBits(purpose);
keys[purpose] = sjcl.misc.hkdf(masterKey, 256, null, info);
});
return keys;
}
// Usage
const masterKey = sjcl.random.randomWords(8);
const keys = deriveHMACKeys(masterKey, ['authentication', 'integrity', 'session']);
const authHMAC = new sjcl.misc.hmac(keys.authentication);
const integrityHMAC = new sjcl.misc.hmac(keys.integrity);Simple message authentication with HMAC:
const sjcl = require('sjcl');
class MessageAuthenticator {
constructor(key) {
this.key = key;
}
authenticate(message) {
const hmac = new sjcl.misc.hmac(this.key);
const tag = hmac.encrypt(message);
return {
message: message,
tag: sjcl.codec.hex.fromBits(tag)
};
}
verify(message, hexTag) {
const hmac = new sjcl.misc.hmac(this.key);
const computedTag = hmac.encrypt(message);
const providedTag = sjcl.codec.hex.toBits(hexTag);
return sjcl.bitArray.equal(computedTag, providedTag);
}
}
// Usage
const authKey = sjcl.random.randomWords(8);
const authenticator = new MessageAuthenticator(authKey);
const authenticated = authenticator.authenticate("Important message");
console.log(authenticated);
const isValid = authenticator.verify("Important message", authenticated.tag);
console.log("Message valid:", isValid);Secure pattern combining encryption with authentication:
const sjcl = require('sjcl');
function encryptThenMAC(plaintext, encKey, macKey) {
// Encrypt first
const iv = sjcl.random.randomWords(4);
const aes = new sjcl.cipher.aes(encKey);
const ciphertext = sjcl.mode.gcm.encrypt(aes, plaintext, iv);
// Then authenticate the ciphertext + IV
const hmac = new sjcl.misc.hmac(macKey);
const dataToAuth = sjcl.bitArray.concat(iv, ciphertext);
const authTag = hmac.encrypt(dataToAuth);
return {\n iv: iv,\n ciphertext: ciphertext,\n authTag: authTag\n };\n}\n\nfunction verifyThenDecrypt(encrypted, encKey, macKey) {\n // Verify authentication first\n const hmac = new sjcl.misc.hmac(macKey);\n const dataToAuth = sjcl.bitArray.concat(encrypted.iv, encrypted.ciphertext);\n const computedTag = hmac.encrypt(dataToAuth);\n \n if (!sjcl.bitArray.equal(computedTag, encrypted.authTag)) {\n throw new sjcl.exception.corrupt(\"Authentication failed\");\n }\n \n // Then decrypt\n const aes = new sjcl.cipher.aes(encKey);\n return sjcl.mode.gcm.decrypt(aes, encrypted.ciphertext, encrypted.iv);\n}\n\n// Usage\nconst encKey = sjcl.random.randomWords(8);\nconst macKey = sjcl.random.randomWords(8);\nconst plaintext = sjcl.codec.utf8String.toBits(\"Secret message\");\n\nconst encrypted = encryptThenMAC(plaintext, encKey, macKey);\nconst decrypted = verifyThenDecrypt(encrypted, encKey, macKey);\n\nconsole.log(sjcl.codec.utf8String.fromBits(decrypted));\n```\n\n### Time-safe HMAC Verification\n\nPrevent timing attacks when verifying HMAC tags:\n\n```javascript\nconst sjcl = require('sjcl');\n\nfunction timeSafeHMACVerify(key, data, providedTag) {\n const hmac = new sjcl.misc.hmac(key);\n const computedTag = hmac.encrypt(data);\n \n // Use bitArray.equal for constant-time comparison\n return sjcl.bitArray.equal(computedTag, providedTag);\n}\n\n// Usage\nconst key = sjcl.random.randomWords(8);\nconst data = \"sensitive data\";\nconst hmac = new sjcl.misc.hmac(key);\nconst tag = hmac.encrypt(data);\n\n// This is time-safe\nconst isValid1 = timeSafeHMACVerify(key, data, tag);\n\n// This is NOT time-safe (don't do this)\nconst hmac2 = new sjcl.misc.hmac(key);\nconst tag2 = hmac2.encrypt(data);\nconst hexTag1 = sjcl.codec.hex.fromBits(tag);\nconst hexTag2 = sjcl.codec.hex.fromBits(tag2);\nconst isValid2 = hexTag1 === hexTag2; // Vulnerable to timing attacks\n```\n\n## API Authentication\n\n### Request Signing\n\nSign API requests with HMAC for authentication:\n\n```javascript\nconst sjcl = require('sjcl');\n\nclass APIRequestSigner {\n constructor(apiKey, secretKey) {\n this.apiKey = apiKey;\n this.secretKey = sjcl.codec.utf8String.toBits(secretKey);\n }\n \n signRequest(method, url, body, timestamp) {\n // Create string to sign\n const stringToSign = [method, url, body || '', timestamp].join('\\n');\n \n // Generate HMAC signature\n const hmac = new sjcl.misc.hmac(this.secretKey);\n const signature = hmac.encrypt(stringToSign);\n \n return {\n apiKey: this.apiKey,\n timestamp: timestamp,\n signature: sjcl.codec.base64.fromBits(signature)\n };\n }\n \n verifyRequest(method, url, body, timestamp, signature) {\n const expected = this.signRequest(method, url, body, timestamp);\n const providedSig = sjcl.codec.base64.toBits(signature);\n const expectedSig = sjcl.codec.base64.toBits(expected.signature);\n \n return sjcl.bitArray.equal(providedSig, expectedSig);\n }\n}\n\n// Usage\nconst signer = new APIRequestSigner('api123', 'secret456');\nconst timestamp = Date.now().toString();\n\nconst signature = signer.signRequest('POST', '/api/data', '{\"test\":true}', timestamp);\nconsole.log('Authorization:', `HMAC ${signature.apiKey}:${signature.signature}`);\n\nconst isValid = signer.verifyRequest(\n 'POST', \n '/api/data', \n '{\"test\":true}', \n timestamp, \n signature.signature\n);\nconsole.log('Request valid:', isValid);\n```\n\n## Security Recommendations\n\n1. **Key Length**: Use keys at least as long as the hash output (256 bits for SHA-256)\n2. **Key Generation**: Generate keys using cryptographically secure random number generators\n3. **Key Derivation**: Use PBKDF2/scrypt/HKDF for deriving keys from passwords\n4. **Constant-time Verification**: Always use `sjcl.bitArray.equal()` for tag comparison\n5. **Hash Function**: Use SHA-256 or SHA-512, avoid SHA-1\n6. **Tag Length**: Use full-length tags, don't truncate unless absolutely necessary\n7. **Separate Keys**: Use different keys for different purposes (encryption vs authentication)\n\n## Common Pitfalls\n\n1. **Timing Attacks**: Never use string comparison for HMAC verification\n2. **Key Reuse**: Don't use encryption keys for HMAC\n3. **Weak Keys**: Don't use predictable or short keys\n4. **MAC-then-Encrypt**: Always use Encrypt-then-MAC pattern\n5. **Tag Truncation**: Avoid truncating HMAC tags without security analysisInstall with Tessl CLI
npx tessl i tessl/npm-sjcl