Skip to main content
Block Ciphers Advanced

The ARIA Algorithm

ARIA is South Korea's national block cipher, a close cousin of AES that shares its S-box but mixes bytes with a different, self-inverse diffusion layer. Learn how its alternating rounds, key schedule, and 1/π constants work.

PL
Pashalis Laoutaris
October 2, 2026
17 min read

Interactive ARIA Encryption

🔐 ARIA Encryption

6
Enter text and click a button to start!
–

The ARIA Algorithm

Introduction

ARIA is the national standard block cipher of South Korea. Korean cryptographers developed it in 2003, and it has the same outward shape as AES: a 128-bit block with 128, 192, or 256-bit keys. It also borrows heavily from AES’s ideas. It is a substitution-permutation network, and one of its S-boxes is exactly the AES S-box.

The difference is in how it mixes bytes. AES uses a few simple byte shuffles and a matrix multiplication. ARIA uses a single dense mixing layer that XORs seven bytes into every output byte, and that layer undoes itself when applied twice. That small property shapes the whole design, including how decryption keys are made.

Table of Contents

History and Standardization

A large group of Korean researchers designed ARIA in 2003. It was established as a Korean standard in 2004 under the name KS X 1213:2004, and it has been widely used in Korea, especially for government-to-public services. ARIA was added to the PKCS #11 standard in 2007.

The IETF has published several documents about it. RFC 5794 describes the algorithm itself. RFC 6209 defines ARIA cipher suites for TLS, and RFC 8269 covers its use in SRTP, the secure real-time transport protocol.

How ARIA Works

ARIA treats the block as sixteen bytes and runs 12, 14, or 16 rounds, depending on whether the key has 128, 192, or 256 bits. It alternates between two kinds of round, an odd round and an even round:

FO(D, key) = A(SL1(D ^ key))     used in rounds 1, 3, 5, ...
FE(D, key) = A(SL2(D ^ key))     used in rounds 2, 4, 6, ...

Each round XORs in a 128-bit round key, applies a substitution layer (SL1 or SL2), and then a diffusion layer A. The final round is different. It XORs a round key, applies SL2, and finishes with a second key addition instead of the diffusion layer. That is why a 12-round cipher needs 13 round keys.

Interactive Visualizer

The visualizer above runs this exact algorithm. Pick a key size and encrypt a block. Each row of the log shows the sixteen state bytes after a round, grouped into four columns of four bytes. The note tells you whether the round was odd or even.

The S-Boxes

ARIA uses four S-boxes, SB1 to SB4, each mapping one byte to one byte. They come in two inverse pairs. SB3 is the inverse of SB1, and SB4 is the inverse of SB2. The first, SB1, is the same S-box that AES uses. For example, SB1(0x23) = 0x26 and SB4(0xef) = 0xd3.

The two substitution layers apply the boxes to the sixteen bytes in a repeating pattern:

SL1 uses SB1, SB2, SB3, SB4, SB1, SB2, SB3, SB4, ... (byte 0 to byte 15)
SL2 uses SB3, SB4, SB1, SB2, SB3, SB4, SB1, SB2, ... (byte 0 to byte 15)

Because each box in SL2 is the inverse of the box in the same position of SL1, the layer SL2 is exactly the inverse of SL1. That fact is what makes the decryption trick below work.

The Diffusion Layer

The diffusion layer A turns sixteen input bytes into sixteen output bytes. Each output byte is the XOR of exactly seven input bytes. Here are the first two of the sixteen equations:

y0 = x3 ^ x4 ^ x6 ^ x8 ^ x9 ^ x13 ^ x14
y1 = x2 ^ x5 ^ x7 ^ x8 ^ x9 ^ x12 ^ x15

The full list has sixteen such equations. The RFC prints all of them, and the Python code below stores them as a table of index lists. The layer has a striking property: A is an involution. Applying it twice returns the original input, so A(A(x)) = x. A single change to one input byte reaches seven output bytes, and after a couple of rounds every byte is affected. The layer also has a branch number of 8. That means the active bytes at the input and the active bytes at the output always add up to at least eight. I confirmed this by checking all 65,535 nonzero patterns of active bytes against the equations in the Python code below.

