ChaCha12 Algorithm Explanation
The ChaCha12 algorithm is a variant of the ChaCha stream cipher. It operates on a state of 16
words, each of which is 32 bits. The algorithm processes data by applying a series of
transformations called quarter-round functions. Below, we describe how ChaCha12 works and
provide sample code in C#.
1. **State Initialization**:
The state is initialized using:
- A constant string ("expand 32-byte k")
- A 256-bit key (split into 8 words)
- A 32-bit counter
- A 96-bit nonce (split into 3 words)
2. **Quarter-Round Function**:
The quarter-round function operates on four 32-bit words (A, B, C, D) as follows:
- A += B; D ^= A; D = (D << 16) | (D >> 16)
- C += D; B ^= C; B = (B << 12) | (B >> 20)
- A += B; D ^= A; D = (D << 8) | (D >> 24)
- C += D; B ^= C; B = (B << 7) | (B >> 25)
3. **Rounds**:
ChaCha12 applies 12 rounds of the quarter-round function in two alternating phases:
- Column Round: Applies the quarter-round function to the four columns of the state.
- Diagonal Round: Applies the quarter-round function to the diagonals of the state.
4. **State Addition**:
After 12 rounds, the result is added to the original state, and the keystream is extracted.
5. **Encryption**:
The plaintext is XORed with the keystream to produce the ciphertext.
C# Code for ChaCha12
using System;
class ChaCha12
private static uint RotateLeft(uint value, int shift)
return (value << shift) | (value >> (32 - shift));
private static void QuarterRound(ref uint a, ref uint b, ref uint c, ref uint d)
a += b; d ^= a; d = RotateLeft(d, 16);
c += d; b ^= c; b = RotateLeft(b, 12);
a += b; d ^= a; d = RotateLeft(d, 8);
c += d; b ^= c; b = RotateLeft(b, 7);
private static uint[] InitializeState(byte[] key, uint counter, byte[] nonce)
{
uint[] constants = new uint[] { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 };
uint[] keyWords = new uint[8];
for (int i = 0; i < 8; i++)
keyWords[i] = BitConverter.ToUInt32(key, i * 4);
uint[] nonceWords = new uint[3];
for (int i = 0; i < 3; i++)
nonceWords[i] = BitConverter.ToUInt32(nonce, i * 4);
uint[] state = new uint[16];
[Link](constants, 0, state, 0, 4);
[Link](keyWords, 0, state, 4, 8);
state[12] = counter;
[Link](nonceWords, 0, state, 13, 3);
return state;
private static void ChaCha12Rounds(ref uint[] state)
for (int i = 0; i < 6; i++)
QuarterRound(ref state[0], ref state[4], ref state[8], ref state[12]);
QuarterRound(ref state[1], ref state[5], ref state[9], ref state[13]);
QuarterRound(ref state[2], ref state[6], ref state[10], ref state[14]);
QuarterRound(ref state[3], ref state[7], ref state[11], ref state[15]);
QuarterRound(ref state[0], ref state[5], ref state[10], ref state[15]);
QuarterRound(ref state[1], ref state[6], ref state[11], ref state[12]);
QuarterRound(ref state[2], ref state[7], ref state[8], ref state[13]);
QuarterRound(ref state[3], ref state[4], ref state[9], ref state[14]);
private static void AddOriginalState(uint[] state, uint[] originalState)
for (int i = 0; i < [Link]; i++)
state[i] += originalState[i];
public static byte[] Encrypt(byte[] key, uint counter, byte[] nonce, byte[] plaintext)
byte[] ciphertext = new byte[[Link]];
uint[] state = InitializeState(key, counter, nonce);
uint[] workingState = new uint[16];
byte[] keystream = new byte[64];
for (int i = 0; i < [Link]; i += 64)
[Link](state, workingState, 16);
ChaCha12Rounds(ref workingState);
AddOriginalState(workingState, state);
for (int j = 0; j < 16; j++)
[Link]([Link](workingState[j]), 0, keystream, j * 4, 4);
for (int j = 0; j < 64 && (i + j) < [Link]; j++)
ciphertext[i + j] = (byte)(plaintext[i + j] ^ keystream[j]);
state[12]++;
return ciphertext;