Skip to content
CyberXplained

Salsa20 Encryption Algorithm

CoreSymmetric

About Salsa20

A stream cipher designed by Daniel J. Bernstein in 2005

Salsa20 is a stream cipher that was submitted to the eSTREAM project by Daniel J. Bernstein. It's known for its speed and security, and has been widely adopted in various security applications.

Key Features

  • 256-bit key (128-bit version also available)
  • 64-bit nonce (initialization vector)
  • 64-bit counter for position in the stream
  • High performance in software implementations
  • No known practical attacks against full rounds
  • Simple design with ARX operations (Add-Rotate-XOR)

Technical Specifications

  • Block size: 512 bits (64 bytes)
  • State: 4×4 matrix of 32-bit words
  • 20 rounds (Salsa20/20)
  • Reduced round variants: Salsa20/8, Salsa20/12
  • No S-boxes or table lookups (cache-timing resistant)
  • Little-endian byte ordering

How Salsa20 Works

Salsa20 generates a keystream which is then XORed with the plaintext to produce ciphertext. The core function transforms a 64-byte block using a series of operations:

  1. Initialize a 4×4 matrix of 32-bit words with key, nonce, counter, and constants
  2. Apply 20 rounds of the quarter-round function to mix the state
  3. Add the original input to the final state (to ensure invertibility)
  4. Use the resulting 64-byte block as keystream
  5. XOR the keystream with plaintext to get ciphertext

Quarter-Round Function

The quarter-round function is the core operation in Salsa20. It operates on 4 words (a, b, c, d) and applies a series of ARX (Add-Rotate-XOR) operations:

b ^= (a + d) <<< 7
					c ^= (b + a) <<< 9
					d ^= (c + b) <<< 13
					a ^= (d + c) <<< 18

Security Considerations

  • No known practical attacks against full 20-round Salsa20
  • Reduced-round variants (Salsa20/8, Salsa20/12) have been analyzed
  • Nonce reuse is catastrophic (never reuse the same key-nonce pair)
  • No authentication built-in (should be used with a MAC)
  • Resistant to timing attacks due to lack of data-dependent operations

Applications and Variants

Salsa20 and its variants are used in various security applications:

  • NaCl cryptographic library
  • ChaCha20 (a variant with improved diffusion)
  • XSalsa20 (extended nonce variant used in NaCl)
  • Signal Protocol (in combination with other algorithms)
  • TLS 1.3 (ChaCha20-Poly1305)

Salsa20 Animation

Visual representation of the Salsa20 encryption process

Salsa20 Animation

Salsa20 Visualization

State Matrix Transformation

0,0
0,1
0,2
0,3
1,0
1,1
1,2
1,3
2,0
2,1
2,2
2,3
3,0
3,1
3,2
3,3

Quarter-Round Function

b ^= (a + d) <<< 7
c ^= (b + a) <<< 9
d ^= (c + b) <<< 13
a ^= (d + c) <<< 18

Salsa20 Rounds

Input
Round 1-4
Round 5-8
Round 9-12
Round 13-16
Round 17-20
Output

Encryption Process

Keystream
Plaintext
⊕ (XOR)
Ciphertext
SlowFast

Salsa20 Simulator

Try encrypting and decrypting with Salsa20

Salsa20 Simulator

Salsa20 Encryption Simulator

8 (Faster)121620 (More Secure)

Salsa20 Implementation

JavaScript implementation of the Salsa20 algorithm

Salsa20 Implementation

// Salsa20 Core Implementation in JavaScript

// Helper functions
function rotl(value, shift) {
  return ((value << shift) | (value >>> (32 - shift))) >>> 0;
}

// Convert between little-endian words and byte arrays
function littleEndianToInt(bytes, pos) {
  return (
    (bytes[pos] & 0xff) |
    ((bytes[pos + 1] & 0xff) << 8) |
    ((bytes[pos + 2] & 0xff) << 16) |
    ((bytes[pos + 3] & 0xff) << 24)
  ) >>> 0;
}

function intToLittleEndian(x, out, pos) {
  out[pos] = x & 0xff;
  out[pos + 1] = (x >>> 8) & 0xff;
  out[pos + 2] = (x >>> 16) & 0xff;
  out[pos + 3] = (x >>> 24) & 0xff;
}

// Quarter round function
function quarterRound(y, a, b, c, d) {
  y[b] = (y[b] ^ rotl(y[a] + y[d], 7)) >>> 0;
  y[c] = (y[c] ^ rotl(y[b] + y[a], 9)) >>> 0;
  y[d] = (y[d] ^ rotl(y[c] + y[b], 13)) >>> 0;
  y[a] = (y[a] ^ rotl(y[d] + y[c], 18)) >>> 0;
  return y;
}