Key Schedule

ARIA turns the master key into 13, 15, or 17 round keys. The first step splits the key into two 128-bit halves, KL and KR. A shorter key is padded with zeros on the right, so a 128-bit key has KR = 0.

Next, ARIA runs a small three-round Feistel network using its own round functions to produce four 128-bit values:

W0 = KL
W1 = FO(W0, CK1) ^ KR
W2 = FE(W1, CK2) ^ W0
W3 = FO(W2, CK3) ^ W1

The constants CK1, CK2, and CK3 are chosen from three fixed 128-bit values C1, C2, and C3. These are nothing-up-my-sleeve numbers: together they are the first 384 bits of the fractional part of 1/π. A 128-bit key uses them in the order C1, C2, C3. A 192-bit key uses C2, C3, C1, and a 256-bit key uses C3, C1, C2.

Every round key is then a combination of two W values, one of them rotated:

ek1  = W0 ^ (W1 >>> 19)       ek5  = W0 ^ (W1 >>> 31)
ek2  = W1 ^ (W2 >>> 19)       ek6  = W1 ^ (W2 >>> 31)
ek3  = W2 ^ (W3 >>> 19)       ek7  = W2 ^ (W3 >>> 31)
ek4  = (W0 >>> 19) ^ W3       ek8  = (W0 >>> 31) ^ W3

The pattern continues with left rotations by 61 and 31 bits for ek9 to ek16, and ek17 = W0 ^ (W1 <<< 19). Here >>> is a right rotation and <<< is a left rotation of the 128-bit value.

Decryption

Decryption reuses the encryption procedure with a different set of round keys. The decryption keys come from the encryption keys in reverse order, with the diffusion layer A applied to every key except the first and last:

dk1 = ek(n+1),  dk2 = A(ek n),  dk3 = A(ek(n-1)),  ...,  dk n = A(ek2),  dk(n+1) = ek1

This works because of the two inverse properties. SL2 undoes SL1, and A undoes itself. When encryption’s steps are run backward, each key addition lands on the other side of a diffusion layer, and passing the key through A compensates. The result is that a single routine performs both encryption and decryption.

A Worked Example

This is the 128-bit example from RFC 5794. The key is 000102030405060708090a0b0c0d0e0f and the plaintext is 00112233445566778899aabbccddeeff.

Key schedule. The four key-schedule values come out as:

W0 = 000102030405060708090a0b0c0d0e0f
W1 = 2afbea741e1746dd55c63ba1afcea0a5
W2 = 7c8578018bb127e02dfe4e78c288e33c
W3 = 6785b52b74da46bf181054082763ff6d

The first round key is ek1 = d415a75c794b85c5e0d2a0b3cb793bf6, and the last, ek13, is 0f0aa16daee61bd7dfee5a599970fb35.

Round 1 (odd). XOR the plaintext with ek1, which gives d404856f3d1ee3b2684b0a0807a4d509. The substitution layer SL1 turns that into 489467d927594dd54595a350c5a9b539. The diffusion layer then produces the state after round 1:

P1 = 7fc7f12befd0a0791de87fa96b469f52

Round 2 (even). The same steps with ek2 and SL2 give P2 = ac8de17e49f7c5117618993162b189e9.

Round 12. After round 11 the state is P11 = e7e0d2457ed73d23d481424095afdca0. The last round XORs ek12 = 8684946a155be77ef810744847e35fad, applies SL2, and XORs ek13. After that last key addition, the ciphertext is:

d718fbd6ab644c739da95f3be6451778

With the 192-bit key 000102...1617 the same plaintext gives 26449c1805dbe7aa25a468ce263a9e79, and with the 256-bit key 000102...1e1f it gives f92bd7c79fb72e2f2b8f80c1972d24fc. All three match the RFC. The RFC also prints every intermediate value shown above.

