Skip to content
Brendan Mc. edited this page Mar 17, 2015 · 16 revisions

Cipher Modes

Out of the box, SJCL supports three cipher modes: CCM, GCM, and OCB2.

The default cipher mode is CCM, although you can choose any other with a mode parameter:

sjcl.encrypt("password", "Your plaintext", {mode : "ccm || gcm || ocb2"})
sjcl.decrypt("password", ciphertext) // Mode can be inferred.

All three cipher modes are authenticated and confidential. There's often little to no reason to change the cipher's mode of operation except for compatibility with another system. OCB2 is the only one of the three that's encumbered by patents in the United States.

If the first argument given is a String, SJCL will assume that it is a possibly low-entropy password and will derive a suitable key with PBKDF2 and a random salt. However, if the first argument is a bitArray of the appropriate length (for AES, 128 or 256 bits), SJCL will pass it directly to the cipher without any manipulation, assuming that the user has already handled key generation. (The first argument can also be an ElGamal public/private key. See: Asymmetric Crypto)

Associated Data

All of the cipher modes above support associated data--data that's authenticated by the secret key (so it can't be changed), but is un-encrypted (so it can be read by anybody).

Associated data is often used to attach public header information to a ciphertext to allow sorting or indexing without enabling decryption. Another more security-conscious use would be putting timestamps or counters in the associated data to deter replay attacks.

SJCL puts associated data in the adata parameter and assumes that it is either a string or a bit array.

sjcl.encrypt("password", "Secrets!", {adata: "Not secrets!"})

Unfortunately, there's no way to retrieve the associated data from the decrypt function, so you have to decode the message and pull it out yourself:

sjcl.decrypt("password", ciphertext) // Look for any thrown errors.

// Output will be a bit array.
sjcl.json.decode(ciphertext).adata

Hashing

SJCL supports SHA-1, SHA-256, SHA-512, and RIPEMD-160, but only SHA-256 is enabled by default. The other three can be included with the --with-sha1, --with-sha512, or --with-ripemd160 options. (See Getting Started)

Hashing a string is as simple as:

sjcl.hash.sha256.hash("Hello World!")

The above will return a bit array, which you can convert into a more tractable form with any of the Codecs. (Bit arrays can also be passed as input for handling encodings other than utf8.)

SJCL hashes can also be used similar to the way that Node.js does hashing:

var h = new sjcl.hash.sha256()
h.update(data[0])
...
h.update(data[n - 1])

var out = h.finalize()

Hashing this way requires multiple lines of code but makes it easier to handle large files or streams.

HMAC

SJCL supports building HMACs with any of the implemented hashes. Once a secret key (not a password!) and a hash algorithm have been passed in, they work a lot like a hash.

Their one-line API looks like:

var key = ... // Either from an RBG or a PBKDF.
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256)

hmac.encrypt("Hello World!") // Contrary to the name, there is no decrypt.

And their stream-centric API looks like:

var key = ... // Still from an RBG or a PBKDF.
var hmac = new sjcl.misc.hmac(key, sjcl.hash.sha256)

hmac.update(data[0])
...
hmac.update(data[n - 1])

hmac.digest()

Note: Once again, the only real purpose of using HMACs in SJCL is for compatibility with other protocols/systems. If you just want data integrity without confidentiality, using Associated Data may be a safer option.

Generating Random Bytes

SJCL implements the Fortuna pseudo-random number generator. When initialized, the generator is seeded with 128 random bytes from Node.js' crypto package or from the browser's random number generator. If more entropy is required later, the generator can be re-seeded manually or it can collect random noise from key presses and mouse or computer movements.

In most calls to Fortuna's API, you'll be asked to give an integer in the range 0 to 10 inclusive called the paranoia. The paranoia level defines the quality of the randomness, 0 being lowest quality (good for testing only) and 10 being highest.

Assuming that the generator is already seeded, getting a random array is as simple as:

sjcl.random.randomWords(numWords, paranoia)

where numWords is the number of random words to generate (4 bytes to a word) and paranoia is the value discussed above (paranoia will default to 6 if omitted).

Seeding the Generator

If the environment allows easy access to random bytes, they can be piped into the generator with the addEntropy function:

var buf = crypto.randomBytes(1024 / 8) // 128 bytes
buf = new Uint32Array(new Uint8Array(buf).buffer)

sjcl.random.addEntropy(buf, 1024, "crypto.randomBytes")

However, in some cases there might not be easy access to random bytes so you would have to start the generator's collectors and wait until enough entropy has been gathered:

var cont = function () {
    sjcl.random.removeEventListener('seeded', cont)
    // The collectors can be turned off if no more entopy is needed.
    // sjcl.random.stopCollectors()

    // Continue doing whatever.
    sjcl.random.randomWords(...)
}

sjcl.random.addEventListener('seeded', cont)
sjcl.random.startCollectors()

If a paranoia other than 6 is preferred, then append: sjcl.random.setDefaultParanoia(paranoia)

Distinguishing between the two cases is fairly easy, as well. If the isReady function returns 0, then you need to collect entropy. If it returns 1 or 2, then you can continue automatically.


Footnotes

  1. sjcl.misc.pbkdf2 : For key derivation and stretching (More info)