Quindi, ho usato il modulo crypto
in node.js per implementare un generatore di chiavi sicuro e le funzioni di crittografia / decrittografia. Sapendo che il migliore amico della sicurezza è un riflettore sull'implementazione, voglio buttarlo qui e assicurarmi di non cadere nei buchi.
Un po 'del mio caso d'uso: sto costruendo un'app che comunica su Internet utilizzando socket tcp. Per il momento, lo scambio di chiavi e la verifica dell'identità sono al di fuori del mio scopo, genererò le chiavi e le copierò manualmente a ciascuna estremità della comunicazione.
Ho intenzione di rilasciare questo codice come un modulo node.js, esponendo esplicitamente le ipotesi e le limitazioni inerenti alle scelte che ho fatto ... questo è destinato a essere un pony un trucco, fatto bene.
Ho copiato le funzioni rilevanti di seguito. Puoi vedere il codice completo all'indirizzo link , che include diverse altre funzioni bloccate, tra cui alcuni per la gestione delle password degli utenti, puoi guardare quelle cose se sei interessato, ma tutto quello che ti chiedo qui sono le cose che ho copiato qui sotto. Ho leggermente riscritto queste funzioni per postare qui per ritagliare un piccolo cruft opzionale, capisco che il diavolo potrebbe essere nei dettagli ed è per questo che ho incluso il link sopra alla sorgente reale.
Ho fatto commenti dove appropriato per descrivere cosa sto facendo e perché. Non esitate a correggere eventuali ipotesi errate che ho esposto oltre a valutare l'implementazione stessa.
generazione di chiavi:
// gen_key() is intended for manual use on the command line.
// It's entirely synchronous and not built for high volume use
var gen_key = exports.gen_key = function(params) {
params = params || {};
// ASSERTION: I'm using AES 256, therefore the resulting
// ... key length should be 256 bits
var keylen = 256; // 256 bits
// the following are relevant to deriving keys from passwords
// ASSUMPTION: I've universally seen a salt length of 128
// ... bits recommended, so I went with it
var saltlen = 128; // 128 bits
// ASSERTION: This number is obviously dependent on the
// ... current and recent state of hardware, though for
// ... our purposes overkill is a virtue
var iterations = 1048576; // default to 2^20 iterations
// all of the relevant functions take input in bytes
var byte_length = params.byte_length ?
parseInt(params.byte_length) :
(params.bit_length ?
(parseInt(params.bit_length)/8) :
(keylen/8));
if (params.password) {
// crypto.randomBytes is a CSPRNG
var salt = params.salt?
params.salt:
crypto.randomBytes(saltlen/8);
if (params.iterations) iterations =
params.iterations;
// NOTE: The native pbkdf2 function uses sha1, which is
// ... perhaps not ideal.
var key = crypto.pbkdf2Sync(params.password, salt,
iterations, byte_length);
// NOTE: This is tuned to output a string which can
// ... be copied and saved, but allows for other types
// ... of use
return params.return_params ?
[
(params.raw?key:key.toString('base64')),
{
salt: (params.raw?salt:salt.toString('base64')),
iterations: iterations,
keylen: byte_length,
algo: 'pbkdf2',
hash: 'hmac-sha1'
}
] :
(params.raw?key:key.toString('base64'));
}
else {
// crypto.randomBytes is a CSPRNG
var key = crypto.randomBytes(byte_length);
// ASSUMPTION: A string of random bytes generated by a
// ... CSPRNG is sufficient to use as a cryptographically
// ... secure key without further processing
return params.raw?key:key.toString('base64');
}
};
crittografia simmetrica:
var encipher = exports.encipher = function(payload, key, mackey, opts, cb) {
opts = opts || {};
// ASSUMPTION: I've universally seen an iv length of 128
// ... bits recommended, so I went with it
var iv_length = 128; // bit length
// this is intended for moderate volume usage, so make it asynchronous
// it's not optimized for large payload sizes at this time
// ASSUMPTION: A random IV is still preferred over a simple one even
// ... though we're using CTR (see below), which can effectively cope
// ... with a simple IV
crypto.randomBytes(iv_length/8, function(err, iv) {
if (err) cb(err);
// for now, force the cipher used and mode
opts.algorithm = 'aes-256';
// ASSUMPTION: CTR mode is preferred over CBC or another mode
// ... with padding, because it eliminates the padding-oracle
// ... attack. I use CTR over GCM because the authentication
// ... properties of GCM are not natively available in node.js
opts.mode = 'ctr';
// QUESTION: I mostly just chose this with the bigger-is-better
// ... mindset, any reason to choose something different?
opts.hmac_algo = 'sha512';
// convert our payload and keys to buffers, particularly to
// allow us to specify the encoding of our keys
if (!Buffer.isBuffer(payload)) payload = opts.payload_encoding ?
new Buffer(payload, opts.payload_encoding) :
new Buffer(payload);
if (!Buffer.isBuffer(key)) key = opts.key_encoding ?
new Buffer(key, opts.key_encoding) :
new Buffer(key);
if (!Buffer.isBuffer(mackey)) mackey = opts.mackey_encoding ?
new Buffer(mackey, opts.mackey_encoding) :
new Buffer(mackey);
var cipher = crypto.createCipheriv(opts.algorithm+'-'+opts.mode,
key, iv);
cipher.write(payload);
cipher.end();
var ciphertext = cipher.read();
// ASSERTION: the key used to generate the HMAC should be
// ... different than the key used to generate the ciphertext
// ... though it's not explicitly enforced
var hmac = crypto.createHmac(opts.hmac_algo, mackey);
// ASSERTION: The HMAC should be produced from the iv+ciphertext
hmac.write(iv);
hmac.write(ciphertext);
hmac.end();
var mac = hmac.read();
// send all of the public data needed to decrypt the message,
// the calling application can handle how they're packaged together
cb(null, mac, iv, ciphertext);
});
};
decrittazione simmetrica:
// This is strictly a functional reversal of the encipher method
// Only question has to do with timing
var decipher = exports.decipher = function(payload, key, mackey, mac,
iv, opts) {
opts = opts || {};
// we pass in the pieces individually, the calling application
// manages how the iv and mac are packaged together
// for now, force the cipher used and mode
opts.algorithm = 'aes-256';
opts.mode = 'ctr';
opts.hmac_algo = 'sha512';
if (!Buffer.isBuffer(payload)) payload = opts.payload_encoding ?
new Buffer(payload, opts.payload_encoding) :
new Buffer(payload);
if (!Buffer.isBuffer(key)) key = opts.key_encoding ?
new Buffer(key, opts.key_encoding) :
new Buffer(key);
if (!Buffer.isBuffer(mackey)) mackey = opts.mackey_encoding ?
new Buffer(mackey, opts.mackey_encoding) :
new Buffer(mackey);
if (!Buffer.isBuffer(iv)) iv = opts.iv_encoding ?
new Buffer(iv, opts.iv_encoding) :
new Buffer(iv);
if (!Buffer.isBuffer(mac)) mac = opts.mac_encoding ?
new Buffer(mac, opts.mac_encoding) :
new Buffer(mac);
var hmac = crypto.createHmac(opts.hmac_algo, mackey);
hmac.write(iv);
hmac.write(payload);
hmac.end();
// if we haven't authenticated, then we've got a problem
// QUESTION: Presumably the calling application needs to
// ... behave carefully to avoid enabling a timing-oracle
// ... attack?
if (hmac.read().toString(opts.mac_encoding) !==
mac.toString(opts.mac_encoding))
return new Error('Message failed to authenticate');
var decipher = crypto.createDecipheriv(opts.algorithm+'-'+opts.mode,
key, iv);
decipher.write(payload);
decipher.end();
var plaintext = decipher.read();
return plaintext; // return a raw buffer of our decrypted text
};
EDIT - e un caso d'uso:
> var onecrypt = require('./lib/onecrypt');
> var key = onecrypt.gen_key({ raw: true });
> var mackey = onecrypt.gen_key({ raw: true});
> var result;
> onecrypt.encipher('secret message YAY!', key, mackey, null, function(err, mac, iv, ciphertext) { result = [mac, iv, ciphertext]; });
-- in my use-case, I would then send the mac, iv, and ciphertext over an unencrypted TCP socket
> var plain = onecrypt.decipher(result[2], key, mackey, result[0], result[1]);
> console.log(plain.toString());
-- outputs:
'secret message YAY!'