Python Implementation

This is a complete ARIA: the real S-boxes, the real diffusion layer, the real key schedule with the 1/π constants, and decryption through transformed round keys. SB1 and SB2 were copied out of the RFC by a script, and SB3 and SB4 are computed as their inverses.

# aria.py
#
# ARIA, the Korean standard block cipher (KS X 1213, RFC 5794).
# A 128-bit block, 128/192/256-bit keys, and 12/14/16 rounds. Each round
# is substitution (two alternating S-box layers), then a diffusion layer
# that XORs seven of the sixteen state bytes into each output byte.
# SB1 and SB2 are copied from the RFC; SB3 and SB4 are their inverses.

SB1 = [
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
    0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
    0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
    0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
    0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
    0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
    0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
    0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
    0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
    0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
    0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
    0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
    0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
    0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
    0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
]

SB2 = [
    0xe2, 0x4e, 0x54, 0xfc, 0x94, 0xc2, 0x4a, 0xcc,
    0x62, 0x0d, 0x6a, 0x46, 0x3c, 0x4d, 0x8b, 0xd1,
    0x5e, 0xfa, 0x64, 0xcb, 0xb4, 0x97, 0xbe, 0x2b,
    0xbc, 0x77, 0x2e, 0x03, 0xd3, 0x19, 0x59, 0xc1,
    0x1d, 0x06, 0x41, 0x6b, 0x55, 0xf0, 0x99, 0x69,
    0xea, 0x9c, 0x18, 0xae, 0x63, 0xdf, 0xe7, 0xbb,
    0x00, 0x73, 0x66, 0xfb, 0x96, 0x4c, 0x85, 0xe4,
    0x3a, 0x09, 0x45, 0xaa, 0x0f, 0xee, 0x10, 0xeb,
    0x2d, 0x7f, 0xf4, 0x29, 0xac, 0xcf, 0xad, 0x91,
    0x8d, 0x78, 0xc8, 0x95, 0xf9, 0x2f, 0xce, 0xcd,
    0x08, 0x7a, 0x88, 0x38, 0x5c, 0x83, 0x2a, 0x28,
    0x47, 0xdb, 0xb8, 0xc7, 0x93, 0xa4, 0x12, 0x53,
    0xff, 0x87, 0x0e, 0x31, 0x36, 0x21, 0x58, 0x48,
    0x01, 0x8e, 0x37, 0x74, 0x32, 0xca, 0xe9, 0xb1,
    0xb7, 0xab, 0x0c, 0xd7, 0xc4, 0x56, 0x42, 0x26,
    0x07, 0x98, 0x60, 0xd9, 0xb6, 0xb9, 0x11, 0x40,
    0xec, 0x20, 0x8c, 0xbd, 0xa0, 0xc9, 0x84, 0x04,
    0x49, 0x23, 0xf1, 0x4f, 0x50, 0x1f, 0x13, 0xdc,
    0xd8, 0xc0, 0x9e, 0x57, 0xe3, 0xc3, 0x7b, 0x65,
    0x3b, 0x02, 0x8f, 0x3e, 0xe8, 0x25, 0x92, 0xe5,
    0x15, 0xdd, 0xfd, 0x17, 0xa9, 0xbf, 0xd4, 0x9a,
    0x7e, 0xc5, 0x39, 0x67, 0xfe, 0x76, 0x9d, 0x43,
    0xa7, 0xe1, 0xd0, 0xf5, 0x68, 0xf2, 0x1b, 0x34,
    0x70, 0x05, 0xa3, 0x8a, 0xd5, 0x79, 0x86, 0xa8,
    0x30, 0xc6, 0x51, 0x4b, 0x1e, 0xa6, 0x27, 0xf6,
    0x35, 0xd2, 0x6e, 0x24, 0x16, 0x82, 0x5f, 0xda,
    0xe6, 0x75, 0xa2, 0xef, 0x2c, 0xb2, 0x1c, 0x9f,
    0x5d, 0x6f, 0x80, 0x0a, 0x72, 0x44, 0x9b, 0x6c,
    0x90, 0x0b, 0x5b, 0x33, 0x7d, 0x5a, 0x52, 0xf3,
    0x61, 0xa1, 0xf7, 0xb0, 0xd6, 0x3f, 0x7c, 0x6d,
    0xed, 0x14, 0xe0, 0xa5, 0x3d, 0x22, 0xb3, 0xf8,
    0x89, 0xde, 0x71, 0x1a, 0xaf, 0xba, 0xb5, 0x81,
]

