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 NaCl in TypeScript, which is ECMAScript with compile-time types. Library implements NaCl's box and secret box. Signing code comes from SUPERCOP version 2014-11-24.
TypeScript re-write of scrypt key derivation function has been added to ecma-nacl beyond NaCl. Scrypt is a highly valuable thing for services that allow users to have passwords, while doing proper work with crypto keys, derived from those passwords.
Re-writes are based on C sources, 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. Scrypt's tests a taken from an RFC draft. Besides this, we added comparison runs between ecma-nacl, and js-nacl with js-scrypt, which is an Emscripten-compilation of C respective libraries. These comparison runs can be done in both node and browsers.
This library is registered on npmjs.org. To install it, do:
npm install ecma-nacl
Package comes with already compiled library code in dist/ folder. For testing, bring up a build environment (see below), and run necessary gulp tasks.
Once you get package, or this repo, do in the folder
which will install dev-dependencies.
Building is done with gulp, exposed via npm script. Do in the folder
npm run gulp help
and have fun with it.
Add module into code as
var nacl = ;
Secret-key authenticated encryption is provided by secret_box, which implements XSalsa20+Poly1305.
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. 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_box;// decryption, or opening is done byvar result_bytes = naclsecret_box;
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_boxformatWN;// decryption, or opening is done byvar result_bytes = naclsecret_boxformatWN;// extraction of nonce from cipher can be done as followsvar extracted_nonce = naclsecret_boxformatWN;
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. Object nonce contains functions to advance nonce, to calculate consequent nonces, etc. The 24 bytes of a nnce are taken as three 32-bit integers, and are advanced by 1 (oddly), by 2 (evenly), or by n. So, when encrypting many segments of a huge file, one can 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 oddlynaclnonce;// nonce changed in place evenlynaclnonce;// nonce changed in place by deltanaclnonce;// calculate related noncevar relatedNonce = naclnonce;// find delta between nonces (null result is for unrelated nonces)var delta = naclnonce;
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, one may make an encryptor and a decryptor. These are made to produce and read ciphers with-nonce format.
// delta is optional, defaults to onevar encryptor = naclsecret_boxformatWN;// packing bytes is done withvar cipher_bytes = encryptor;// when encryptor is no longer needed, key should be properly wiped from memoryencryptor;var decryptor = naclsecret_boxformatWN;// opening is done withvar result_bytes = decryptor;// when encryptor is no longer needed, key should be properly wiped from memorydecryptor;
Public-key authenticated encryption is provided by box, which implements Curve25519+XSalsa20+Poly1305. 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 = naclbox;
Secret key may come from browser's crypto.getRandomValues(array), or be derived from a passphrase with scrypt.
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 = naclbox;// Bob opens the messagevar msg_bytes = naclbox;
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 = naclbox;// Alice encrypts message for Bobvar cipher_bytes = naclboxstream;// Bob calculates DH-shared keyvar dhshared_key = naclbox;// Bob opens the messagevar msg_bytes = naclboxstream;
Or, we may use box encryptors that do first step of DH-shared key calculation only at creation.
// generate nonce, browser examplevar nonce = 24;crypto;// make encryptor to produce with-nonce format (default delta is two)var encryptor = naclboxformatWN;// pack messages to Bobvar cipher_to_send = encryptor;// open mesages from Bobvar decryptor = naclboxformatWN;var msg_from_bob = decryptor;// when encryptor is no longer needed, key should be properly wiped from memoryencryptor;decryptor;
// get nonce from Alice's first message, advance it oddly, and// use for encryptor, as encryptors on both sides advance nonces evenlyvar nonce = naclboxformatWN;naclnonce;// make encryptor to produce with-nonce format (default delta is two)var encryptor = naclboxformatWN;// pack messages to Alicevar cipher_to_send = encryptor;// open mesages from Alicevar decryptor = naclboxformatWN;var msg_from_alice = encryptor;// when encryptor is no longer needed, key should be properly wiped from memoryencryptor;decryptor;
signing object contains all related functionality.
// signing key pair can be generated from some seed array, which can// either be random itself, or be generated from a passwordvar pair = naclsigning;// make signature bytes, for msgvar msgSig = naclsigning;// verify signaturevar sigIsOK = naclsigning;
There are functions like NaCl's sign and sign_open methods, which place signature and message into one array, and expect the same for opening (verification). In a context of JWK, abovementioned functions seem to be more flexible and useful than C's API.
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).
Scrypt derives a key from users password.
Algorithm is memory-hard, which means it uses lots and lots of memory.
There are three parameters that go into derivation:
Amount of memory used is roughly
128 * N * r == r * 2^(7+logN) bytes.
r = 8 ,
logN is 10, it is a MB range of memory,
logN is 20, it is a GB range of memory in use.
p says how many times should the whole operation occur.
So, when running out of memory (js is not giving enough memory for
logN = 20
), one may up
It goes without saying, that such operations take time, and this implementation has a callback for progress reporting.
// given pass (secret), salt and other less-secret parameters// key of length keyLen is generated as follows:var logN = 17;var r = 8;var p = 2;var key = nacl;
Colin Percival's paper about scrypt makes for a lovely weekend-long reading.
This code is provided here under Mozilla Public License Version 2.0.
NaCl C library is public domain code by Daniel J. Bernstein and co. We thank thy wisdom of giving us developer-friendly library.
Scrypt C library is created by Colin Percival. We thank thee for bringing us strong new ideas in key derivation tasks.