NaCl is a great crypto library that is not placing a burden of crypto-math choices onto developers, providing only solid high-level functionality (box - for public-key, and secret_box - for secret key authenticated encryption), in a let's stop blaming users of cryptographic library (e.g. end product developers, or us) manner. Take a look at details of NaCl's design "The security impact of a new cryptographic library".
ecma-nacl is a re-write of most important NaCl's functionality, which is ready for production, box and secret_box. Signing code still has XXX comments, indicating that the warning in signing in NaCl should be taken seriously.
Rewrite is based on the copy of NaCl, included in this repository. Tests are written to correspond those in C code, to make sure that output of this library is the same as that of C's version. Besides this, we added comparison runs between ecma-nacl, and js-nacl, which is an Emscripten-compilation of C library. These comparison runs can be done in both node and browsers.
As of version 0.5, js-nacl is properly compiled into asm.js code, recognized by Firefox. Proper asm.js does run two times faster than pure js. So, we have a tradeoff here: with js-nacl one gets speed, which realistically matters on client side when encrypting 100s MBs of data, and with ecma-nacl one gets smaller library size and better auditability. Asm.js might still be a moving target, while ecma-nacl is exploiting only those JS features that has already been widely implemented. At the same time, js-nacl keeps C-ish api, while ecma-nacl is providing JS-ish api, adding convenient packaging features. In fact, we are confident, that things like XSP file format, introduced in ecma-nacl api, shall spread beyond JS implementations of NaCl.
This library is registered on npmjs.org. To install it:
npm install ecma-nacl
make-browserified.js will let you make a browserified module. So, make sure that you have browserify module to run the script. You may also modify it to suite your particular needs.
Add module into code as
var nacl = require'ecma-nacl';
Secret-key authenticated encryption is provided by secret_box, which implements XSalsa20+Poly1305, and nothing else.
When encrypting, or packing, NaCl does following things. First, it encrypts plain text bytes using XSalas20 algorithm. Secondly, it creates 16 bytes of authentication Poly1305 code, and places these infront of the cipher. Thus, regular byte layout is 16 bytes of Poly1305 code, followed by cipher with actual message, having exactly the same length as plain text message.
Decrypting, or opening goes through these steps in reverse. First, Poly1305 code is read and is compared with code, generated by reading cipher. When these do not match, it means either that key+nonce pair is incorrect, or that cipher with message has been damaged/changed. Our code will throw an exception in such a case. Secondly, when verification is successful, XSalsa20 will do decryption, producing message bytes.
// all incoming and outgoing things are Uint8Array's;// to encrypt, or pack plain text bytes into cipher bytes, usevar cipher_bytes = naclsecret_boxpackplain_bytes nonce key;// decryption, or opening is done byvar result_bytes = naclsecret_boxopencipher_bytes nonce key;
Above pack method will produce an Uint8Array with cipher, offset by 16 zero bytes in the underlying buffer. Deciphered bytes, on the other hand, are offset by 32 zero bytes. This should always be kept in mind, when transferring raw buffers to/from web-workers. In all other places, this padding is never noticed, thanks to typed array api.
Key is 32 bytes long. Nonce is 24 bytes. Nonce means number-used-once, i.e. it should be unique for every segment encrypted by the same key.
Sometimes, when storing things, it is convenient to pack cipher together with nonce (WN) into the same array.
+-------+ +------+ +---------------+ | nonce | | poly | | data cipher | +-------+ +------+ +---------------+ | <---- WN format ----> |
For this, secret_box has formatWN object, which is used analogously:
// encrypting, and placing nonce as first 24 bytes infront NaCl's byte output layoutvar cipher_bytes = naclsecret_boxformatWNpackplain_bytes nonce key;// decryption, or opening is done byvar result_bytes = naclsecret_boxformatWNopencipher_bytes key;// extraction of nonce from cipher can be done as followsvar extracted_nonce = naclsecret_boxformatWNcopyNonceFromcipher_bytes;
Cipher array here has no offset in the buffer, but decrypted array does have the same 32 zero bytes offset, as mentioned above.
It is important to always use different nonce, when encrypting something new with the same key. A function is provided, to advance nonce. The 24 bytes are taken as three 32-bit integers, and are advanced by 1 (oddly) or by 2 (evenly). So, when encrypting many segments of a huge file, advance nonce oddly every time. When key is shared, and is used for communication between two parties, one party's initial nonce may be oddly advanced initial nonce, received from the second party, and all other respective nonces are advanced evenly on both sides of communication. This way, unique nonces are used for every message send.
// nonce changed in place oddlynacladvanceNonceOddlynonce;// nonce changed in place evenlynacladvanceNonceEvenlynonce;
It is common, that certain code needs to be given encryption/decryption functionality, but according to principle of least authority such code does not necessarily need to know secret key, with which encryption is done. So, there is an encryptor for opening and packing, with inbuilt even advance of the nonce, on every new cipher that is generated. It is made to produce and read ciphers with-nonce format.
var encryptor = naclsecret_boxformatWNmakeEncryptorkey nextNonce;// packing bytes is done withvar cipher_bytes = encryptorpackplain_bytes;// opening is done withvar result_bytes = encryptoropencipher_bytes;// when encryptor is no longer needed, key should be properly wiped from memoryencryptordestroy;
Public-key authenticated encryption is provided by box, which implements Curve25519+XSalsa20+Poly1305, and nothing else. Given pairs of secret-public keys, corresponding shared, in Diffie–Hellman sense, key is calculated (Curve25519) and is used for data encryption with secret_box (XSalsa20+Poly1305).
Given any random secret key, we can generate corresponding public key:
var public_key = naclboxgenerate_pubkeysecret_key;
There are two ways to use box. The first way is to always do two things, calculation of DH-shared key and subsequent packing/opening, in one step.
// Alice encrypts message for Bobvar cipher_bytes = naclboxpackmsg_bytes nonce bob_pkey alice_skey;// Bob opens the messagevar msg_bytes = naclboxopencipher_bytes nonce alice_pkey bob_skey;
The second way is to calculate DH-shared key once and use it for packing/opening multiple messages, with box.stream.pack and box.stream.open, which are just nicknames of described above secret_box.pack and secret_box.open.
// Alice calculates DH-shared keyvar dhshared_key = naclboxcalc_dhshared_keybob_pkey alice_skey;// Alice encrypts message for Bobvar cipher_bytes = naclboxstreampackmsg_bytes nonce dhshared_key;// Bob calculates DH-shared keyvar dhshared_key = naclboxcalc_dhshared_keyalice_pkey bob_skey;// Bob opens the messagevar msg_bytes = naclboxstreamopencipher_bytes nonce dhshared_key;
Or, we may use box encryptors that do first step of DH-shared key calculation only at creation.
// generate nonce, browser examplevar nonce = 24;cryptogetRandomValuesnonce;// make encryptor to produce with-nonce formatvar encryptor = naclboxformatWNmakeEncryptorbob_pkey alice_skey nonce;// pack messages to Bobvar cipher_to_send = encryptorpackmsg_bytes;// open mesages from Bobvar msg_from_bob = encryptoropenreceived_cipher;// when encryptor is no longer needed, key should be properly wiped from memoryencryptordestroy;
// get nonce from Alice's first message, advance it oddly, and// use for encryptor, as encryptors on both sides advance nonces evenlyvar nonce = naclboxformatWNcopyNonceFromcipher1_from_alice;nacladvanceNonceOddlynonce;// make encryptor to produce with-nonce formatvar encryptor = naclboxformatWNmakeEncryptoralice_pkey bob_skey nonce;// pack messages to Alicevar cipher_to_send = encryptorpackmsg_bytes;// open mesages from Alicevar msg_from_alice = encryptoropenreceived_cipher;// when encryptor is no longer needed, key should be properly wiped from memoryencryptordestroy;
NaCl does not do it. The randombytes in the original code is a unix shim with the following rational, given in the comment, quote: "it's really stupid that there isn't a syscall for this".
So, you should obtain cryptographically strong random bytes yourself. In node, there is crypto. There is crypto in browser. IE6? IE6 must die! Stop supporting insecure crap! Respect your users, and tell them truth, that they need modern secure browser(s).
It is still not settled into production code in NaCl. Period. When it is ready, we will be able to serve it.
DNSCurve is questioning whether common places of signing be better served with public-key encryption.
Each NaCl's cipher must be read completely, before any plain text output. Such requirement makes reading big files awkward. Thus, the simplest solution is to pack NaCl's binary ciphers into self-contained small segments, each encrypted with a different nonce. Such segment-based format is also useful in a streaming situation, when one end starts to send a file, without knowing when EOF comes.
We call this format XSP, to stand for XSalsa+Poly, to indicate that file layout is specifically tailored for storing NaCl's secret box's ciphers.
Each segment contains a header. The first segment contains a file header, followed by a common segment header.
File header layout:
+-----+ +-------------------+ +--------------+ | xsp | | file key envelope | | max seg size | +-----+ +-------------------+ +--------------+
First three bytes is a constant ASCII encoded string 'xsp'. This takes up first 3 bytes.
"File key envelope" is an encrypted, and packed with-nonce (WN format), key, used to encrypt every segment of a given file. Length of this envelope calculated as follows: key is 32 bytes, add 16 Poly bytes, and 24 nonce. This gives 72 bytes for file key envelope.
"Max seg size" is 4 bytes with a maximum, or common segment length, written in little endian way. Total length of any segment in this file should never exceed given value.
Thus, file header, has a predictable length of 79 bytes.
| <-- segment header --> | +-------------------------+ +---------------+ | seg size | nonce | poly | | data cipher | +-------------------------+ +---------------+ | <---- WN format ----> |
Segment starts with 4 bytes, containing total length of this segment, written in little endian way. Next 40 bytes contain nonce and poly bytes. These first 44 bytes we call a segment header.
Notice that nonce and poly in the header are layed out so, that together with the following data cipher, they constitute WN pack format.
First segment layout:
+-------------+ +----------------+ +---------------+ | file header | | segment header | | data cipher | +-------------+ +----------------+ +---------------+
Data cipher are the crypto stream bytes, into which data was xor-ed. Therefore, data cipher's length is exactly the same as encoded data's length.
There is a sub-module, with XSP-related functionality:
var xsp = naclfileXSP;
Each xsp object has two encrypted parts that use different keys. One part is the content, encrypted with file key. Another part is an encrypted file key package, which is encrypted with some master key. Thus, to instantiate encryptor, we need a file key, and a function that will encrypt it to a master key. Notice, that we can provide master key itself, but, a principle of least priviledge dictates that we should provide only required master key's encrypting capability, and nothing else.
// we should get or generate a file keyvar fileKey = foo;// given master key encryptor, we can take its pack functionvar fileKeyPackFunc = masterKeyEncpack;var fileKeyOpenFunc = masterKeyEncopen;
There are two situations, in which xsp encryptor can be initialized. First situation is when there is no existing xsp file:
var enc = xspmakeNewFileEncryptormaxSegSize fileKey fileKeyPackFunc;
Second situation is when file exists:
var enc = xspmakeExistingFileEncryptorfirstSegHeader fileKeyOpenFunc;
Encryptor can pack Uint8Array data into segments:
var segments =dataOffset = 0;// packing first segment uses special methodvar encRes = encpackFirstSegmentdata nonce;// result object has created segment, and a number of packed data bytessegmentspush encResseg ;dataOffset += encResdataLen;// nonce must be changed for use in a different segmentnacladvanceNonceOddlynonce;whiledataOffset < datalength// packing other segmentsencRes = encpackSegmentdata nonce;segmentspush encResseg ;dataOffset += encResdataLen;nacladvanceNonceOddlynonce;
Encryptor can open segments (notice how incoming arrays must be alligned with segments' starting point), given complete xsp file as Uint8Array:
var fileOffset = 0dataParts =decRes;whilefileOffset < xspFilelengthdecRes = encopenSegment xspFilesubarrayfileOffset ;// result object has opened data, and segment's size, read from filedataPartspush decResdata ;fileOffset += decRessegLen
Encryptors should be properly disposed after use:
This code is provided here under Mozilla Public License Version 2.0.
NaCl C library is public domain code by Daniel J. Bernstein and others crypto gods and semi-gods. We thank thy wisdom of giving us developer-friendly library.