What Is Encryption?
Imagine you want to send a secret note to your friend across the room, but other people can see the note as it passes between you. You need a way to scramble the message so that only your friend can read it. That's what encryption does.
"Hello World!"secret-key-1237a3f1b9e...secret-key-123"Hello World!"Three key terms
The original, readable message. This is what you want to protect.
"Hello World!"The scrambled, unreadable output. This is safe to send over insecure channels.
7a3f1b9ec8d2...The secret that controls the scrambling. Only someone with the same key can undo the encryption.
my-secret-keyWhat is a block cipher?
There are different ways to do encryption. A block cipher works by chopping your message into fixed-size chunks called blocks, then encrypting each block separately.
RCEA is a block cipher. What makes it unique is how it scrambles each block โ it maps the bytes onto the faces of a virtual Rubik's Cube and uses cube rotations as the scrambling mechanism. The block size depends on the cube size: a 3ร3 cube has 6 faces ร 9 cells = 54 bytes per block.
The Rubik's Cube Idea
A real Rubik's Cube has 6 faces, each covered in a grid of colored squares. A standard 3ร3 cube has 9 squares per face, for a total of 54 squares. Now imagine replacing each colored square with a byte of data (a number from 0 to 255).
Mapping data to the cube
To encrypt a message, RCEA takes a chunk of bytes and lays them out across the 6 faces of the cube. Here's what it looks like for the string "Hello World!" on a 3ร3 cube:
Example: "Hello World!" mapped onto a 3ร3 cube (54 bytes, PKCS#7 padded)
How the mapping works
- Bytes fill the Front face first (left-to-right, top-to-bottom)
- Then Back, Left, Right, Up, and Down
- If the message is shorter than the block, it gets PKCS#7 padded (filled with padding bytes)
- Each face is an NรN grid, so one block is always
6 ร Nยฒbytes
Block size formula
The cube size N determines how many bytes fit in one block:
blockSize = 6 ร NยฒWhy a cube?
When you twist a Rubik's Cube, pieces move from one face to another. This creates positional diffusion โ a byte that was on the Front face might end up on the Right face. In traditional ciphers like AES, this is done with simple row shifts. RCEA uses the much richer structure of a Rubik's Cube, where a single rotation moves 2N+1 bytes across multiple faces simultaneously.
- 6 faces ร 3 layers = 18 possible moves
- Each move shifts bytes across 4 face boundaries
- 5 rotations per round (N+2 = 3+2)
- ~4.3 ร 10ยน&sup9; possible cube states
- AES ShiftRows: 3 fixed rotations (always the same)
- RCEA CubeRotate: key-dependent rotations (different every time)
- AES works on a flat 4ร4 grid (2D)
- RCEA works on a 3D cube structure
How RCEA Works
RCEA encrypts one block at a time. Each block passes through a pipeline of four operations, and this pipeline repeats for a configurable number of rounds (4 to 20). More rounds = more scrambling = harder to break.
Step by step
The plaintext block (e.g. 54 bytes for a 3ร3 cube) is laid out across the 6 faces.
Before any rounds begin, the block is XORed with a keyed state โ a block-sized value derived from your secret key. This ensures the starting point depends on the key.
Each round applies four operations in order: SubBytes, CubeRotate, FaceMix, AddRoundKey. After all R rounds, the block is thoroughly scrambled.
The resulting bytes are the encrypted block. In CBC mode, this ciphertext is also fed into the next block's encryption (chaining).
The mixing analogy
Think of it like mixing paint. One stir blends the colors a little โ you can still see streaks. Ten stirs and the colors are uniformly blended. Each round is a stir. With 4 rounds, the data is reasonably mixed. With 10+ rounds, every output byte depends on every input byte and every bit of the key.
The actual code
Here's what the encrypt function looks like in pseudocode. This is the exact logic used by RCEA:
// 1. Start with the plaintext block state = plaintextBlock // 2. Mix in the key-derived initial state state = XOR(state, keyedState) // 3. Run R rounds of transformation for round = 0 to R-1: state = SubBytes(state) // confusion: S-box lookup state = CubeRotate(state, rots) // diffusion: cube face rotations state = FaceMix(state) // diffusion: row & column XOR scan state = XOR(state, roundKey[r]) // key mixing: round key addition // 4. Output ciphertextBlock = state
Decryption runs the same steps in reverse order, using inverse operations (inverse S-Box, inverse rotations, inverse FaceMix). Because every operation has a mathematical inverse, decryption perfectly recovers the original plaintext.
Why these four operations?
Good encryption needs two properties, identified by Claude Shannon in 1949:
Each bit of the ciphertext should depend on the key in a complex way. If you change one bit of the key, the ciphertext should change unpredictably. SubBytes provides confusion through its non-linear S-Box lookup.
Changing one bit of the plaintext should change many bits of the ciphertext (and vice versa). CubeRotate and FaceMix provide diffusion by moving bytes around and mixing their values together.
The Four Operations
Each round applies four operations in sequence. Let's look at each one in detail โ what it does, why it's needed, and exactly how it transforms the data.
1. SubBytes โ Byte Substitution
Purpose: Provides confusion โ makes the relationship between the key and ciphertext as complex as possible.
Every single byte in the block is replaced with a different byte using a fixed lookup table called the S-Box (Substitution Box). This is the same S-Box used in AES โ the Rijndael S-Box, which has been studied for decades and is known to have excellent non-linearity properties.
How it works
- Take one byte, for example
0x53(decimal 83) - Split it into two halves: high nibble =
5(row), low nibble =3(column) - Look up row 5, column 3 in the 16ร16 S-Box table
- The value at that position is
0xEDโ that replaces the original byte - Repeat for every byte in the entire block
| .0 | .1 | .2 | .3 | .4 | .5 | .6 | .7 | .8 | .9 | .A | .B | .C | .D | .E | .F | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 4. | 09 | 83 | 2C | 1A | 1B | 6E | 5A | A0 | 52 | 3B | D6 | B3 | 29 | E3 | 2F | 84 |
| 5. | 53 | D1 | 00 | ED | 20 | FC | B1 | 5B | 6A | CB | BE | 39 | 4A | 4C | 58 | CF |
| 6. | D0 | EF | AA | FB | 43 | 4D | 33 | 85 | 45 | F9 | 02 | 7F | 50 | 3C | 9F | A8 |
Why is it non-linear? If the S-Box were a simple arithmetic operation (like "add 5 to each byte"), an attacker could write a math equation to describe the encryption and solve it with algebra. The S-Box is carefully designed so that no simple equation describes it โ it breaks linearity.
Is it reversible? Yes! There's an inverse S-Box (INV_SBOX) where INV_SBOX[SBOX[x]] = x for every byte. This is how decryption undoes the substitution.
In code: result[i] = SBOX[state[i]] โ that's it. One lookup per byte.
2. CubeRotate โ Cube Face Rotations
Purpose: Provides positional diffusion โ moves bytes from one face to another so their positions get thoroughly shuffled.
Think of actually twisting a Rubik's Cube. When you rotate a face, the 2N bytes along the edge of that face move to adjacent faces, and the Nยฒ bytes on the face itself get rearranged. RCEA applies N + 2 rotations per round, and the specific rotations are derived from the key.
How it works
- The key schedule pre-computes a list of move indices for each round
- Each move index selects one of the
6รNpossible rotations (6 faces ร N layers) - The rotation is applied as a permutation โ bytes get shuffled to new positions
- This repeats for each move in the round (N+2 times)
The front face's bytes are rearranged (rotated within the face).
Bytes from adjacent faces (Up right column, Right left column) also move.
Highlighted cells show bytes that moved.
Key-dependent rotations: Unlike AES where ShiftRows always shifts by the same amount, RCEA's rotations are different for every key. An attacker who doesn't know the key can't predict which rotations were applied.
Permutation math: Each rotation is stored as a permutation array โ position i moves to position perm[i]. Multiple rotations compose by applying them sequentially.
In code: state = applyPermutation(state, allMoves[idx].forward) for each move index.
3. FaceMix โ Value Mixing
Purpose: Provides value diffusion โ makes each byte's value depend on its neighbors, so changing one byte changes many others.
CubeRotate moves bytes around but doesn't change their values. FaceMix does the opposite โ it keeps bytes in place but XORs each one with the S-Box of its neighbor. It runs two passes on each face: first along rows (left to right), then along columns (top to bottom).
How it works
- Pass 1 โ Row scan: For each row, starting from the second cell, XOR the cell with S-Box(left neighbor). Scan left โ right.
- Pass 2 โ Column scan: For each column, starting from the second cell, XOR the cell with S-Box(cell above). Scan top โ bottom.
- Repeat for all 6 faces.
Why S-Box in the XOR? Using S-Box(neighbor) instead of the raw neighbor adds non-linearity. Without it, the XOR scan would be a linear operation that's easy to reverse-engineer.
Why two passes? The row scan propagates changes horizontally. The column scan propagates vertically. Together, changing any single byte affects every byte in the face.
Ripple effect: Because each cell depends on the previous one, a change in the first cell propagates through the entire row (pass 1) and then through the entire column (pass 2). After both passes, every cell in the face is affected.
The S-Box in the XOR: Using S-Box(neighbor) instead of just neighbor adds non-linearity. Without it, the scan would be a linear operation breakable with algebra.
In code: result[idx] = result[idx] ^ SBOX[result[prevIdx]]
4. AddRoundKey โ Key Addition
Purpose: Makes the cipher key-dependent โ without this step, the same input always produces the same output, regardless of the key.
This is the simplest operation: XOR every byte in the state with the corresponding byte from the round key. Each round uses a different round key (derived from the secret key via the key schedule), so the mixing is unique per round.
How it works
- Take the current state (all 6รNยฒ bytes)
- Take the round key for this round (also 6รNยฒ bytes, derived from the secret key)
- XOR them together, byte by byte:
newState[i] = state[i] โ roundKey[i]
A3โ5C=FFWhy XOR? XOR is its own inverse: A โ B โ B = A. This makes decryption trivial โ just XOR with the same round key again.
Why per-round keys? If every round used the same key, an attacker could potentially cancel out multiple rounds. Unique round keys prevent this.
In code: state = xorBytes(state, roundKeys[r])
CBC Mode & Authentication
So far we've talked about encrypting a single block. But real messages are usually longer than one block (54 bytes for a 3ร3 cube). How do we encrypt a 10KB file? We need a mode of operation โ a strategy for handling multiple blocks.
The problem with encrypting blocks independently
The simplest approach (called ECB mode) encrypts each block separately. But this has a fatal flaw: identical plaintext blocks produce identical ciphertext blocks. An attacker can spot patterns โ for example, if you encrypt an image, the shape of the image is still visible in the ciphertext!
CBC: Cipher Block Chaining
RCEA uses CBC mode. Before encrypting each block, it XORs the plaintext with the previous ciphertext block. This creates a chain โ each block's encryption depends on all the blocks before it. Identical plaintext blocks now produce different ciphertext because each is mixed with different previous data.
The IV (Initialization Vector)
The first block has no previous ciphertext to chain with. So we generate a random IV (Initialization Vector) โ a block-sized random value. This means encrypting the same message with the same key produces completely different ciphertext each time (because the IV is different).
- Must be random (cryptographically secure)
- Must be unique for each encryption
- Does NOT need to be secret
- Sent alongside the ciphertext (prepended)
- Same size as one block (6รNยฒ bytes)
The IV's job is to add randomness, not secrecy. Even if an attacker knows the IV, they still can't decrypt without the key. It's like a salt in password hashing โ public but essential for security.
PKCS#7 Padding
Messages don't always divide evenly into blocks. If your message is 100 bytes and your block size is 54, you need to pad the last block to fill it up. RCEA uses PKCS#7 padding:
If 8 bytes short: append 08 08 08 08 08 08 08 08
If 3 bytes short: append 03 03 03
If 1 byte short: append 01
If exactly full: add a whole new block of 36 36 36 ... 36 (for 54-byte blocks)
The pad byte tells the decryptor how many padding bytes to remove. This is unambiguous because even a full block gets padding.
HMAC Authentication (Encrypt-then-MAC)
Encryption alone protects confidentiality (nobody can read the message), but not integrity (nobody has tampered with it). An attacker could flip bits in the ciphertext, and decryption would succeed โ producing garbage that looks like a valid message.
RCEA uses Encrypt-then-MAC: after encrypting, it computes an HMAC-SHA256 tag over the entire ciphertext (IV + encrypted blocks). This tag is a 32-byte fingerprint. When decrypting, the tag is verified first. If it doesn't match, decryption is refused โ the data has been tampered with.
Encrypt all blocks with CBC to produce ciphertext
Compute: tag = HMAC-SHA256(hmacKey, IV || C1 || C2 || ...)
Append the 32-byte tag to the output
On decrypt: verify tag BEFORE attempting any decryption
If tag doesn't match โ refuse to decrypt (tampering detected)
Key Schedule
The key schedule is the process of taking your secret key (just 16 or 32 bytes) and expanding it into all the material the cipher needs: a keyed initial state, one round key per round, rotation sequences for CubeRotate, and an HMAC key for authentication. Without this expansion, the short key couldn't fill all these roles.
Why expand? A short key (16โ32 bytes) can't directly fill all the round keys, rotation indices, and the keyed state. The KDF stretches the key into hundreds of bytes of pseudo-random material, each portion derived with a unique label so they're cryptographically independent.
The expansion process
RCEA uses a KDF (Key Derivation Function) based on SHA-256. It works like a deterministic random number generator โ given the same key and label, it always produces the same output, but different labels produce completely independent outputs.
KDF(key, label, length):
output = empty
counter = 0
while output.length < length:
chunk = SHA-256(key || label || counter)
output = output + chunk
counter++
return output[0..length]Each call with a different label produces independent output. The counter handles cases where more than 32 bytes are needed.
What gets derived
rcea_phase1_columnsA block-sized array (6รNยฒ bytes) that's XORed with the plaintext before round 1. This is like a pre-key โ it ensures the starting state is already key-dependent.
After generating it from the KDF, RCEA also applies N+2 key-derived cube rotations to it, further scrambling the initial state.
rcea_round_key_0, ..._1, ..._2, etc.One block-sized key per round. Round 0 uses rcea_round_key_0, round 1 uses rcea_round_key_1, and so on.
For a 3ร3 cube with 10 rounds, that's 10 ร 54 = 540 bytes of round key material, all derived from the original 16 or 32-byte key.
rcea_round_rot_0, ..._1, etc.Each round needs N+2 rotation indices for CubeRotate. Each index is computed as byte mod (6รN) to select from the 6รN possible cube moves.
For a 3ร3 cube: each round has 5 rotations chosen from 18 possible moves. With 10 rounds, that's 50 rotation decisions, all deterministically derived from the key.
rcea_hmac_key32 bytes for the HMAC-SHA256 computation. This is kept separate from the encryption material โ using the same key for both encryption and MAC would be a security weakness. The KDF's different labels ensure cryptographic independence.
Why label-based derivation?
Using unique labels (like rcea_round_key_0) for each derived value ensures key separation:
- Knowing one round key reveals nothing about other round keys
- The HMAC key is independent from the encryption keys
- The keyed state is independent from everything else
- An attacker who somehow recovers one derived value can't compute the others
Security Notes
Educational Purpose Only
RCEA is designed for learning, not for protecting real secrets. Use AES, ChaCha20, or other well-vetted algorithms for real-world encryption.
What RCEA does well
- Demonstrates core block cipher concepts (confusion, diffusion, key scheduling)
- Uses the well-studied AES S-Box for non-linearity
- Includes proper CBC mode with random IVs
- Uses HMAC-SHA256 for ciphertext authentication (Encrypt-then-MAC)
- Key derivation via Web Crypto HKDF
Why you shouldn't use it for real security
- Not peer-reviewed or cryptanalyzed by experts
- No formal security proofs
- Side-channel attacks not considered (timing, cache)
- Novel cipher design โ proven algorithms are always safer
RCEA vs. Established Algorithms
| Property | RCEA | AES-256 | ChaCha20 |
|---|---|---|---|
| Type | Block cipher | Block cipher | Stream cipher |
| Block size | 24โ216 bytes (variable, 6รNยฒ) | 16 bytes (fixed) | 64 bytes (internal state) |
| Key size | 128 or 256 bits | 128, 192, or 256 bits | 256 bits |
| Rounds | 4โ20 (configurable) | 10 / 12 / 14 | 20 |
| S-Box | AES Rijndael S-Box | AES Rijndael S-Box | None (ARX design) |
| Confusion | SubBytes (S-Box lookup) | SubBytes (S-Box lookup) | Addition + XOR (non-linear mixing) |
| Diffusion | Cube rotations (positional permutation) + FaceMix (row/col XOR scan) | ShiftRows (row rotation) + MixColumns (GF(2&sup8;) matrix multiply) | Quarter-round function (add, rotate, XOR) |
| Key schedule | HKDF-SHA256 expansion โ keyed state, round keys, rotation sequences | Rijndael key schedule (word rotations + S-Box + Rcon) | Key + nonce packed into initial state |
| Mode of operation | CBC + HMAC-SHA256 (Encrypt-then-MAC) | GCM, CBC, CTR, etc. | Poly1305 AEAD (built-in auth) |
| Hardware accel. | None | AES-NI instructions | SIMD-friendly |
| Performance | Slow (JS, large blocks, many permutations) | Very fast (~1 cycle/byte with AES-NI) | Fast (~4 cycles/byte, constant-time) |
| Side-channel resistance | Not considered | Constant-time with AES-NI | Constant-time by design (no S-Box) |
| Status | Educational | NIST Standard (2001) | IETF Standard (2018) |
Step-by-Step: RCEA vs. AES Round Comparison
RCEA's round structure is directly inspired by AES. Each step has an analogous operation:
Key Differences
Uses 3D Rubik's Cube rotations to permute bytes across 6 faces. The number of possible permutations grows with cube size (e.g., a 3ร3 cube has ~4.3ร10ยนโน possible states). Rotation sequences are key-dependent.
Uses fixed ShiftRows (deterministic row shifts) + MixColumns (a specific matrix over GF(2โธ)). The permutation is always the same โ security comes from the key addition, not the shuffling pattern.
Data lives on 6 faces of a cube: a 3D structure. Rotations mix bytes across face boundaries, providing cross-dimensional diffusion in a single operation.
Data is a flat 4ร4 byte matrix (2D). ShiftRows works on rows, MixColumns on columns โ two separate operations to achieve full diffusion.
Block size = 6รNยฒ bytes. A 2ร2 cube gives 24-byte blocks, a 6ร6 gives 216-byte blocks. Larger blocks diffuse more data per operation but are slower.
Always 16 bytes (128 bits). This fixed size enables hardware optimization (AES-NI) and is efficient for most use cases.
FaceMix uses simple XOR accumulation โ effective but not proven optimal. Cube rotations provide permutation diffusion without formal mixing guarantees.
MixColumns is provably optimal for branch number (every output byte depends on all input bytes after 2 rounds). Based on rigorous finite field arithmetic.