Salsa20 cipher

Salsa20Cipher.pas

{ Salsa20 stream cipher.

  Key size: 32 bytes, IV size: 8 bytes, Block size: 64 bytes

  HOW TO USE - CODE SNIPPETS

  var
    salsa20: TSalsa20Cipher;
    key, iv, message1, message2: TBytes;

  // message is the data to encrypt or decrypt.
  // initialize message1, message2, key and iv arrays before calling 'combine_with_keystream'.
  ...
  ...
  salsa20 := TSalsa20Cipher.create(key, iv);

  // combine_with_keystream encrypts or decrypts data in the first argument.
  // if called with plain data - encrypts data,
  // if called with encrypted data - decrypts data.
  // The encrypted/decrypted result data has the same length as the source data.

  salsa20.combine_with_keystream(message1, 0, length(message1));
  salsa20.combine_with_keystream(message2, 0, length(message2));

  ------------------------------------------------------------------------------------

  References
  https://en.wikipedia.org/wiki/Salsa20
}
unit Salsa20Cipher;


interface

uses System.SysUtils, System.Math;


type
  TSalsa20Cipher = record

    /// <summary>
    /// key32 - key of length 32 bytes.
    /// iv8 - initialization vector of length 8 bytes.
    /// </summary>
    constructor create(key32: TBytes; iv8: TBytes);

    /// <summary>
    /// Encrypts or decrypts data in m.
    /// The result bytes will be written to m.
    /// </summary>
    procedure combine_with_keystream(m: TBytes; offset, cb: Cardinal);

  const
    BLOCK_SIZE = 64;
  private
    _block: array [0 .. 63] of byte;
    _block_pos: Cardinal;
    _s: array [0 .. 15] of UInt32; // state
    procedure next_block(); inline;
    function rotate_left(u, bits: UInt32): UInt32; inline;

  const
    _sigma: array [0 .. 3] of UInt32 = ($61707865, $3320646E, $79622D32, $6B206574);
  end;


implementation

type
  TArr8UInt32 = array [0 .. 7] of UInt32;
  PArr8Uint32 = ^TArr8UInt32;


constructor TSalsa20Cipher.create(key32: TBytes; iv8: TBytes);
begin
  Assert(Length(key32) = 32);
  Assert(Length(iv8) = 8);

  _block_pos := BLOCK_SIZE;

  // Key setup
  _s[1] := PArr8Uint32(@key32[0])^[0];
  _s[2] := PArr8Uint32(@key32[0])^[1];
  _s[3] := PArr8Uint32(@key32[0])^[2];
  _s[4] := PArr8Uint32(@key32[0])^[3];
  _s[11] := PArr8Uint32(@key32[0])^[4];
  _s[12] := PArr8Uint32(@key32[0])^[5];
  _s[13] := PArr8Uint32(@key32[0])^[6];
  _s[14] := PArr8Uint32(@key32[0])^[7];

  _s[0] := _sigma[0];
  _s[5] := _sigma[1];
  _s[10] := _sigma[2];
  _s[15] := _sigma[3];

  // IV setup
  _s[6] := PArr8Uint32(@iv8[0])^[0];
  _s[7] := PArr8Uint32(@iv8[0])^[1];
  _s[8] := 0; // counter, low
  _s[9] := 0; // counter, high
end;





procedure TSalsa20Cipher.combine_with_keystream(m: TBytes; offset, cb: Cardinal);
var
  cb_copy, i: Cardinal;
begin
  Assert(offset >= 0);
  Assert(Assigned(m));
  Assert(cb >= 0);
  Assert(offset >= (Length(m) - cb));

  while cb > 0 do
  begin
    Assert(_block_pos <= BLOCK_SIZE);
    if _block_pos = BLOCK_SIZE then
    begin
      next_block();
      _block_pos := 0;
    end;

    cb_copy := Min(BLOCK_SIZE - _block_pos, cb);
    Assert(cb_copy > 0);

    for i := 0 to Pred(cb_copy) do
      m[i + offset] := m[i + offset] xor _block[i + _block_pos];

    inc(_block_pos, cb_copy);
    inc(offset, cb_copy);
    Dec(cb, cb_copy);
  end;
end;



procedure TSalsa20Cipher.next_block();
var
  i, i4, xi: UInt32;
  x: array [0 .. 15] of UInt32; // working buffer