// Core Salsa20 function
function salsa20Core(input, rounds) {
  // Create working array
  const x = new Uint32Array(16);
  for (let i = 0; i < 16; i++) {
    x[i] = input[i];
  }
  
  // Main loop
  for (let i = rounds; i > 0; i -= 2) {
    // Column round
    quarterRound(x, 0, 4, 8, 12);
    quarterRound(x, 5, 9, 13, 1);
    quarterRound(x, 10, 14, 2, 6);
    quarterRound(x, 15, 3, 7, 11);
    
    // Row round
    quarterRound(x, 0, 1, 2, 3);
    quarterRound(x, 5, 6, 7, 4);
    quarterRound(x, 10, 11, 8, 9);
    quarterRound(x, 15, 12, 13, 14);
  }
  
  // Add input to output
  const output = new Uint32Array(16);
  for (let i = 0; i < 16; i++) {
    output[i] = (x[i] + input[i]) >>> 0;
  }
  
  return output;
}

// Expand key and nonce into initial state
function expandKey(key, nonce) {
  const state = new Uint32Array(16);
  
  // Constants for 256-bit key
  const sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
  
  // Set up initial state with constants
  state[0] = sigma[0];
  state[5] = sigma[1];
  state[10] = sigma[2];
  state[15] = sigma[3];
  
  // Add key (256-bit)
  state[1] = littleEndianToInt(key, 0);
  state[2] = littleEndianToInt(key, 4);
  state[3] = littleEndianToInt(key, 8);
  state[4] = littleEndianToInt(key, 12);
  state[11] = littleEndianToInt(key, 16);
  state[12] = littleEndianToInt(key, 20);
  state[13] = littleEndianToInt(key, 24);
  state[14] = littleEndianToInt(key, 28);
  
  // Add nonce and counter
  state[6] = littleEndianToInt(nonce, 0);
  state[7] = littleEndianToInt(nonce, 4);
  state[8] = 0; // Counter (low)
  state[9] = 0; // Counter (high)
  
  return state;
}

// Generate keystream block
function generateKeystreamBlock(state, rounds) {
  const result = new Uint8Array(64);
  const block = salsa20Core(state, rounds);
  
  // Convert words to bytes
  for (let i = 0; i < 16; i++) {
    intToLittleEndian(block[i], result, i * 4);
  }
  
  // Increment counter
  state[8] = (state[8] + 1) >>> 0;
  if (state[8] === 0) {
    state[9] = (state[9] + 1) >>> 0;
  }
  
  return result;
}

// Encrypt/decrypt function (XOR with keystream)
function processBlock(input, output, state, rounds) {
  const keystream = generateKeystreamBlock(state, rounds);
  
  for (let i = 0; i < input.length && i < 64; i++) {
    output[i] = input[i] ^ keystream[i];
  }
}

// Main Salsa20 encryption/decryption function
function salsa20(input, key, nonce, rounds = 20) {
  const output = new Uint8Array(input.length);
  const state = expandKey(key, nonce);
  
  for (let i = 0; i < input.length; i += 64) {
    const chunk = input.subarray(i, Math.min(i + 64, input.length));
    processBlock(chunk, output.subarray(i, i + chunk.length), state, rounds);
  }
  
  return output;
}

// Helper to convert hex string to byte array
function hexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
  }
  return bytes;
}

// Helper to convert byte array to hex string
function bytesToHex(bytes) {
  return Array.from(bytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');
}

// Example usage
function encryptSalsa20(plaintext, keyHex, nonceHex, rounds = 20) {
  const plaintextBytes = new TextEncoder().encode(plaintext);
  const keyBytes = hexToBytes(keyHex);
  const nonceBytes = hexToBytes(nonceHex);
  
  const ciphertext = salsa20(plaintextBytes, keyBytes, nonceBytes, rounds);
  return bytesToHex(ciphertext);
}

function decryptSalsa20(ciphertextHex, keyHex, nonceHex, rounds = 20) {
  const ciphertextBytes = hexToBytes(ciphertextHex);
  const keyBytes = hexToBytes(keyHex);
  const nonceBytes = hexToBytes(nonceHex);
  
  const plaintextBytes = salsa20(ciphertextBytes, keyBytes, nonceBytes, rounds);
  return new TextDecoder().decode(plaintextBytes);
}

Implementation Notes

  • This is a pure JavaScript implementation of Salsa20 for educational purposes.
  • For production use, consider using established cryptographic libraries.
  • The implementation follows the Salsa20 specification by Daniel J. Bernstein.
  • The code includes the core algorithm, key expansion, and helper functions.
  • Salsa20 is a stream cipher, so it generates a keystream that is XORed with the plaintext.
  • The same function is used for both encryption and decryption (symmetric).
  • The implementation supports the standard 20 rounds, but can be configured for 8 or 12 rounds.