SB3 = [SB1.index(v) for v in range(256)]
SB4 = [SB2.index(v) for v in range(256)]

# Output byte i of the diffusion layer XORs these seven input bytes.
DIFFUSION = [
    [3, 4, 6, 8, 9, 13, 14],
    [2, 5, 7, 8, 9, 12, 15],
    [1, 4, 6, 10, 11, 12, 15],
    [0, 5, 7, 10, 11, 13, 14],
    [0, 2, 5, 8, 11, 14, 15],
    [1, 3, 4, 9, 10, 14, 15],
    [0, 2, 7, 9, 10, 12, 13],
    [1, 3, 6, 8, 11, 12, 13],
    [0, 1, 4, 7, 10, 13, 15],
    [0, 1, 5, 6, 11, 12, 14],
    [2, 3, 5, 6, 8, 13, 15],
    [2, 3, 4, 7, 9, 12, 14],
    [1, 2, 6, 7, 9, 11, 12],
    [0, 3, 6, 7, 8, 10, 13],
    [0, 3, 4, 5, 9, 11, 14],
    [1, 2, 4, 5, 8, 10, 15],
]

# First 384 bits of the fractional part of 1/pi.
C = [
    0x517cc1b727220a94fe13abe8fa9a6ee0,
    0x6db14acc9e21c820ff28b1d5ef5de2b0,
    0xdb92371d2126e9700324977504e8c90e
]

MASK128 = (1 << 128) - 1


def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))


def sl1(x):
    boxes = (SB1, SB2, SB3, SB4)
    return bytes(boxes[i % 4][v] for i, v in enumerate(x))


def sl2(x):
    boxes = (SB3, SB4, SB1, SB2)
    return bytes(boxes[i % 4][v] for i, v in enumerate(x))


def diffuse(x):
    out = []
    for taps in DIFFUSION:
        v = 0
        for t in taps:
            v ^= x[t]
        out.append(v)
    return bytes(out)


def fo(d, rk):
    return diffuse(sl1(xor(d, rk)))


def fe(d, rk):
    return diffuse(sl2(xor(d, rk)))


def rotl(x, n):
    n %= 128
    return ((x << n) | (x >> (128 - n))) & MASK128


def rotr(x, n):
    return rotl(x, 128 - n)


def to_bytes(x):
    return x.to_bytes(16, "big")


def encryption_keys(key):
    """Return the 13, 15 or 17 round keys ek1..ek(n+1) as 16-byte strings."""
    if len(key) not in (16, 24, 32):
        raise ValueError("ARIA keys are 16, 24 or 32 bytes")
    padded = key + bytes(32 - len(key))
    kl, kr = padded[:16], padded[16:]
    shift = {16: 0, 24: 1, 32: 2}[len(key)]
    ck1, ck2, ck3 = (to_bytes(C[(shift + i) % 3]) for i in range(3))
    w0 = kl
    w1 = xor(fo(w0, ck1), kr)
    w2 = xor(fe(w1, ck2), w0)
    w3 = xor(fo(w2, ck3), w1)
    w = [int.from_bytes(v, "big") for v in (w0, w1, w2, w3)]
    ek = []
    for amount, left in ((19, False), (31, False), (61, True), (31, True)):
        rot = rotl if left else rotr
        for i in range(4):
            if i < 3:
                ek.append(w[i] ^ rot(w[i + 1], amount))
            else:
                ek.append(rot(w[0], amount) ^ w[3])
    ek.append(w[0] ^ rotl(w[1], 19))
    rounds = {16: 12, 24: 14, 32: 16}[len(key)]
    return [to_bytes(v) for v in ek[:rounds + 1]]


