SM4 (ECB)
SM4 (ECB)
China's standard, four words in a queue and a key schedule that is the cipher again.
- Keyspace
- 2^128
- Decode
- same options back
- Works on
- UTF-8 or hex, hex out
- Family
- 10 in feistel
Options
Access
- Create
create("sm4") - CLI
ciphers encode sm4 'ATTACK AT DAWN' --key 0123456789abcdeffedcba9876543210 - Tryplayground with the sample above
- Kindes, desx, triple-des, triple-des-cbc +5
SM4 is China's block cipher. It started life as SMS4, the cipher inside WAPI, China's own Wi-Fi security standard from 2003. China pitched it for IEEE 802.11i too. It didn't get in. So it went its own way: published on its own in 2006, renamed SM4 in 2012, a national standard as GB/T 32907-2016 and an ISO one a year later. TLS 1.3 has suites for it since RFC 8998, and OpenSSL ships sm4-ecb. One key size, 128 bits. One block size, 16 bytes. No menu to pick from.
Here it runs on its own, in ECB, like camellia and aria. UTF-8 text in, PKCS#7 padding, hex out.
const sm4 = create("sm4");
const key = "0123456789abcdeffedcba9876543210";
sm4.encode("ATTACK AT DAWN", { key }).text; // "47dea04afc3c7e332fcac11987a2ec6e"
sm4.decode("47dea04afc3c7e332fcac11987a2ec6e", { key }).text; // "ATTACK AT DAWN"
Four words in a queue
Forget halves. SM4 cuts the block into four 32-bit words and lines them up. Each round takes the three newest, XORs them together with a round key and pushes the result through T. That result, XORed with the oldest word, joins the queue at the back. The oldest one leaves. Feistel, just unbalanced: three words drive one.
T is two steps. First tau, the S-box on each of the four bytes. Then L, which XORs the word with copies of itself rotated by 2, 10, 18 and 24 bits. That's the whole round. No tables beyond one S-box, no multiplication.
After 32 rounds the last four words come out in reverse order. Why reverse them? Because then decryption is the same 32 rounds with the round keys backwards. Same code, both ways.
The key schedule is the cipher again
The key goes in as four words too, each XORed with a family constant FK first. Then the same queue runs 32 times. Two things change. L gets lighter, rotations by 13 and 23 only. And a constant CK joins the XOR every round. Every new word it makes is a round key.
CK looks like a table of 32 random words. It isn't. Byte j of CK_i is (4i + j) × 7 mod 256, so the file computes it on the spot. Thirty-two words nobody has to copy by hand.
The S-box is the one thing that's stored, as the standard prints it. The draft says Lagrange interpolation turns it into a polynomial of degree 254 with 255 terms. Good luck solving that one by hand.
Checked against
GB/T 32907-2016 example 1 both ways, and examples 4 and 5 of the draft-ribose-cfrg-sm4 appendix. Example 2 of the standard too: one block encrypted a million times under the same key. That takes about a second here, so it runs with the rest of the suite. The ECB examples from the draft, two blocks each, check whole messages. Then every single-bit vector, one bit set in the key over a zero block or in the block under a zero key. That's 256 ciphertexts, checked by digest against OpenSSL. UTF-8 text with padding comes from openssl enc -sm4-ecb.
How strong is it
The draft lists a decade of cryptanalysis and no practical attack on the full cipher. The best ones in its survey reach 24 of the 32 rounds. The block is 128 bits, so Sweet32 doesn't apply either.
ECB is still ECB, though. Thirty-two As are two equal blocks, and they come out as two equal blocks:
sm4.encode("A".repeat(32), { key }).text;
// 99ce75c0ca2949d3eb87bd2d831f3510 99ce75c0ca2949d3eb87bd2d831f3510 002a8a4efa863ccad024ac0300bb40d2
Spaces added to show the blocks. The last one is only padding.
A key that isn't 32 hex digits is an InvalidOptionError, a missing one a MissingOptionError. On decode, ciphertext that isn't whole 16-byte blocks of hex is a CipherError. A wrong key almost always breaks the padding, and that's a CipherError too. So are decrypted bytes that aren't UTF-8.
Plain TypeScript, not constant time. Puzzles and learning, not secrets.