decrypt = function (password, options, data, callback) { exports.generateKey(password, options, (err, key) => { if (err) { return callback(err); } const decipher = Crypto.createDecipheriv(options.algorithm, key.key, key.iv); let dec = decipher.update(data, null, 'utf8'); dec = dec + decipher.final('utf8'); callback(null, dec); }); }
n/a
encrypt = function (password, options, data, callback) { exports.generateKey(password, options, (err, key) => { if (err) { return callback(err); } const cipher = Crypto.createCipheriv(options.algorithm, key.key, key.iv); const enc = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]); callback(null, enc, key); }); }
n/a
generateKey = function (password, options, callback) { const callbackTick = Hoek.nextTick(callback); if (!password) { return callbackTick(Boom.internal('Empty password')); } if (!options || typeof options !== 'object') { return callbackTick(Boom.internal('Bad options')); } const algorithm = exports.algorithms[options.algorithm]; if (!algorithm) { return callbackTick(Boom.internal('Unknown algorithm: ' + options.algorithm)); } const generate = () => { if (Buffer.isBuffer(password)) { if (password.length < algorithm.keyBits / 8) { return callbackTick(Boom.internal('Key buffer (password) too small')); } const result = { key: password, salt: '' }; return generateIv(result); } if (password.length < options.minPasswordlength) { return callbackTick(Boom.internal('Password string too short (min ' + options.minPasswordlength + ' characters required )')); } if (options.salt) { return generateKey(options.salt); } if (options.saltBits) { return generateSalt(); } return callbackTick(Boom.internal('Missing salt or saltBits options')); }; const generateSalt = () => { const randomSalt = Cryptiles.randomBits(options.saltBits); if (randomSalt instanceof Error) { return callbackTick(Boom.wrap(randomSalt)); } const salt = randomSalt.toString('hex'); return generateKey(salt); }; const generateKey = (salt) => { Crypto.pbkdf2(password, salt, options.iterations, algorithm.keyBits / 8, 'sha1', (err, derivedKey) => { if (err) { return callback(Boom.wrap(err)); } const result = { key: derivedKey, salt }; return generateIv(result); }); }; const generateIv = (result) => { if (algorithm.ivBits && !options.iv) { const randomIv = Cryptiles.randomBits(algorithm.ivBits); if (randomIv instanceof Error) { return callbackTick(Boom.wrap(randomIv)); } result.iv = randomIv; return callbackTick(null, result); } if (options.iv) { result.iv = options.iv; } return callbackTick(null, result); }; generate(); }
n/a
hmacWithPassword = function (password, options, data, callback) { exports.generateKey(password, options, (err, key) => { if (err) { return callback(err); } const hmac = Crypto.createHmac(options.algorithm, key.key).update(data); const digest = hmac.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); const result = { digest, salt: key.salt }; return callback(null, result); }); }
n/a
seal = function (object, password, options, callback) { const now = Date.now() + (options.localtimeOffsetMsec || 0); // Measure now before any other processing const callbackTick = Hoek.nextTick(callback); // Serialize object const objectString = internals.stringify(object); if (objectString instanceof Error) { return callbackTick(objectString); } // Obtain password let passwordId = ''; password = internals.normalizePassword(password); if (password.id) { if (!/^\w+$/.test(password.id)) { return callbackTick(Boom.internal('Invalid password id')); } passwordId = password.id; } // Encrypt object string exports.encrypt(password.encryption, options.encryption, objectString, (err, encrypted, key) => { if (err) { return callback(err); } // Base64url the encrypted value const encryptedB64 = Hoek.base64urlEncode(encrypted); const iv = Hoek.base64urlEncode(key.iv); const expiration = (options.ttl ? now + options.ttl : ''); const macBaseString = exports.macPrefix + '*' + passwordId + '*' + key.salt + '*' + iv + '*' + encryptedB64 + '*' + expiration ; // Mac the combined values exports.hmacWithPassword(password.integrity, options.integrity, macBaseString, (err, mac) => { if (err) { return callback(err); } // Put it all together // prefix*[password-id]*encryption-salt*encryption-iv*encrypted*[expiration]*hmac-salt*hmac // Allowed URI query name/value characters: *-. \d \w const sealed = macBaseString + '*' + mac.salt + '*' + mac.digest; return callback(null, sealed); }); }); }
...
d: {
e: 'f'
}
};
var password = 'some_not_random_password_that_is_at_least_32_characters';
Iron.seal(obj, password, Iron.defaults, function (err, sealed) {
console.log(sealed);
});
```
The result `sealed` object is a string which can be sent via cookies, URI query parameter, or an HTTP header attribute.
To unseal the string:
...
unseal = function (sealed, password, options, callback) { const now = Date.now() + (options.localtimeOffsetMsec || 0); // Measure now before any other processing const callbackTick = Hoek.nextTick(callback); // Break string into components const parts = sealed.split('*'); if (parts.length !== 8) { return callbackTick(Boom.internal('Incorrect number of sealed components')); } const macPrefix = parts[0]; const passwordId = parts[1]; const encryptionSalt = parts[2]; const encryptionIv = parts[3]; const encryptedB64 = parts[4]; const expiration = parts[5]; const hmacSalt = parts[6]; const hmac = parts[7]; const macBaseString = macPrefix + '*' + passwordId + '*' + encryptionSalt + '*' + encryptionIv + '*' + encryptedB64 + '*' + expiration; // Check prefix if (macPrefix !== exports.macPrefix) { return callbackTick(Boom.internal('Wrong mac prefix')); } // Check expiration if (expiration) { if (!expiration.match(/^\d+$/)) { return callbackTick(Boom.internal('Invalid expiration')); } const exp = parseInt(expiration, 10); if (exp <= (now - (options.timestampSkewSec * 1000))) { return callbackTick(Boom.internal('Expired seal')); } } // Obtain password if (password instanceof Object && !(Buffer.isBuffer(password))) { password = password[passwordId || 'default']; if (!password) { return callbackTick(Boom.internal('Cannot find password: ' + passwordId)); } } password = internals.normalizePassword(password); // Check hmac const macOptions = Hoek.clone(options.integrity); macOptions.salt = hmacSalt; exports.hmacWithPassword(password.integrity, macOptions, macBaseString, (err, mac) => { if (err) { return callback(err); } if (!Cryptiles.fixedTimeComparison(mac.digest, hmac)) { return callback(Boom.internal('Bad hmac value')); } // Decrypt const encrypted = Hoek.base64urlDecode(encryptedB64, 'buffer'); if (encrypted instanceof Error) { return callback(Boom.wrap(encrypted)); } const decryptOptions = Hoek.clone(options.encryption); decryptOptions.salt = encryptionSalt; decryptOptions.iv = Hoek.base64urlDecode(encryptionIv, 'buffer'); if (decryptOptions.iv instanceof Error) { return callback(Boom.wrap(decryptOptions.iv)); } exports.decrypt(password.encryption, decryptOptions, encrypted, (ignoreErr, decrypted) => { // Cannot fail since all errors covered by hmacWithPassword() // Parse JSON let object = null; try { object = JSON.parse(decrypted); } catch (err) { return callback(Boom.internal('Failed parsing sealed object JSON: ' + err.message)); } return callback(null, object); }); }); }
...
});
```
The result `sealed` object is a string which can be sent via cookies, URI query parameter, or an HTTP header attribute.
To unseal the string:
```javascript
Iron.unseal(sealed, password, Iron.defaults, function (err, unsealed) {
// unsealed has the same content as obj
});
```
### Options
...