def decryption_keys(ek):
    n = len(ek) - 1
    return [ek[n]] + [diffuse(ek[n - i]) for i in range(1, n)] + [ek[0]]


def crypt_block(keys, block):
    n = len(keys) - 1
    p = block
    for i in range(n - 1):
        p = fo(p, keys[i]) if i % 2 == 0 else fe(p, keys[i])
    return xor(sl2(xor(p, keys[n - 1])), keys[n])


def encrypt_block(key, block):
    return crypt_block(encryption_keys(key), block)


def decrypt_block(key, block):
    return crypt_block(decryption_keys(encryption_keys(key)), block)


if __name__ == "__main__":
    key = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
    plaintext = bytes.fromhex("00112233445566778899aabbccddeeff")

    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:        000102030405060708090a0b0c0d0e0f
Plaintext:  00112233445566778899aabbccddeeff
Ciphertext: d718fbd6ab644c739da95f3be6451778
Recovered:  00112233445566778899aabbccddeeff

I checked this code four ways before writing it up. It matches every intermediate value in the RFC’s 128-bit example, which means W0 to W3, all 13 round keys, and all 11 round outputs. It reproduces the RFC’s 192-bit and 256-bit ciphertexts. It matches the vectors in the Linux kernel’s test suite, and it agrees with OpenSSL on 75 random key and block pairs, covering all three key sizes in both directions. I also confirmed that the three constants really are the first 384 bits of the fractional part of 1/π.

One detail helped catch mistakes. In the RFC’s printed SB4 table, one entry appears as a single digit, 9, where the value is 09. Because SB3 and SB4 are exact inverses of SB1 and SB2, the code computes them instead of copying them, and a script confirmed that the RFC’s tables obey the inverse relationship.

For Fun: The Whole Cipher in 3 Lines

This is the same spirit as the golfed sections elsewhere on this site. It is not for learning the algorithm from. The version below packs all of ARIA into three lines. Line 1 holds the four S-boxes, the diffusion table, the constants, and the layer functions. Lines 2 and 3 hold the key schedule, decryption keys, and block encryption and decryption, and the end of line 3 runs the RFC example. It needs no imports and no other files.

