Triple DES (CBC)
- create
- create("triple-des-cbc")
- family
- Feistel
- options
- --key --iv
- self-inverse
- no
- keyspace
- 2^112 or 2^168 keys (56 bits of every 64-bit DES key)
- try it
- ciphers triple-des-cbc "ATTACK AT DAWN" --key 0123456789abcdef23456789abcdef01456789abcdef0123 --iv 0001020304050607
This is the Triple DES you actually run into. Old PKCS#12 files, the 3DES_EDE_CBC suites in TLS and 3des-cbc in SSH all use it in CBC, not ECB. The block function is the same as in Triple DES, DES encrypt, decrypt, encrypt under K1, K2 and K3. The chaining is the same as in AES-CBC, from NIST SP 800-38A. Each plaintext block is XORed with the ciphertext block before it, the first one with the IV, and then encrypted.
The key is 32 or 48 hex digits, as in ECB, and parity bits still don't matter. iv is required and it's 16 hex digits, not 32. A Triple DES block is 8 bytes, so an IV copied over from AES is an InvalidOptionError, not a silent cut. Text goes in as UTF-8 with PKCS#7 padding, ciphertext comes out as lowercase hex.
const cbc = create("triple-des-cbc");
const key = "0123456789abcdef23456789abcdef01456789abcdef0123";
const iv = "0001020304050607";
cbc.encode("ATTACK AT DAWN", { key, iv }).text; // "00b5ad5bd633b1c564e7e3a858c7d8fb"
cbc.decode("00b5ad5bd633b1c564e7e3a858c7d8fb", { key, iv }).text; // "ATTACK AT DAWN"
OpenSSL agrees. -des-ede3-cbc takes the 48-digit key, -des-ede-cbc the 32-digit one:
printf %s "ATTACK AT DAWN" | openssl enc -des-ede3-cbc -K 0123456789abcdef23456789abcdef01456789abcdef0123 -iv 0001020304050607 | xxd -p
The tests check the mode against the multi-block CBC vectors from NIST's CAVP, two-key and three-key.
What the chain fixes
Sixteen As, two equal blocks. ECB gives two equal blocks back, 9f6ca443ceebf424 twice. CBC doesn't:
cbc.encode("A".repeat(16), { key, iv }).text;
// c93fea87be35b90e 9429d1e9334f7bc4 5917c5548017b5ab
Spaces added to show the blocks, and the last one is padding. Nothing repeats, because the second block was mixed with the first one's ciphertext before DES saw it. Same message, same key and same IV still give the same ciphertext, though. So a new IV for every message.
What it doesn't fix
On decryption the IV gets XORed in after Triple DES. Change the IV and you change the first block of plaintext, bit for bit, no key needed. With 8-byte blocks the first block is ATTACK A:
cbc.decode("00b5ad5bd633b1c564e7e3a858c7d8fb", { key, iv: "05101007090a0607" }).text;
// "DEFEND AT DAWN"
That IV is the old one XORed with ATTACK and DEFEND at bytes 0 to 5. Same bit flipping as in AES-CBC, and the same padding oracle if an error gives away bad padding.
The small block causes one more problem. 64 bits means ciphertext blocks start repeating after around 2^32 blocks, about 32 GB under one key. In CBC, two equal ciphertext blocks give away the XOR of two plaintext blocks. That's Sweet32 from 2016, and a big part of why NIST disallowed Triple DES for encryption after 2023. Nobody's pushing 32 GB through this implementation, it's far too slow for that.
A key that isn't 32 or 48 hex digits is an InvalidOptionError, and so is an iv that isn't 16. A missing key or IV is a MissingOptionError. On decode, hex that isn't whole 8-byte blocks is a CipherError. So is a wrong key, since the padding almost never survives it, and so are decrypted bytes that aren't UTF-8. A wrong IV garbles only the first block, and those bytes are almost never UTF-8. But an IV that's only a little off, like the one above, decodes without a word.
No integrity check, plain TypeScript over bit arrays, not constant time. For puzzles and legacy files. Don't protect anything real with it.
AES (OCB)
AES in OCB mode, RFC 7253. One AES call per block encrypts and authenticates at once, and decoding refuses anything that was changed. UTF-8 text in, hex out, text bytes plus the tag.
Rijndael (ECB)
The cipher AES was cut from, with the wider blocks it lost on the way. Block and key each 128 to 256 bits, UTF-8 text in, hex out.