0
# Message Authentication
1
2
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.
3
4
## Capabilities
5
6
### HMAC (Hash-based Message Authentication Code)
7
8
Provides message authentication using cryptographic hash functions, combining a secret key with hash algorithms for secure authentication.
9
10
```javascript { .api }
11
/**
12
* HMAC constructor
13
* @param {BitArray} key - Secret key for HMAC (any length)
14
* @param {Function} [Hash] - Hash function to use (default: SHA-256)
15
* @throws {sjcl.exception.invalid} If key or hash function is invalid
16
*/
17
new sjcl.misc.hmac(key, Hash);
18
```
19
20
**Instance Methods:**
21
22
```javascript { .api }
23
/**
24
* Compute HMAC of data (alias for mac method)
25
* @param {BitArray|string} data - Data to authenticate
26
* @returns {BitArray} HMAC authentication tag
27
* @throws {sjcl.exception.invalid} If called after update without reset
28
*/
29
sjcl.misc.hmac.prototype.encrypt(data);
30
31
/**
32
* Compute HMAC of data
33
* @param {BitArray|string} data - Data to authenticate
34
* @returns {BitArray} HMAC authentication tag
35
* @throws {sjcl.exception.invalid} If called after update without reset
36
*/
37
sjcl.misc.hmac.prototype.mac(data);
38
39
/**
40
* Reset HMAC state for reuse
41
* @returns {sjcl.misc.hmac} This HMAC instance for chaining
42
*/
43
sjcl.misc.hmac.prototype.reset();
44
45
/**
46
* Add data to HMAC computation (streaming interface)
47
* @param {BitArray|string} data - Data to add to HMAC
48
* @returns {sjcl.misc.hmac} This HMAC instance for chaining
49
*/
50
sjcl.misc.hmac.prototype.update(data);
51
52
/**
53
* Complete HMAC computation and return result
54
* @returns {BitArray} Final HMAC authentication tag
55
*/
56
sjcl.misc.hmac.prototype.digest();
57
```
58
59
**Usage Examples:**
60
61
```javascript
62
const sjcl = require('sjcl');
63
64
// Basic HMAC with SHA-256 (default)
65
const key = sjcl.random.randomWords(8); // 256-bit key
66
const hmac = new sjcl.misc.hmac(key);
67
const data = "Hello, World!";
68
const authTag = hmac.encrypt(data);
69
70
console.log("HMAC:", sjcl.codec.hex.fromBits(authTag));
71
72
// Verify HMAC
73
const hmacVerify = new sjcl.misc.hmac(key);
74
const computedTag = hmacVerify.encrypt(data);
75
const isValid = sjcl.bitArray.equal(authTag, computedTag);
76
console.log("HMAC valid:", isValid);
77
78
// Using different hash functions
79
const hmacSHA1 = new sjcl.misc.hmac(key, sjcl.hash.sha1);
80
const hmacSHA512 = new sjcl.misc.hmac(key, sjcl.hash.sha512);
81
82
const tagSHA1 = hmacSHA1.encrypt(data);
83
const tagSHA512 = hmacSHA512.encrypt(data);
84
```
85
86
### Streaming HMAC
87
88
For large data or when data arrives in chunks, use the streaming interface:
89
90
```javascript
91
const sjcl = require('sjcl');
92
93
// Streaming HMAC computation
94
const key = sjcl.random.randomWords(8);
95
const hmac = new sjcl.misc.hmac(key);
96
97
// Add data in chunks
98
hmac.update("First chunk ");
99
hmac.update("Second chunk ");
100
hmac.update("Final chunk");
101
102
// Get final result
103
const streamingTag = hmac.digest();
104
105
// Compare with one-shot computation
106
const hmacOneShot = new sjcl.misc.hmac(key);
107
const oneShotTag = hmacOneShot.encrypt("First chunk Second chunk Final chunk");
108
109
console.log("Tags equal:", sjcl.bitArray.equal(streamingTag, oneShotTag));
110
111
// Reset for reuse
112
hmac.reset();
113
const newTag = hmac.encrypt("New data");
114
```
115
116
### HMAC Key Management
117
118
Proper key management is crucial for HMAC security:
119
120
```javascript
121
const sjcl = require('sjcl');
122
123
// Generate HMAC keys
124
function generateHMACKey(keySize = 256) {
125
// Key should be at least as long as hash output
126
const minSize = 256; // SHA-256 output size
127
const actualSize = Math.max(keySize, minSize);
128
return sjcl.random.randomWords(actualSize / 32);
129
}
130
131
// Derive HMAC key from password
132
function deriveHMACKey(password, salt, iterations = 100000) {
133
return sjcl.misc.pbkdf2(password, salt, iterations, 256);
134
}
135
136
// Multiple HMAC keys from master key
137
function deriveHMACKeys(masterKey, purposes) {
138
const keys = {};
139
purposes.forEach(purpose => {
140
const info = sjcl.codec.utf8String.toBits(purpose);
141
keys[purpose] = sjcl.misc.hkdf(masterKey, 256, null, info);
142
});
143
return keys;
144
}
145
146
// Usage
147
const masterKey = sjcl.random.randomWords(8);
148
const keys = deriveHMACKeys(masterKey, ['authentication', 'integrity', 'session']);
149
150
const authHMAC = new sjcl.misc.hmac(keys.authentication);
151
const integrityHMAC = new sjcl.misc.hmac(keys.integrity);
152
```
153
154
## Authentication Patterns
155
156
### Message Authentication
157
158
Simple message authentication with HMAC:
159
160
```javascript
161
const sjcl = require('sjcl');
162
163
class MessageAuthenticator {
164
constructor(key) {
165
this.key = key;
166
}
167
168
authenticate(message) {
169
const hmac = new sjcl.misc.hmac(this.key);
170
const tag = hmac.encrypt(message);
171
172
return {
173
message: message,
174
tag: sjcl.codec.hex.fromBits(tag)
175
};
176
}
177
178
verify(message, hexTag) {
179
const hmac = new sjcl.misc.hmac(this.key);
180
const computedTag = hmac.encrypt(message);
181
const providedTag = sjcl.codec.hex.toBits(hexTag);
182
183
return sjcl.bitArray.equal(computedTag, providedTag);
184
}
185
}
186
187
// Usage
188
const authKey = sjcl.random.randomWords(8);
189
const authenticator = new MessageAuthenticator(authKey);
190
191
const authenticated = authenticator.authenticate("Important message");
192
console.log(authenticated);
193
194
const isValid = authenticator.verify("Important message", authenticated.tag);
195
console.log("Message valid:", isValid);
196
```
197
198
### Encrypt-then-MAC
199
200
Secure pattern combining encryption with authentication:
201
202
```javascript
203
const sjcl = require('sjcl');
204
205
function encryptThenMAC(plaintext, encKey, macKey) {
206
// Encrypt first
207
const iv = sjcl.random.randomWords(4);
208
const aes = new sjcl.cipher.aes(encKey);
209
const ciphertext = sjcl.mode.gcm.encrypt(aes, plaintext, iv);
210
211
// Then authenticate the ciphertext + IV
212
const hmac = new sjcl.misc.hmac(macKey);
213
const dataToAuth = sjcl.bitArray.concat(iv, ciphertext);
214
const authTag = hmac.encrypt(dataToAuth);
215
216
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 analysis