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.
When a switch is first initialized, it must generate a key pair and fingerprint for each cipher set. For cs3a, this key pair is 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
console.log(crypto.createHash("sha256").update(keys.publicKey).digest("hex")); // fingerprint
In order to create a new line, a switch must generate a new, temporary key pair for that line. This key pair is also created using crypto_box. The public key for the line is called the LINE KEY
. After a successful open handshake, both parties will be in posession of a shared secret (agreedKey).
The BODY of the open packet is binary and defined as the following byte sections in sequential order:
AUTH
- 16 bytes, the calculated onetimeauth(line key, inner ciphertext)LINE KEY
- 32 bytes, the sender's line level public keyINNER CIPHERTEXT
- the secretbox() encrypted inner packet
After a line has been opened, the line encryption keys are generated by performing a SHA-256 hash of the shared secret (agreedKey) and the line ids from each switch:
- line encryption key: SHA256(secret, my-line-id, their-line-id)
- line decryption key: SHA256(secret, their-line-id, my-line-id)
The enclosing line packet binary is defined as the following byte sections in sequential order:
NONCE
- 24 bytes, randomly generatedCHANNEL CIPHERTEXT
- the secretbox() output representing the encrypted inner packet
The following example illustrates the usage of cs3a for the sending and receiving sides of an open
request. Warning: pseudo code interspersed with real code.
Sender (initiating an open request):
var sodium = require("sodium").api;
// Generate Switch Key Pair
// Upon switch initialization, an instance level key pair is generated for
// the switch.
var senderKeys = sodium.crypto_box_keypair();
// Generate Line Key Pair
// When the sender is ready to create an open request, a line specific key
// pair is generated.
var senderLineKeys = sodium.crypto_box_keypair();
// Generate a 24 byte nonce of 0
// Both sender and receiver must use the same initial nonce.
var nonce = new Buffer(24);
for (var i = 0; i < 24; ++i) {
nonce[i] = 0;
}
// Generate the shared secret (agreedKey)
// Before any specific message handling, this "message independent pre-computaion"
// is performed, generating the agreedKey. This shared secret is later used as a
// part of the encryption process for all packets sent over the line.
var agreedKey = sodium.crypto_box_beforenm(receiverKeys.publicKey, senderLineKeys.secretKey);
// Sample inner packet
// (The json schema can be found in the spec for open)
var plainText = JSON.stringify("This is a test chunk of data for the inner packet. it would have JSON and a payload of the sender publicKey");
// Encrypt the inner packet
// Note that this step uses NaCl's crypto_secretbox from the components for
// secret-key cryptography.
var innerPacketData = sodium.crypto_secretbox(new Buffer(plainText), nonce, agreedKey);
// Take the encrypted inner packet and the line level public key, and build the
// data section of the outer packet.
var openPacketData = Buffer.concat([senderLineKeys.publicKey, innerPacketData]);
// Generate the macKey
// The macKey uses the switch level private key.
var macKey = sodium.crypto_box_beforenm(receiverKeys.publicKey, senderKeys.secretKey);
// Generate the open MAC
// Note that this uses the NaCl's crypto_onetimeauth from the components for
// secret-key cryptography.
var openMAC = sodium.crypto_onetimeauth(openPacketData, macKey);
// Generate the outer packet BODY
// <open-MAC><sender-line-public-key><encrypted-inner-packet-data>
var openPacketBody = Buffer.concat([openMAC, openPacketData]);
Receiver (accepting an open request):
var sodium = require("sodium").api;
// Generate Switch Key Pair
// Upon switch initialization, an instance level key pair is generated for
// the switch.
var receiverKeys = sodium.crypto_box_keypair();
// Generate Line Key Pair
// Upon receiving an open request, a line specific key pair is generated.
var receiverLineKeys = sodium.crypto_box_keypair();
// Unpack and authenticate the outer packet
//
// At this point, the open packet has been received. Remember the following
// format:
// <open-MAC><sender-line-public-key><encrypted-inner-packet-data>
//
var openMAC = ... // the leading 16 bytes
var senderLineKeys.publicKey = ... // the next 32 bytes
var innerPacketData = ... // the remaining bytes are the encrypted inner packet data
var openPacketData = ... // Buffer.concat([senderLineKeys.publicKey, innerPacketData]);
// Generate the macKey
// This macKey will match the macKey generated by the sender, due to the corresponding
// public/private keys. Here is the sender version for comparison:
// macKey = sodium.crypto_box_beforenm(receiverKeys.publicKey, senderKeys.secretKey);
//
var macKey = sodium.crypto_box_beforenm(senderKeys.publicKey, receiverKeys.secretKey);
// Authenticate the open packet
// With all the pieces available, a onetimeauth verification can be completed. If
// successful, this step completes the authentication of the open request.
var authed = sodium.crypto_onetimeauth_verify(openMAC, openPacketData, macKey) === 0 ;
console.log("Open mac verify:", authed);
// Auth successful? The decryption can proceed.
// Generate a 24 byte nonce of 0
// (same as sender for open)
var nonce = new Buffer(24);
for (var i = 0; i < 24; ++i) {
nonce[i] = 0;
}
// Generate the shared secret (agreedKey)
// This agreedKey will match the agreedKey generated by the sender, due to the
// corresponding public/private keys. Here is the sender version for comparison:
// agreedKey = sodium.crypto_box_beforenm(receiverKeys.publicKey, senderLineKeys.secretKey);
//
var agreedKey = sodium.crypto_box_beforenm(senderLineKeys.publicKey, receiverKeys.secretKey);
// Decrypt the inner packet
// Using the agreedKey, the inner packet can now be decrypted. This should match
// the sample plaintext from the sender example.
var decryptedPacketData = sodium.crypto_secretbox_open(innerPacketData, nonce, agreedKey);