SB1=[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];SB2=[0xe2,0x4e,0x54,0xfc,0x94,0xc2,0x4a,0xcc,0x62,0x0d,0x6a,0x46,0x3c,0x4d,0x8b,0xd1,0x5e,0xfa,0x64,0xcb,0xb4,0x97,0xbe,0x2b,0xbc,0x77,0x2e,0x03,0xd3,0x19,0x59,0xc1,0x1d,0x06,0x41,0x6b,0x55,0xf0,0x99,0x69,0xea,0x9c,0x18,0xae,0x63,0xdf,0xe7,0xbb,0x00,0x73,0x66,0xfb,0x96,0x4c,0x85,0xe4,0x3a,0x09,0x45,0xaa,0x0f,0xee,0x10,0xeb,0x2d,0x7f,0xf4,0x29,0xac,0xcf,0xad,0x91,0x8d,0x78,0xc8,0x95,0xf9,0x2f,0xce,0xcd,0x08,0x7a,0x88,0x38,0x5c,0x83,0x2a,0x28,0x47,0xdb,0xb8,0xc7,0x93,0xa4,0x12,0x53,0xff,0x87,0x0e,0x31,0x36,0x21,0x58,0x48,0x01,0x8e,0x37,0x74,0x32,0xca,0xe9,0xb1,0xb7,0xab,0x0c,0xd7,0xc4,0x56,0x42,0x26,0x07,0x98,0x60,0xd9,0xb6,0xb9,0x11,0x40,0xec,0x20,0x8c,0xbd,0xa0,0xc9,0x84,0x04,0x49,0x23,0xf1,0x4f,0x50,0x1f,0x13,0xdc,0xd8,0xc0,0x9e,0x57,0xe3,0xc3,0x7b,0x65,0x3b,0x02,0x8f,0x3e,0xe8,0x25,0x92,0xe5,0x15,0xdd,0xfd,0x17,0xa9,0xbf,0xd4,0x9a,0x7e,0xc5,0x39,0x67,0xfe,0x76,0x9d,0x43,0xa7,0xe1,0xd0,0xf5,0x68,0xf2,0x1b,0x34,0x70,0x05,0xa3,0x8a,0xd5,0x79,0x86,0xa8,0x30,0xc6,0x51,0x4b,0x1e,0xa6,0x27,0xf6,0x35,0xd2,0x6e,0x24,0x16,0x82,0x5f,0xda,0xe6,0x75,0xa2,0xef,0x2c,0xb2,0x1c,0x9f,0x5d,0x6f,0x80,0x0a,0x72,0x44,0x9b,0x6c,0x90,0x0b,0x5b,0x33,0x7d,0x5a,0x52,0xf3,0x61,0xa1,0xf7,0xb0,0xd6,0x3f,0x7c,0x6d,0xed,0x14,0xe0,0xa5,0x3d,0x22,0xb3,0xf8,0x89,0xde,0x71,0x1a,0xaf,0xba,0xb5,0x81];SB3=[SB1.index(v) for v in range(256)];SB4=[SB2.index(v) for v in range(256)];DIFFUSION=[[3,4,6,8,9,13,14],[2,5,7,8,9,12,15],[1,4,6,10,11,12,15],[0,5,7,10,11,13,14],[0,2,5,8,11,14,15],[1,3,4,9,10,14,15],[0,2,7,9,10,12,13],[1,3,6,8,11,12,13],[0,1,4,7,10,13,15],[0,1,5,6,11,12,14],[2,3,5,6,8,13,15],[2,3,4,7,9,12,14],[1,2,6,7,9,11,12],[0,3,6,7,8,10,13],[0,3,4,5,9,11,14],[1,2,4,5,8,10,15]];C=[0x517cc1b727220a94fe13abe8fa9a6ee0,0x6db14acc9e21c820ff28b1d5ef5de2b0,0xdb92371d2126e9700324977504e8c90e];M=(1<<128)-1;R=__import__('functools').reduce;xor=lambda a,b:bytes(x^y for x,y in zip(a,b));sl1=lambda x:bytes((SB1,SB2,SB3,SB4)[i%4][v] for i,v in enumerate(x));sl2=lambda x:bytes((SB3,SB4,SB1,SB2)[i%4][v] for i,v in enumerate(x));diffuse=lambda x:bytes(R(int.__xor__,(x[t] for t in taps),0) for taps in DIFFUSION);fo=lambda d,rk:diffuse(sl1(xor(d,rk)));fe=lambda d,rk:diffuse(sl2(xor(d,rk)));rotl=lambda x,n:((x<<(n%128))|(x>>(128-n%128)))&M;rotr=lambda x,n:rotl(x,128-n);tb=lambda x:x.to_bytes(16,'big')
def encryption_keys(key): p=key+bytes(32-len(key));s={16:0,24:1,32:2}[len(key)];c=[tb(C[(s+i)%3]) for i in range(3)];w0=p[:16];w1=xor(fo(w0,c[0]),p[16:]);w2=xor(fe(w1,c[1]),w0);w3=xor(fo(w2,c[2]),w1);w=[int.from_bytes(v,'big') for v in (w0,w1,w2,w3)];ek=[(w[i]^(rotl(w[i+1],a) if l else rotr(w[i+1],a))) if i<3 else ((rotl(w[0],a) if l else rotr(w[0],a))^w[3]) for a,l in ((19,0),(31,0),(61,1),(31,1)) for i in range(4)];ek.append(w[0]^rotl(w[1],19));return [tb(v) for v in ek[:{16:12,24:14,32:16}[len(key)]+1]]
decryption_keys=lambda ek:[ek[-1]]+[diffuse(ek[-1-i]) for i in range(1,len(ek)-1)]+[ek[0]];crypt_block=lambda k,b:xor(sl2(xor(R(lambda p,i:(fo if i%2==0 else fe)(p,k[i]),range(len(k)-2),b),k[-2])),k[-1]);encrypt_block=lambda key,block:crypt_block(encryption_keys(key),block);decrypt_block=lambda key,block:crypt_block(decryption_keys(encryption_keys(key)),block);k=bytes(range(16));p=bytes.fromhex('00112233445566778899aabbccddeeff');c=encrypt_block(k,p);print('Ciphertext:',c.hex());print('Decrypted: ',decrypt_block(k,c).hex())

