Cipher Set 3a
Cipher Set 3a is based on Daniel J. Bernstein's NaCl: Networking and Cryptography library. The cipher set leverages the public-key and secret-key portions of NaCl. Implementations will need to support the crypto_box, crypto_secretbox, and crypto_onetimeauth related functions.
The version of NaCl used for 3a is implemented with crypto_box_curve25519xsalsa20poly1305, future versions of NaCl with other configurations will likely be defined in different Cipher Sets.
Keys
All CS3a key pairs are generated using NaCl's crypto_box from the components for public-key cryptography.
Here is some example code:
var sodium = require("sodium").api;
var keys = sodium.crypto_box_keypair();
console.log(keys.publicKey); // binary public key, 32 bytes
Message BODY
The BODY of a message packet is binary and defined as the following byte sections in sequential order:
KEY- 32 bytes, the sending exchange's ephemeral public keyNONCE- 24 bytes, randomly generatedCIPHERTEXT- the inner packet bytes encrypted using secretbox() using theNONCEas the nonce and the shared secret (derived from the recipients endpoint key and the included ephemeral key) as the keyAUTH- 16 bytes, the calculated onetimeauth(KEY+NONCE+CIPHERTEXT, SHA256(NONCE+ secret)) using the shared secret derived from both endpoint keys, the hashing is to minimize the chance that the same key input is ever used twice
Channel Setup
Channel secret keys are generated by performing a SHA-256 hash of the shared secret (agreedKey) and the TOKEN values:
- channel encryption key: SHA256(secret, sent-KEY, received-KEY) / 2
- channel decryption key: SHA256(secret, received-KEY, sent-KEY) / 2
Channel BODY
The enclosing channel packet binary is defined as the following byte sections in sequential order:
TOKEN- 16 bytes, from the handshake, required for all channel packetsNONCE- 24 bytes, randomly generatedCIPHERTEXT- the secretbox() output representing the encrypted inner packet
Example Code For Discussion (handshake)
The following example illustrates the usage of cs3a for the sending and receiving handshakes for a new exchange.
Warning: pseudo code interspersed with real code.
Message (handshake) Encryption:
// Generate Exchange Key Pair
var ephemeral = sodium.crypto_box_keypair();
// get the shared secret to create the iv+key for the open aes
var secret = sodium.crypto_box_beforenm(remote.publicKey, self.ephemeral.secretKey);
var nonce = crypto.randomBytes(24);
// encrypt the inner
var innerc = sodium.crypto_secretbox(inner, nonce, secret);
var body = Buffer.concat([ephemeral.publicKey,nonce,innerc]);
// hmac it with secret from the endpoint keys
var msecret = sodium.crypto_box_beforenm(remote.publicKey, self.secretKey);
var akey = crypto.createHash('sha256').update(Buffer.concat([nonce,msecret])).digest();
var mac = sodium.crypto_onetimeauth(body,akey);
Message Decryption:
var key = body.slice(0,32);
var nonce = body.slice(32,32+24);
var innerc = body.slice(32+24,body.length-16);
var secret = sodium.crypto_box_beforenm(key, self.secretKey);
// decipher the inner
var inner = sodium.crypto_secretbox_open(innerc,nonce,secret);
Sender Verification:
var mac1 = body.slice(body.length-16);
var nonce = body.slice(32,32+24);
var secret = sodium.crypto_box_beforenm(remote.publicKey, self.secretKey);
var akey = crypto.createHash('sha256').update(Buffer.concat([nonce,secret])).digest();
var mac2 = sodium.crypto_onetimeauth(body.slice(0,body.length-16),akey);
if(mac2 != mac1) return false;
Channel Key Setup:
// extract received ephemeral key
var key = body.slice(0,32);
var secret = sodium.crypto_box_beforenm(key, remote.ephemeral.secretKey);
var encKey = crypto.createHash("sha256")
.update(secret)
.update(remote.ephemeral.publicKey)
.update(key)
.digest();
var decKey = crypto.createHash("sha256")
.update(secret)
.update(key)
.update(remote.ephemeral.publicKey)
.digest();
Channel Encryption:
var nonce = crypto.randomBytes(24);
var cbody = sodium.crypto_secretbox(inner, nonce, encKey);
var outer = Buffer.concat([nonce,cbody]);
Channel Decryption:
var nonce = outer.slice(0,24);
var cbody = outer.slice(24);
var body = sodium.crypto_secretbox_open(cbody,nonce,decKey);