AES (OCB)
- create
- create("aes-ocb")
- family
- Substitution-permutation
- options
- --key --nonce --aad? --tagLength?
- self-inverse
- no
- keyspace
- 2^128, 2^192 or 2^256 keys
- try it
- ciphers aes-ocb "ATTACK AT DAWN" --key 2b7e151628aed2a6abf7158809cf4f3c --nonce 000102030405060708090a0b
CCM gets its tag by running AES over everything twice. Once for the MAC, once for the keystream. OCB does both jobs in one pass. It's Phillip Rogaway's Offset Codebook, the third version, written down in RFC 7253. It's about twice as fast as CCM, and for years the patents kept most people away. Rogaway has since dropped all of them, and now it's in OpenSSL.
The trick is the offset. Every block gets its own mask, built from the key, the nonce and the block's position. The block is XORed with that mask, goes through AES and gets XORed with the same mask again. So it's a codebook, like ECB, only every block has a different one. The tag comes from AES over the XOR of all plaintext blocks, masked once more and mixed with a hash of the associated data. A short last block doesn't go through AES at all. It's XORed with AES of its offset, like a keystream.
const ocb = create("aes-ocb");
const key = "2b7e151628aed2a6abf7158809cf4f3c";
const nonce = "000102030405060708090a0b";
ocb.encode("ATTACK AT DAWN", { key, nonce }).text;
// "d5ae8f2ca0693c898f8e7466703c" + "a89edcaab00caca2cf36bfcac3794412"
ocb.decode("d5ae8f2ca0693c898f8e7466703ca89edcaab00caca2cf36bfcac3794412", { key, nonce }).text;
// "ATTACK AT DAWN"
Fourteen bytes of text, fourteen of ciphertext, sixteen of tag. No padding, same as CCM.
The options
The key is 32, 48 or 64 hex digits. nonce is required and can be anything from 1 to 15 bytes, so 2 to 30 hex digits. The examples in RFC 7253 all use 12. Unlike CCM, the nonce length doesn't cap the text.
tagLength is in bits, 64, 96 or 128, default 128. Those are the three the RFC names. And here's the catch that CCM doesn't have. The tag length goes into the nonce block, so it changes the ciphertext too, not only the tag:
ocb.encode("ATTACK AT DAWN", { key, nonce, tagLength: 64 }).text;
// "006442d918150ca6a4daf6903e6b" + "6141a3b9b9a68fe8"
Not a single byte in common with the 128-bit version. So cutting a long tag short doesn't give you a valid short one, it gives you an error.
aad works like in CCM. It's hex that the tag covers, nobody encrypts, and the output doesn't carry. The receiver needs the same value:
const aad = "46524f4d3a2048512e"; // "FROM: HQ." in hex
ocb.encode("ATTACK AT DAWN", { key, nonce, aad }).text;
// "d5ae8f2ca0693c898f8e7466703c" + "3540bc18319e6bafb89838f7fa34b4d6"
The text bytes stay the same, only the tag moves. The associated data goes through its own hash, and that hash lands only in the tag.
The reference is Node's createCipheriv("aes-128-ocb"), OpenSSL underneath, and it gives the same bytes. The mode is also checked against the sample results in RFC 7253 Appendix A, the iterated one included, for all nine named parameter sets.
Flipping bits
The DAWN to DUSK trick from CTR, XOR 00140405 into the right place:
ocb.decode("d5ae8f2ca0693c898f8e74727439a89edcaab00caca2cf36bfcac3794412", { key, nonce });
// CipherError: [aes-ocb] Tag does not match: wrong key, nonce, aad or tagLength, or the ciphertext was changed
Those fourteen bytes are a short last block, a keystream, so they do decrypt to ATTACK AT DUSK. decode just never shows it. The tag covers the plaintext, and a new tag needs the key.
Same nonce twice
This one hurts OCB more than CCM. Same key and nonce means the same offsets. For a short block that's the same keystream, and XOR of two ciphertexts is XOR of two texts, like in CTR. For whole blocks it's the same codebook, so an equal block at the same position comes out equal:
ocb.encode("ATTACK AT DAWN!!ATTACK AT DAWN!!", { key, nonce }).text;
// "14a72cedef94a49f1beb3c87797ef1a6" + "5b6f33e68b4cb242e94f7067c115b888" + tag
ocb.encode("ATTACK AT DAWN!!RETREAT AT NOON!", { key, nonce }).text;
// "14a72cedef94a49f1beb3c87797ef1a6" + "8e5aa111385224ba68acd9d30701144b" + tag
Inside one message the two equal blocks look nothing alike, because each has its own position. Across two messages under one nonce, the first block gives it away. RFC 7253 says a reused nonce also leaks what forging a tag needs. So every message gets its own nonce. A counter is fine, it only has to be unique.
A key that isn't 32, 48 or 64 hex digits is an InvalidOptionError, so is a nonce outside 2 to 30 hex digits, an aad that isn't whole bytes of hex, or a tagLength other than 64, 96 or 128. A missing key or nonce is a MissingOptionError. On decode, a ciphertext shorter than the tag or a tag that doesn't match is a CipherError.
Plain TypeScript, not constant time. It's here to show how one AES pass can encrypt and authenticate at once. Not for anything real.