It gives the same results as the readable version. Running it prints the RFC ciphertext for the 128-bit key 000102030405060708090a0b0c0d0e0f, then decrypts it back to the plaintext:

Ciphertext: d718fbd6ab644c739da95f3be6451778
Decrypted:  00112233445566778899aabbccddeeff

It also reproduces the RFC’s 192-bit and 256-bit ciphertexts. On 300 random keys and blocks, 100 for each key size, it matched the readable code exactly for the round keys, the decryption keys, and both directions.

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 indexes come from secret bytes, so real software must consider cache-timing leaks that this plain code does not address.
  • Slow by design. The diffusion layer here is a loop over index lists. Optimized code merges the substitution and diffusion into combined tables.
  • No side-channel hardening. There is no protection against power or fault attacks.

Security Status

The designers built ARIA to resist all known attacks on block ciphers, and the cipher was analyzed by the COSIC research group at K.U.Leuven in Belgium, which found no security flaw. Published cryptanalysis reaches only reduced-round versions. One example is a meet-in-the-middle attack on 8 rounds, while the full cipher has 12 to 16.

ARIA shares AES’s S-box and its overall substitution-permutation structure, so the same general analysis techniques apply to it. It is generally regarded as having a security level comparable to AES.

FAQ

Is ARIA as secure as AES?

By every public measure it looks comparable. Both have 128-bit blocks and the same key sizes, and no practical attack exists on either. ARIA has simply been analyzed less widely than AES.

How is ARIA different from AES?

ARIA shares AES’s S-box and its substitution-permutation structure. Its diffusion is different: one dense layer mixing seven bytes into each output byte, with 12, 14, or 16 rounds. AES uses a row shift and a matrix multiplication, with 10, 12, or 14 rounds.

What does it mean that the diffusion layer is an involution?

It means applying the layer twice gives back the original data. That lets decryption reuse the encryption routine, with round keys that are passed through the same layer to compensate.

Why are the key-schedule constants taken from 1/π?

It shows the designers did not choose the numbers to hide a weakness. Anyone can compute the digits of 1/π and check them, which is the same idea behind other “nothing-up-my-sleeve” constants in cryptography.

Where is ARIA used?

It is widely used in Korea, especially in government-to-public services. It is part of PKCS #11, and it has IETF specifications for TLS and SRTP. It is available in OpenSSL and in the Linux kernel.

References

  1. Kwon, D. et al. “New Block Cipher: ARIA.” International Conference on Information Security and Cryptology (ICISC) 2003.

  2. Lee, J. et al. “A Description of the ARIA Encryption Algorithm.” RFC 5794, 2010. Available at: https://www.rfc-editor.org/rfc/rfc5794

  3. Biryukov, A. et al. “Security and Performance Analysis of ARIA.” K.U.Leuven, 2003.

  4. Wikipedia. “ARIA (cipher).” Available at: https://en.wikipedia.org/wiki/ARIA_(cipher)