begin
  Assert(sizeof(_s) >= 16);

  Move(_s[0], x[0], sizeof(_s));

  for i := 0 to 9 do
  begin
    x[4] := x[4] xor rotate_left(x[0] + x[12], 7);
    x[8] := x[8] xor rotate_left(x[4] + x[0], 9);
    x[12] := x[12] xor rotate_left(x[8] + x[4], 13);
    x[0] := x[0] xor rotate_left(x[12] + x[8], 18);

    x[9] := x[9] xor rotate_left(x[5] + x[1], 7);
    x[13] := x[13] xor rotate_left(x[9] + x[5], 9);
    x[1] := x[1] xor rotate_left(x[13] + x[9], 13);
    x[5] := x[5] xor rotate_left(x[1] + x[13], 18);

    x[14] := x[14] xor rotate_left(x[10] + x[6], 7);
    x[2] := x[2] xor rotate_left(x[14] + x[10], 9);
    x[6] := x[6] xor rotate_left(x[2] + x[14], 13);
    x[10] := x[10] xor rotate_left(x[6] + x[2], 18);

    x[3] := x[3] xor rotate_left(x[15] + x[11], 7);
    x[7] := x[7] xor rotate_left(x[3] + x[15], 9);
    x[11] := x[11] xor rotate_left(x[7] + x[3], 13);
    x[15] := x[15] xor rotate_left(x[11] + x[7], 18);

    x[1] := x[1] xor rotate_left(x[0] + x[3], 7);
    x[2] := x[2] xor rotate_left(x[1] + x[0], 9);
    x[3] := x[3] xor rotate_left(x[2] + x[1], 13);
    x[0] := x[0] xor rotate_left(x[3] + x[2], 18);

    x[6] := x[6] xor rotate_left(x[5] + x[4], 7);
    x[7] := x[7] xor rotate_left(x[6] + x[5], 9);
    x[4] := x[4] xor rotate_left(x[7] + x[6], 13);
    x[5] := x[5] xor rotate_left(x[4] + x[7], 18);

    x[11] := x[11] xor rotate_left(x[10] + x[9], 7);
    x[8] := x[8] xor rotate_left(x[11] + x[10], 9);
    x[9] := x[9] xor rotate_left(x[8] + x[11], 13);
    x[10] := x[10] xor rotate_left(x[9] + x[8], 18);

    x[12] := x[12] xor rotate_left(x[15] + x[14], 7);
    x[13] := x[13] xor rotate_left(x[12] + x[15], 9);
    x[14] := x[14] xor rotate_left(x[13] + x[12], 13);
    x[15] := x[15] xor rotate_left(x[14] + x[13], 18);
  end;

  for i := 0 to 15 do
    x[i] := x[i] + _s[i];

  for i := 0 to 15 do
  begin
    i4 := i shl 2;
    xi := x[i];

    _block[i4] := byte(xi);
    _block[i4 + 1] := byte(xi shr 8);
    _block[i4 + 2] := byte(xi shr 16);
    _block[i4 + 3] := byte(xi shr 24);
  end;

  inc(_s[8]);

  if _s[8] = 0 then
    inc(_s[9]);

end;



function TSalsa20Cipher.rotate_left(u, bits: UInt32): UInt32;
begin
  result := ((u shl bits) or (u shr (32 - bits)));
end;


end.

HOW TO USE - Example

Salsa20CipherDemo.dpr

program Salsa20CipherDemo;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, System.NetEncoding,
  Salsa20Cipher in 'Salsa20Cipher.pas';

const
  key: TBytes = [194, 202, 169, 249, 109, 110, 89, 27, 34, 60, 253, 150, 188, 97, 10, 89, 99, 27,
    214, 220, 3, 232, 184, 120, 171, 64, 229, 230, 58, 89, 168, 235];
  iv: TBytes = [$E8, $30, $10, $4B, $97, $20, $5D, $2A];

var
  salsa20: TSalsa20Cipher;
  message1, message2: TBytes;
  txt1, txt2: string;

procedure print_byte_array(arr: TBytes);
var
  b: byte;
begin
  for b in arr do
    Write(format('%0x ', [b]));
  Writeln('');
end;

begin
  try
    txt1 := 'Hello';
    txt2 := 'World!';
    message1 := TEncoding.UTF8.GetBytes(txt1);
    message2 := TEncoding.UTF8.GetBytes(txt2);

    Writeln('Plain bytes:');
    print_byte_array(message1);
    print_byte_array(message2);

    salsa20 := TSalsa20Cipher.create(key, iv);

    // encrypt
    salsa20.combine_with_keystream(message1, 0, length(message1));
    salsa20.combine_with_keystream(message2, 0, length(message2));

    Writeln(sLineBreak + 'Encrypted bytes:');
    print_byte_array(message1);
    print_byte_array(message2);

    // IMPORTANT: create new instance of the cipher before decrypting!
    salsa20 := TSalsa20Cipher.create(key, iv);

    // decrypt
    salsa20.combine_with_keystream(message1, 0, length(message1));
    salsa20.combine_with_keystream(message2, 0, length(message2));

    Writeln(sLineBreak + 'Plain bytes after decryption:');
    print_byte_array(message1);
    print_byte_array(message2);

    readln;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;

end.

Program output

Plain bytes:
48 65 6C 6C 6F
57 6F 72 6C 64 21

Encrypted bytes:
FC 3B 45 87 2
46 1 F6 1B 11 E0

Plain bytes after decryption:
48 65 6C 6C 6F
57 6F 72 6C 64 21