The Camellia Algorithm
Camellia is the cipher that stands next to AES in international standards. Learn how this Japanese Feistel design combines byte S-boxes, a linear mixing layer, and key-dependent FL layers to match AES-level security.
Interactive Camellia Encryption
🔐 Camellia Encryption
The Camellia Algorithm
Introduction
Camellia was developed jointly by Mitsubishi Electric and NTT of Japan and published in 2000. It has the same interface as AES: a 128-bit block and a choice of 128, 192, or 256-bit keys. It was designed to offer the same level of security, and it has earned a place in nearly every major standards list that also includes AES.
The name comes from Camellia japonica, a flower known for living a long time. Inside, Camellia is a very different machine from AES. It is a Feistel network like DES, built from byte-sized S-boxes and a simple linear mixing step. That contrast makes it a useful second example of how a modern 128-bit cipher can be built.
Table of Contents
- History and Standardization
- How Camellia Works
- The F-Function and S-Boxes
- The FL and FLINV Layers
- Key Schedule
- A Worked Example
- Python Implementation
- Limitations
- Security Status
- FAQ
- References
History and Standardization
The designers are Kazumaro Aoki, Tetsuya Ichikawa, Masayuki Kanda, Mitsuru Matsui, Shiho Moriai, Junko Nakajima, and Toshio Tokita. Their goal was a cipher that matched Rijndael’s security and could run well in software, in small devices, and in hardware.
International evaluators agreed it met that goal. Camellia was selected by the European NESSIE project and by Japan’s CRYPTREC program, and it was standardized in ISO/IEC 18033-3. The IETF published its algorithm description as RFC 3713, then added profiles for TLS (RFC 5932) and IPsec (RFC 4312). It is available in OpenSSL and in many other libraries.
Camellia is patented, but the holders offer a royalty-free license. That removed the main barrier to its adoption, and it remains a common alternative to AES in protocol specifications.
How Camellia Works
Camellia splits the 128-bit block into two 64-bit halves, D1 and D2. It then runs a Feistel network: 18 rounds for 128-bit keys and 24 rounds for 192 and 256-bit keys.
The whole encryption has this shape:
- Prewhitening. XOR
D1withkw1andD2withkw2. - Feistel rounds. In odd rounds,
D2 ^= F(D1, k). In even rounds,D1 ^= F(D2, k). - FL layer. After every sixth round, apply
FLtoD1andFLINVtoD2. - Postwhitening. XOR
D2withkw3andD1withkw4, then outputD2followed byD1.
The last group of six rounds has no FL layer after it. So the 18-round version has two FL layers, and the 24-round version has three.
Decryption uses the very same procedure with the subkeys in reverse order. The whitening keys swap their positions, the round keys run backward, and the FL keys run backward too. That symmetry means one routine does both jobs, just like DES.
Interactive Visualizer
The visualizer above runs this exact algorithm. Pick a key size and encrypt a block. The log records the two halves after every round, and the FL layers appear as extra rows after rounds 6, 12, and (for longer keys) 18.
The F-Function and S-Boxes
The F-function is where the real mixing happens. It takes a 64-bit half and a 64-bit subkey and works in three stages:
- Key mixing. XOR the half with the subkey.
- Substitution. Split the result into eight bytes and replace each one using an 8-bit S-box.
- Linear mixing. Combine the eight substituted bytes into eight new bytes, where each output byte is the XOR of five or six of the inputs.
Camellia uses four S-boxes, but they are not independent. Only SBOX1 is stored. The other three are derived from it:
SBOX2[x] = SBOX1[x] <<< 1
SBOX3[x] = SBOX1[x] <<< 7
SBOX4[x] = SBOX1[x <<< 1]
Here <<< is an 8-bit rotate left. The eight bytes use the S-boxes in the order 1, 2, 3, 4, 2, 3, 4, 1. The core of SBOX1 is inversion in the finite field GF(2⁸), the same idea behind the AES S-box, wrapped in different affine transformations.
The linear mixing step spreads every S-box output across several bytes of the result. That is what ensures a change in one input byte soon affects the whole half.
The FL and FLINV Layers
A plain Feistel network is regular: every round has the same shape. Regularity helps analysis, but it can also help attackers. Camellia breaks the pattern by inserting two small extra functions every six rounds.
FL(x, k): x1, x2 = x split into two 32-bit halves
x2 = x2 ^ ((x1 & k1) <<< 1)
x1 = x1 ^ (x2 | k2)
FLINV(y, k): y1, y2 = y split into two 32-bit halves
y1 = y1 ^ (y2 | k2)
y2 = y2 ^ ((y1 & k1) <<< 1)
These use AND, OR, and a one-bit rotate, all controlled by key material. FLINV exactly undoes FL when given the same key. The layers add key-dependent nonlinearity between groups of rounds, and they make the cipher harder to model as a simple repeated structure.
Key Schedule
Camellia first builds two 128-bit values, KL and KR. A 128-bit key becomes KL and KR is zero. A 256-bit key splits into KL and KR directly. A 192-bit key uses the first 128 bits as KL, and KR is the last 64 bits followed by their bitwise complement.
Two more 128-bit values come from running the F-function over KL and KR. The first, KA, uses four F-function calls with the constants Σ1 to Σ4. The second, KB, uses two more calls with Σ5 and Σ6, and only longer keys need it. These six constants are “nothing up my sleeve” numbers. Each one is the fractional part of the square root of one of the first six primes (2, 3, 5, 7, 11, and 13), written in hexadecimal, with the leading digit dropped.
Every subkey is then a 64-bit half of KL, KR, KA, or KB rotated left by a fixed amount such as 15, 30, 45, 60, 77, 94, or 111 bits. The RFC lists the exact assignment for each subkey, and the Python code below follows it line by line.
A Worked Example
The test vector from RFC 3713 uses the 128-bit key 0123456789abcdeffedcba9876543210, and the plaintext is the very same value. That coincidence makes the first steps easy to follow.
Key schedule. With a 128-bit key, KL is the key and KR is zero. Four F-function calls produce KA = ae71c3d55ba6bf1d169240a795f89256. The first round key k1 is the left half of KA, which is ae71c3d55ba6bf1d.
Prewhitening. The whitening keys kw1 and kw2 are the two halves of KL. Since the plaintext equals the key, XORing them cancels completely: D1 = 0000000000000000 and D2 = 0000000000000000.
Round 1. With D1 equal to zero, the F-function input is just k1. It returns 77933e474a99fa36, and that becomes the new D2.
After round 6. The state is D1 = 695e2dbc2a7211d1 and D2 = 029d886d6c0dfb08. The first FL layer then produces D1 = 86b8f745aae24ad9 and D2 = ed007390a60dba29.
Result. After all 18 rounds, two FL layers, and postwhitening, the ciphertext is:
67673138549669730857065648eabe43
The same plaintext under the 192-bit key 0123456789abcdeffedcba98765432100011223344556677 gives b4993401b3e996f84ee5cee7d79b09b9. Under the 256-bit key that extends it to 0123456789abcdeffedcba987654321000112233445566778899aabbccddeeff, the result is 9acc237dff16d76c20ef7c919e3a7509. All three match the test data in RFC 3713.
Python Implementation
This is a complete Camellia: the real key schedule, the real S-boxes, the FL layers, and all three key sizes. The SBOX1 table was copied out of the RFC by a script, so no value was typed by hand.
# camellia.py
#
# Camellia: a 128-bit block cipher with 128, 192 or 256-bit keys.
# 18 rounds for 128-bit keys and 24 rounds for 192/256-bit keys, a Feistel
# network with an FL / FLINV layer after every sixth round.
# Follows RFC 3713. Only SBOX1 is stored; the other three S-boxes are derived.
MASK64 = (1 << 64) - 1
MASK128 = (1 << 128) - 1
SIGMA = [
0xA09E667F3BCC908B, 0xB67AE8584CAA73B2, 0xC6EF372FE94F82BE,
0x54FF53A5F1D36F1C, 0x10E527FADE682D1D, 0xB05688C2B3E6C1FD,
]
SBOX1 = [
0x70, 0x82, 0x2c, 0xec, 0xb3, 0x27, 0xc0, 0xe5,
0xe4, 0x85, 0x57, 0x35, 0xea, 0x0c, 0xae, 0x41,
0x23, 0xef, 0x6b, 0x93, 0x45, 0x19, 0xa5, 0x21,
0xed, 0x0e, 0x4f, 0x4e, 0x1d, 0x65, 0x92, 0xbd,
0x86, 0xb8, 0xaf, 0x8f, 0x7c, 0xeb, 0x1f, 0xce,
0x3e, 0x30, 0xdc, 0x5f, 0x5e, 0xc5, 0x0b, 0x1a,
0xa6, 0xe1, 0x39, 0xca, 0xd5, 0x47, 0x5d, 0x3d,
0xd9, 0x01, 0x5a, 0xd6, 0x51, 0x56, 0x6c, 0x4d,
0x8b, 0x0d, 0x9a, 0x66, 0xfb, 0xcc, 0xb0, 0x2d,
0x74, 0x12, 0x2b, 0x20, 0xf0, 0xb1, 0x84, 0x99,
0xdf, 0x4c, 0xcb, 0xc2, 0x34, 0x7e, 0x76, 0x05,
0x6d, 0xb7, 0xa9, 0x31, 0xd1, 0x17, 0x04, 0xd7,
0x14, 0x58, 0x3a, 0x61, 0xde, 0x1b, 0x11, 0x1c,
0x32, 0x0f, 0x9c, 0x16, 0x53, 0x18, 0xf2, 0x22,
0xfe, 0x44, 0xcf, 0xb2, 0xc3, 0xb5, 0x7a, 0x91,
0x24, 0x08, 0xe8, 0xa8, 0x60, 0xfc, 0x69, 0x50,
0xaa, 0xd0, 0xa0, 0x7d, 0xa1, 0x89, 0x62, 0x97,
0x54, 0x5b, 0x1e, 0x95, 0xe0, 0xff, 0x64, 0xd2,
0x10, 0xc4, 0x00, 0x48, 0xa3, 0xf7, 0x75, 0xdb,
0x8a, 0x03, 0xe6, 0xda, 0x09, 0x3f, 0xdd, 0x94,
0x87, 0x5c, 0x83, 0x02, 0xcd, 0x4a, 0x90, 0x33,
0x73, 0x67, 0xf6, 0xf3, 0x9d, 0x7f, 0xbf, 0xe2,
0x52, 0x9b, 0xd8, 0x26, 0xc8, 0x37, 0xc6, 0x3b,
0x81, 0x96, 0x6f, 0x4b, 0x13, 0xbe, 0x63, 0x2e,
0xe9, 0x79, 0xa7, 0x8c, 0x9f, 0x6e, 0xbc, 0x8e,
0x29, 0xf5, 0xf9, 0xb6, 0x2f, 0xfd, 0xb4, 0x59,
0x78, 0x98, 0x06, 0x6a, 0xe7, 0x46, 0x71, 0xba,
0xd4, 0x25, 0xab, 0x42, 0x88, 0xa2, 0x8d, 0xfa,
0x72, 0x07, 0xb9, 0x55, 0xf8, 0xee, 0xac, 0x0a,
0x36, 0x49, 0x2a, 0x68, 0x3c, 0x38, 0xf1, 0xa4,
0x40, 0x28, 0xd3, 0x7b, 0xbb, 0xc9, 0x43, 0xc1,
0x15, 0xe3, 0xad, 0xf4, 0x77, 0xc7, 0x80, 0x9e,
]
def rol8(x, n):
return ((x << n) | (x >> (8 - n))) & 0xFF
SBOX2 = [rol8(v, 1) for v in SBOX1]
SBOX3 = [rol8(v, 7) for v in SBOX1]
SBOX4 = [SBOX1[rol8(x, 1)] for x in range(256)]
def rol128(x, n):
n %= 128
return ((x << n) | (x >> (128 - n))) & MASK128
def rol32(x, n):
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
def f(f_in, ke):
x = f_in ^ ke
t = [(x >> (56 - 8 * i)) & 0xFF for i in range(8)]
t1 = SBOX1[t[0]]
t2 = SBOX2[t[1]]
t3 = SBOX3[t[2]]
t4 = SBOX4[t[3]]
t5 = SBOX2[t[4]]
t6 = SBOX3[t[5]]
t7 = SBOX4[t[6]]
t8 = SBOX1[t[7]]
y = [
t1 ^ t3 ^ t4 ^ t6 ^ t7 ^ t8,
t1 ^ t2 ^ t4 ^ t5 ^ t7 ^ t8,
t1 ^ t2 ^ t3 ^ t5 ^ t6 ^ t8,
t2 ^ t3 ^ t4 ^ t5 ^ t6 ^ t7,
t1 ^ t2 ^ t6 ^ t7 ^ t8,
t2 ^ t3 ^ t5 ^ t7 ^ t8,
t3 ^ t4 ^ t5 ^ t6 ^ t8,
t1 ^ t4 ^ t5 ^ t6 ^ t7,
]
out = 0
for byte in y:
out = (out << 8) | byte
return out
def fl(fl_in, ke):
x1, x2 = fl_in >> 32, fl_in & 0xFFFFFFFF
k1, k2 = ke >> 32, ke & 0xFFFFFFFF
x2 ^= rol32(x1 & k1, 1)
x1 ^= x2 | k2
return (x1 << 32) | x2
def flinv(flinv_in, ke):
y1, y2 = flinv_in >> 32, flinv_in & 0xFFFFFFFF
k1, k2 = ke >> 32, ke & 0xFFFFFFFF
y1 ^= y2 | k2
y2 ^= rol32(y1 & k1, 1)
return (y1 << 32) | y2
def key_schedule(key):
"""Return (kw, k, ke): 4 whitening keys, 18/24 round keys, 4/6 FL keys."""
if len(key) not in (16, 24, 32):
raise ValueError("Camellia keys are 16, 24 or 32 bytes")
big = int.from_bytes(key, "big")
if len(key) == 16:
kl, kr = big, 0
elif len(key) == 24:
kl = big >> 64
kr = ((big & MASK64) << 64) | (~big & MASK64)
else:
kl, kr = big >> 128, big & MASK128
d1, d2 = (kl ^ kr) >> 64, (kl ^ kr) & MASK64
d2 ^= f(d1, SIGMA[0])
d1 ^= f(d2, SIGMA[1])
d1 ^= kl >> 64
d2 ^= kl & MASK64
d2 ^= f(d1, SIGMA[2])
d1 ^= f(d2, SIGMA[3])
ka = (d1 << 64) | d2
d1, d2 = (ka ^ kr) >> 64, (ka ^ kr) & MASK64
d2 ^= f(d1, SIGMA[4])
d1 ^= f(d2, SIGMA[5])
kb = (d1 << 64) | d2
def hi(v, n):
return rol128(v, n) >> 64
def lo(v, n):
return rol128(v, n) & MASK64
if len(key) == 16:
kw = [hi(kl, 0), lo(kl, 0), hi(ka, 111), lo(ka, 111)]
k = [hi(ka, 0), lo(ka, 0), hi(kl, 15), lo(kl, 15), hi(ka, 15), lo(ka, 15),
hi(kl, 45), lo(kl, 45), hi(ka, 45), lo(kl, 60), hi(ka, 60), lo(ka, 60),
hi(kl, 94), lo(kl, 94), hi(ka, 94), lo(ka, 94), hi(kl, 111), lo(kl, 111)]
ke = [hi(ka, 30), lo(ka, 30), hi(kl, 77), lo(kl, 77)]
else:
kw = [hi(kl, 0), lo(kl, 0), hi(kb, 111), lo(kb, 111)]
k = [hi(kb, 0), lo(kb, 0), hi(kr, 15), lo(kr, 15), hi(ka, 15), lo(ka, 15),
hi(kb, 30), lo(kb, 30), hi(kl, 45), lo(kl, 45), hi(ka, 45), lo(ka, 45),
hi(kr, 60), lo(kr, 60), hi(kb, 60), lo(kb, 60), hi(kl, 77), lo(kl, 77),
hi(kr, 94), lo(kr, 94), hi(ka, 94), lo(ka, 94), hi(kl, 111), lo(kl, 111)]
ke = [hi(kr, 30), lo(kr, 30), hi(kl, 60), lo(kl, 60), hi(ka, 77), lo(ka, 77)]
return kw, k, ke
def crypt_block(kw, k, ke, block):
d1 = int.from_bytes(block[:8], "big") ^ kw[0]
d2 = int.from_bytes(block[8:], "big") ^ kw[1]
for i in range(len(k)):
if i % 2 == 0:
d2 ^= f(d1, k[i])
else:
d1 ^= f(d2, k[i])
if i % 6 == 5 and i != len(k) - 1:
n = i // 6
d1 = fl(d1, ke[2 * n])
d2 = flinv(d2, ke[2 * n + 1])
d2 ^= kw[2]
d1 ^= kw[3]
return d2.to_bytes(8, "big") + d1.to_bytes(8, "big")
def reverse_keys(kw, k, ke):
return [kw[2], kw[3], kw[0], kw[1]], k[::-1], ke[::-1]
def encrypt_block(key, block):
return crypt_block(*key_schedule(key), block)
def decrypt_block(key, block):
return crypt_block(*reverse_keys(*key_schedule(key)), block)
if __name__ == "__main__":
key = bytes.fromhex("0123456789abcdeffedcba9876543210")
plaintext = bytes.fromhex("0123456789abcdeffedcba9876543210")
ciphertext = encrypt_block(key, plaintext)
recovered = decrypt_block(key, ciphertext)
print(f"Key: {key.hex()}")
print(f"Plaintext: {plaintext.hex()}")
print(f"Ciphertext: {ciphertext.hex()}")
print(f"Recovered: {recovered.hex()}")
Running it produces this output:
Key: 0123456789abcdeffedcba9876543210
Plaintext: 0123456789abcdeffedcba9876543210
Ciphertext: 67673138549669730857065648eabe43
Recovered: 0123456789abcdeffedcba9876543210
I checked this code against three independent sources before writing it up. It reproduces all three RFC 3713 test vectors, the vectors in the Linux kernel’s test suite, and the output of OpenSSL on 75 random key and block pairs. Those random checks covered 128, 192, and 256-bit keys, and every one was tested in both directions.
For Fun: The Same Cipher in 36 Lines
This is the same spirit as the compact bonus sections elsewhere on this site. It is not for learning the algorithm from. This version squeezes the 200-line implementation above into 36 lines. The tables and many statements share a line, and the F-function, FL layers, and key schedule are written as tight one-liners. It needs no imports and no other files.
MASK64=(1<<64)-1;MASK128=(1<<128)-1;SIGMA=[0xA09E667F3BCC908B,0xB67AE8584CAA73B2,0xC6EF372FE94F82BE,0x54FF53A5F1D36F1C,0x10E527FADE682D1D,0xB05688C2B3E6C1FD];SBOX1=[0x70,0x82,0x2c,0xec,0xb3,0x27,0xc0,0xe5,0xe4,0x85,0x57,0x35,0xea,0x0c,0xae,0x41,0x23,0xef,0x6b,0x93,0x45,0x19,0xa5,0x21,0xed,0x0e,0x4f,0x4e,0x1d,0x65,0x92,0xbd,0x86,0xb8,0xaf,0x8f,0x7c,0xeb,0x1f,0xce,0x3e,0x30,0xdc,0x5f,0x5e,0xc5,0x0b,0x1a,0xa6,0xe1,0x39,0xca,0xd5,0x47,0x5d,0x3d,0xd9,0x01,0x5a,0xd6,0x51,0x56,0x6c,0x4d,0x8b,0x0d,0x9a,0x66,0xfb,0xcc,0xb0,0x2d,0x74,0x12,0x2b,0x20,0xf0,0xb1,0x84,0x99,0xdf,0x4c,0xcb,0xc2,0x34,0x7e,0x76,0x05,0x6d,0xb7,0xa9,0x31,0xd1,0x17,0x04,0xd7,0x14,0x58,0x3a,0x61,0xde,0x1b,0x11,0x1c,0x32,0x0f,0x9c,0x16,0x53,0x18,0xf2,0x22,0xfe,0x44,0xcf,0xb2,0xc3,0xb5,0x7a,0x91,0x24,0x08,0xe8,0xa8,0x60,0xfc,0x69,0x50,0xaa,0xd0,0xa0,0x7d,0xa1,0x89,0x62,0x97,0x54,0x5b,0x1e,0x95,0xe0,0xff,0x64,0xd2,0x10,0xc4,0x00,0x48,0xa3,0xf7,0x75,0xdb,0x8a,0x03,0xe6,0xda,0x09,0x3f,0xdd,0x94,0x87,0x5c,0x83,0x02,0xcd,0x4a,0x90,0x33,0x73,0x67,0xf6,0xf3,0x9d,0x7f,0xbf,0xe2,0x52,0x9b,0xd8,0x26,0xc8,0x37,0xc6,0x3b,0x81,0x96,0x6f,0x4b,0x13,0xbe,0x63,0x2e,0xe9,0x79,0xa7,0x8c,0x9f,0x6e,0xbc,0x8e,0x29,0xf5,0xf9,0xb6,0x2f,0xfd,0xb4,0x59,0x78,0x98,0x06,0x6a,0xe7,0x46,0x71,0xba,0xd4,0x25,0xab,0x42,0x88,0xa2,0x8d,0xfa,0x72,0x07,0xb9,0x55,0xf8,0xee,0xac,0x0a,0x36,0x49,0x2a,0x68,0x3c,0x38,0xf1,0xa4,0x40,0x28,0xd3,0x7b,0xbb,0xc9,0x43,0xc1,0x15,0xe3,0xad,0xf4,0x77,0xc7,0x80,0x9e]
rol8=lambda x,n:((x<<n)|(x>>(8-n)))&0xFF;SBOX2=[rol8(v,1) for v in SBOX1];SBOX3=[rol8(v,7) for v in SBOX1];SBOX4=[SBOX1[rol8(x,1)] for x in range(256)]
rol128=lambda x,n:((x<<(n%128))|(x>>(128-n%128)))&MASK128;rol32=lambda x,n:((x<<n)|(x>>(32-n)))&0xFFFFFFFF
def f(f_in,ke):
x=f_in^ke;t=[(x>>(56-8*i))&0xFF for i in range(8)];t1=SBOX1[t[0]];t2=SBOX2[t[1]];t3=SBOX3[t[2]];t4=SBOX4[t[3]];t5=SBOX2[t[4]];t6=SBOX3[t[5]];t7=SBOX4[t[6]];t8=SBOX1[t[7]];y=[t1^t3^t4^t6^t7^t8,t1^t2^t4^t5^t7^t8,t1^t2^t3^t5^t6^t8,t2^t3^t4^t5^t6^t7,t1^t2^t6^t7^t8,t2^t3^t5^t7^t8,t3^t4^t5^t6^t8,t1^t4^t5^t6^t7];out=0
for byte in y:out=(out<<8)|byte
return out
def fl(fl_in,ke):
x1,x2=fl_in>>32,fl_in&0xFFFFFFFF;k1,k2=ke>>32,ke&0xFFFFFFFF;x2^=rol32(x1&k1,1);x1^=x2|k2;return (x1<<32)|x2
def flinv(flinv_in,ke):
y1,y2=flinv_in>>32,flinv_in&0xFFFFFFFF;k1,k2=ke>>32,ke&0xFFFFFFFF;y1^=y2|k2;y2^=rol32(y1&k1,1);return (y1<<32)|y2
def key_schedule(key):
if len(key) not in (16,24,32):raise ValueError("Camellia keys are 16, 24 or 32 bytes")
big=int.from_bytes(key,"big")
if len(key)==16:kl,kr=big,0
elif len(key)==24:kl=big>>64;kr=((big&MASK64)<<64)|(~big&MASK64)
else:kl,kr=big>>128,big&MASK128
d1,d2=(kl^kr)>>64,(kl^kr)&MASK64;d2^=f(d1,SIGMA[0]);d1^=f(d2,SIGMA[1]);d1^=kl>>64;d2^=kl&MASK64;d2^=f(d1,SIGMA[2]);d1^=f(d2,SIGMA[3]);ka=(d1<<64)|d2;d1,d2=(ka^kr)>>64,(ka^kr)&MASK64;d2^=f(d1,SIGMA[4]);d1^=f(d2,SIGMA[5]);kb=(d1<<64)|d2
hi=lambda v,n:rol128(v,n)>>64;lo=lambda v,n:rol128(v,n)&MASK64
if len(key)==16:
kw=[hi(kl,0),lo(kl,0),hi(ka,111),lo(ka,111)];k=[hi(ka,0),lo(ka,0),hi(kl,15),lo(kl,15),hi(ka,15),lo(ka,15),hi(kl,45),lo(kl,45),hi(ka,45),lo(kl,60),hi(ka,60),lo(ka,60),hi(kl,94),lo(kl,94),hi(ka,94),lo(ka,94),hi(kl,111),lo(kl,111)];ke=[hi(ka,30),lo(ka,30),hi(kl,77),lo(kl,77)]
else:
kw=[hi(kl,0),lo(kl,0),hi(kb,111),lo(kb,111)];k=[hi(kb,0),lo(kb,0),hi(kr,15),lo(kr,15),hi(ka,15),lo(ka,15),hi(kb,30),lo(kb,30),hi(kl,45),lo(kl,45),hi(ka,45),lo(ka,45),hi(kr,60),lo(kr,60),hi(kb,60),lo(kb,60),hi(kl,77),lo(kl,77),hi(kr,94),lo(kr,94),hi(ka,94),lo(ka,94),hi(kl,111),lo(kl,111)];ke=[hi(kr,30),lo(kr,30),hi(kl,60),lo(kl,60),hi(ka,77),lo(ka,77)]
return kw,k,ke
def crypt_block(kw,k,ke,block):
d1=int.from_bytes(block[:8],"big")^kw[0];d2=int.from_bytes(block[8:],"big")^kw[1]
for i in range(len(k)):
if i%2==0:d2^=f(d1,k[i])
else:d1^=f(d2,k[i])
if i%6==5 and i!=len(k)-1:
n=i//6;d1=fl(d1,ke[2*n]);d2=flinv(d2,ke[2*n+1])
d2^=kw[2];d1^=kw[3];return d2.to_bytes(8,"big")+d1.to_bytes(8,"big")
def reverse_keys(kw,k,ke):return [kw[2],kw[3],kw[0],kw[1]],k[::-1],ke[::-1]
def encrypt_block(key,block):return crypt_block(*key_schedule(key),block)
def decrypt_block(key,block):return crypt_block(*reverse_keys(*key_schedule(key)),block)
if __name__=="__main__":
key=bytes.fromhex("0123456789abcdeffedcba9876543210");plaintext=bytes.fromhex("0123456789abcdeffedcba9876543210");ciphertext=encrypt_block(key,plaintext);recovered=decrypt_block(key,ciphertext);print(f"Key: {key.hex()}");print(f"Plaintext: {plaintext.hex()}");print(f"Ciphertext: {ciphertext.hex()}");print(f"Recovered: {recovered.hex()}")
Running it prints the same four lines as the readable version, including the RFC 3713 ciphertext:
Key: 0123456789abcdeffedcba9876543210
Plaintext: 0123456789abcdeffedcba9876543210
Ciphertext: 67673138549669730857065648eabe43
Recovered: 0123456789abcdeffedcba9876543210
I checked it against the readable code on 300 random keys and blocks, 100 for each key size. Encryption matched every time, and decryption recovered every block. It also reproduces all three RFC 3713 ciphertexts, for the 128, 192, and 256-bit keys.
Limitations
This is a faithful teaching version of the cipher, not a production one:
- Single 128-bit block only. There is no mode of operation for longer messages and no padding for partial blocks.
- Table lookups depend on secret data. The S-box lookups use secret-dependent indexes, so a real deployment must worry about cache-timing leaks that this straightforward code does not address.
- No side-channel hardening. It has no protection against power, fault, or timing attacks.
- Not for real data. Use a vetted library for anything that matters.
Security Status
No attack on the full cipher is known. The designers report that Camellia has no differential or linear characteristics holding with probability above 2⁻¹²⁸, which makes those attacks extremely unlikely against the full 18 rounds.
Published cryptanalysis reaches only reduced versions. There are impossible differential attacks on 12 rounds of Camellia-192 and 14 rounds of Camellia-256, and truncated differential attacks on 7 and 8 rounds of a modified version. Those are far short of the full 18 or 24 rounds. Camellia is generally regarded as having a security level comparable to AES.
FAQ
Is Camellia as secure as AES?
By every public measure, yes. Both ciphers have a 128-bit block and the same three key sizes, and no practical attack exists on either. Camellia has been analyzed for decades by independent evaluation programs.
Why use Camellia instead of AES?
Usually because a standard or a regional requirement asks for it. AES has hardware support in most processors, which makes it faster, so it is the better default. Camellia is a solid choice when you need an alternative.
Is Camellia a Feistel cipher or a substitution-permutation network?
It is a Feistel cipher. Each round updates only one half of the block, as in DES. AES is the substitution-permutation design in this pair.
What do the FL and FLINV layers do?
They add key-dependent nonlinear steps between groups of rounds. That breaks up the otherwise regular structure of the Feistel network, which makes certain structural attacks harder.
Where is Camellia used?
It appears in TLS cipher suites, IPsec, and OpenSSL, and in libraries such as GnuTLS, mbed TLS, and Crypto++. Disk-encryption tools like VeraCrypt have also supported it, and it was once built into the Firefox browser.
References
-
Aoki, K., Ichikawa, T., Kanda, M., Matsui, M., Moriai, S., Nakajima, J., and Tokita, T. “Camellia: A 128-Bit Block Cipher Suitable for Multiple Platforms - Design and Analysis.” Selected Areas in Cryptography (SAC) 2000.
-
Matsui, M., Nakajima, J., and Moriai, S. “A Description of the Camellia Encryption Algorithm.” RFC 3713, 2004. Available at: https://www.rfc-editor.org/rfc/rfc3713
-
Kato, A., Kanda, M., and Kanno, S. “Camellia Cipher Suites for TLS.” RFC 5932, 2010. Available at: https://www.rfc-editor.org/rfc/rfc5932
-
Wikipedia. “Camellia (cipher).” Available at: https://en.wikipedia.org/wiki/Camellia_(cipher)