Ciphers

SM4 (ECB)

China's standard block cipher from GB/T 32907-2016. Thirty-two rounds over four 32-bit words and a 128-bit key. ECB with PKCS#7 padding.
IDsm463 / 72block · feistel
Cipher / Feistel

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
required 1 / 2

Options

keystring
required
32 hex digits, a 128-bit key
bytesstring
default text
What the plain side is: text for UTF-8 text, or hex to read and write hex there, for bytes that are not text

Access

Createcreate("sm4")
CLIciphers 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.

ts
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:

ts
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.