unit ChaCha;
interface
uses
SysUtils;
type
TChaChaRounds = (cr8, cr12, cr20);
TChaChaKey =
array[0..31]
of Byte;
// 256-bit key
TChaChaNonce =
array[0..11]
of Byte;
// 96-bit nonce
TChaChaBlock =
array[0..63]
of Byte;
// 512-bit block
TChaCha =
class
private
FState:
array[0..15]
of Cardinal;
// Internal state
procedure QuarterRound(
var a, b, c, d: Cardinal);
procedure InnerBlock;
procedure SetupState(
const Key: TChaChaKey;
const Nonce: TChaChaNonce; Counter: Cardinal);
{$IFDEF CHACHA_DEBUG}
procedure DebugState(
const DbgStr:
string);
{$ENDIF}
public
procedure Encrypt(
const Key: TChaChaKey;
const Nonce: TChaChaNonce;
Counter: Cardinal; Rounds: TChaChaRounds;
const Input: TBytes;
var Output: TBytes);
class procedure SelfTest;
end;
implementation
const
SIGMA:
array[0..3]
of Cardinal = ($61707865, $3320646e, $79622d32, $6b206574);
// "expand 32-byte k"
{ TChaCha }
{$IFDEF CHACHA_DEBUG}
procedure TChaCha.DebugState(
const DbgStr:
string);
var
I: Integer;
begin
WriteLn(DbgStr);
for I := 0
to 15
do
begin
Write(Format('
%08x ', [FState[I]]));
if (I + 1)
mod 4 = 0
then WriteLn;
end;
WriteLn;
end;
{$ENDIF}
procedure TChaCha.QuarterRound(
var a, b, c, d: Cardinal);
begin
{$IFOPT R+}{$DEFINE RND_HasRangeChecks}{$ENDIF}
{$IFOPT Q+}{$DEFINE RND_HasOverflowChecks}{$ENDIF}
{$RANGECHECKS OFF}
{$OVERFLOWCHECKS OFF}
a := a + b; d := d
xor a; d := (d
shl 16)
or (d
shr 16);
c := c + d; b := b
xor c; b := (b
shl 12)
or (b
shr 20);
a := a + b; d := d
xor a; d := (d
shl 8)
or (d
shr 24);
c := c + d; b := b
xor c; b := (b
shl 7)
or (b
shr 25);
{$IFDEF RND_HasRangeChecks}{$RANGECHECKS ON}{$ENDIF}
{$IFDEF RND_HasOverflowChecks}{$OVERFLOWCHECKS ON}{$ENDIF}
end;
procedure TChaCha.InnerBlock;
var
x:
array[0..15]
of Cardinal;
begin
Move(FState, x, SizeOf(x));
QuarterRound(x[0], x[4], x[8], x[12]);
QuarterRound(x[1], x[5], x[9], x[13]);
QuarterRound(x[2], x[6], x[10], x[14]);
QuarterRound(x[3], x[7], x[11], x[15]);
QuarterRound(x[0], x[5], x[10], x[15]);
QuarterRound(x[1], x[6], x[11], x[12]);
QuarterRound(x[2], x[7], x[8], x[13]);
QuarterRound(x[3], x[4], x[9], x[14]);
Move(x, FState, SizeOf(FState));
end;
procedure TChaCha.SetupState(
const Key: TChaChaKey;
const Nonce: TChaChaNonce; Counter: Cardinal);
begin
FState[0] := SIGMA[0];
FState[1] := SIGMA[1];
FState[2] := SIGMA[2];
FState[3] := SIGMA[3];
Move(Key[0], FState[4], 32);
FState[12] := Counter;
Move(Nonce[0], FState[13], 12);
end;
procedure ChaChaBlockSIMD(State: Pointer; Rounds: Integer);
{$IF Defined(CPUX86) or Defined(CPUX64)}
asm
// Save state
movdqu xmm0, dqword [State+00]
// Use movdqa for aligned access or switch to movdqu for unaligned
movdqu xmm1, dqword [State+16]
movdqu xmm2, dqword [State+32]
movdqu xmm3, dqword [State+48]
// Main loop
@ROUND:
// Column round
paddd xmm0, xmm1
pxor xmm3, xmm0
pshuflw xmm3, xmm3, 0B1h
pshufhw xmm3, xmm3, 0B1h
paddd xmm2, xmm3
pxor xmm1, xmm2
movdqa xmm4, xmm1
pslld xmm1, 12
psrld xmm4, 20
por xmm1, xmm4
paddd xmm0, xmm1
pxor xmm3, xmm0
movdqa xmm4, xmm3
pslld xmm3, 8
psrld xmm4, 24
por xmm3, xmm4
paddd xmm2, xmm3
pxor xmm1, xmm2
movdqa xmm4, xmm1
pslld xmm1, 7
psrld xmm4, 25
por xmm1, xmm4
// Shuffle for diagonal
pshufd xmm1, xmm1, 39h
pshufd xmm2, xmm2, 4Eh
pshufd xmm3, xmm3, 93h
// Diagonal round
paddd xmm0, xmm1
pxor xmm3, xmm0
pshuflw xmm3, xmm3, 0B1h
pshufhw xmm3, xmm3, 0B1h
paddd xmm2, xmm3
pxor xmm1, xmm2
movdqa xmm4, xmm1
pslld xmm1, 12
psrld xmm4, 20
por xmm1, xmm4
paddd xmm0, xmm1
pxor xmm3, xmm0
movdqa xmm4, xmm3
pslld xmm3, 8
psrld xmm4, 24
por xmm3, xmm4
paddd xmm2, xmm3
pxor xmm1, xmm2
movdqa xmm4, xmm1
pslld xmm1, 7
psrld xmm4, 25
por xmm1, xmm4
// Shuffle back
pshufd xmm1, xmm1, 93h
pshufd xmm2, xmm2, 4Eh
pshufd xmm3, xmm3, 39h
dec Rounds
jnz @ROUND
{// Add original state
paddd xmm0, dqword [State+00] // Use dqword for aligned access, paddd must have memory aligned !
paddd xmm1, dqword [State+16]
paddd xmm2, dqword [State+32]
paddd xmm3, dqword [State+48] }
movdqu xmm4, dqword [State+00]
// unaligned memory state addition
paddd xmm0, xmm4
movdqu xmm4, dqword [State+16]
paddd xmm1, xmm4
movdqu xmm4, dqword [State+32]
paddd xmm2, xmm4
movdqu xmm4, dqword [State+48]
paddd xmm3, xmm4
// Store result
movdqu dqword [State+00], xmm0
// Use movdqa for aligned store or switch to movdqu for unaligned
movdqu dqword [State+16], xmm1
movdqu dqword [State+32], xmm2
movdqu dqword [State+48], xmm3
end;
{$ELSE}
begin
RaiseError(ERR_SIMD_NOT_SUPPORTED);
end;
{$ENDIF}
const SIMDEnabled = true;
procedure TChaCha.Encrypt(
const Key: TChaChaKey;
const Nonce: TChaChaNonce;
Counter: Cardinal; Rounds: TChaChaRounds;
const Input: TBytes;
var Output: TBytes);
var
I, J, RoundCount: Integer;
OrigState:
array[0..15]
of Cardinal;
Keystream: TChaChaBlock;
PKeystream: PByte;
begin
SetLength(Output, Length(Input));
if Length(Input) = 0
then
Exit;
case Rounds
of
cr8: RoundCount := 8;
cr12: RoundCount := 12;
cr20: RoundCount := 20;
else
RoundCount := 20;
end;
for I := 0
to (Length(Input) - 1)
div 64
do
begin
SetupState(Key, Nonce, Counter + Cardinal(I));
Move(FState, OrigState, SizeOf(FState));
{$IFDEF CHACHA_DEBUG}
DebugState('
Before InnerBlock:');
{$ENDIF}
if SIMDEnabled
then
begin
ChaChaBlockSIMD(@FState[0],RoundCount
shr 1);
end else
begin
for J := 1
to RoundCount
shr 1
do
begin
InnerBlock;
{$IFDEF CHACHA_DEBUG}
DebugState(Format('
After InnerBlock %d:', [J]));
{$ENDIF}
end;
for J := 0
to 15
do
FState[J] := Cardinal(UInt64(FState[J]) + UInt64(OrigState[J]));
end;
{$IFDEF CHACHA_DEBUG}
DebugState('
After adding OrigState:');
{$ENDIF}
Move(FState, Keystream, SizeOf(Keystream));
{$IFDEF CHACHA_DEBUG}
Write('
Keystream: ');
for J := 0
to 63
do
Write(Format('
%02x ', [Keystream[J]]));
WriteLn;
{$ENDIF}
PKeystream := @Keystream;
for J := 0
to 63
do
begin
if (I * 64 + J) >= Length(Input)
then Break;
Output[I * 64 + J] := Input[I * 64 + J]
xor PKeystream^;
Inc(PKeystream);
end;
end;
// erase memory
FillChar(FState, SizeOf(FState), 0);
FillChar(Keystream, SizeOf(Keystream), 0);
FillChar(OrigState, SizeOf(OrigState), 0);
end;
class procedure TChaCha.SelfTest;
const
KEY1: TChaChaKey = (
$00, $01, $02, $03, $04, $05, $06, $07,
$08, $09, $0a, $0b, $0c, $0d, $0e, $0f,
$10, $11, $12, $13, $14, $15, $16, $17,
$18, $19, $1a, $1b, $1c, $1d, $1e, $1f
);
NONCE1: TChaChaNonce = (
$00, $00, $00, $00, $00, $00, $00, $4a, $00, $00, $00, $00
);
PLAINTEXT1:
array[0..113]
of Byte = (
$4c, $61, $64, $69, $65, $73, $20, $61, $6e, $64, $20, $47, $65, $6e, $74, $6c,
$65, $6d, $65, $6e, $20, $6f, $66, $20, $74, $68, $65, $20, $63, $6c, $61, $73,
$73, $20, $6f, $66, $20, $27, $39, $39, $3a, $20, $49, $66, $20, $49, $20, $63,
$6f, $75, $6c, $64, $20, $6f, $66, $66, $65, $72, $20, $79, $6f, $75, $20, $6f,
$6e, $6c, $79, $20, $6f, $6e, $65, $20, $74, $69, $70, $20, $66, $6f, $72, $20,
$74, $68, $65, $20, $66, $75, $74, $75, $72, $65, $2c, $20, $73, $75, $6e, $73,
$63, $72, $65, $65, $6e, $20, $77, $6f, $75, $6c, $64, $20, $62, $65, $20, $69,
$74, $2e
);
EXPECTED_CIPHERTEXT1:
array[0..113]
of Byte = (
$6e, $2e, $35, $9a, $25, $68, $f9, $80, $41, $ba, $07, $28, $dd, $0d, $69, $81,
$e9, $7e, $7a, $
ec, $1d, $43, $60, $c2, $0a, $27, $af, $
cc, $fd, $9f, $ae, $0b,
$f9, $1b, $65, $c5, $52, $47, $33, $ab, $8f, $59, $3d, $ab, $cd, $62, $b3, $57,
$16, $39, $d6, $24, $e6, $51, $52, $ab, $8f, $53, $0c, $35, $9f, $08, $61, $d8,
$07, $ca, $0d, $bf, $50, $0d, $6a, $61, $56, $a3, $8e, $08, $8a, $22, $b6, $5e,
$52, $bc, $51, $4d, $16, $
cc, $f8, $06, $81, $8c, $e9, $1a, $b7, $79, $37, $36,
$5a, $f9, $0b, $bf, $74, $a3, $5b, $e6, $b4, $0b, $8e, $ed, $f2, $78, $5e, $42,
$87, $4d
);
KEY2: TChaChaKey = (
$00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $01
);
NONCE2: TChaChaNonce = (
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $02
);
PLAINTEXT2:
array[0..374]
of Byte = (
$41, $6e, $79, $20, $73, $75, $62, $6d, $69, $73, $73, $69, $6f, $6e, $20, $74,
$6f, $20, $74, $68, $65, $20, $49, $45, $54, $46, $20, $69, $6e, $74, $65, $6e,
$64, $65, $64, $20, $62, $79, $20, $74, $68, $65, $20, $43, $6f, $6e, $74, $72,
$69, $62, $75, $74, $6f, $72, $20, $66, $6f, $72, $20, $70, $75, $62, $6c, $69,
$63, $61, $74, $69, $6f, $6e, $20, $61, $73, $20, $61, $6c, $6c, $20, $6f, $72,
$20, $70, $61, $72, $74, $20, $6f, $66, $20, $61, $6e, $20, $49, $45, $54, $46,
$20, $49, $6e, $74, $65, $72, $6e, $65, $74, $2d, $44, $72, $61, $66, $74, $20,
$6f, $72, $20, $52, $46, $43, $20, $61, $6e, $64, $20, $61, $6e, $79, $20, $73,
$74, $61, $74, $65, $6d, $65, $6e, $74, $20, $6d, $61, $64, $65, $20, $77, $69,
$74, $68, $69, $6e, $20, $74, $68, $65, $20, $63, $6f, $6e, $74, $65, $78, $74,
$20, $6f, $66, $20, $61, $6e, $20, $49, $45, $54, $46, $20, $61, $63, $74, $69,
$76, $69, $74, $79, $20, $69, $73, $20, $63, $6f, $6e, $73, $69, $64, $65, $72,
$65, $64, $20, $61, $6e, $20, $22, $49, $45, $54, $46, $20, $43, $6f, $6e, $74,
$72, $69, $62, $75, $74, $69, $6f, $6e, $22, $2e, $20, $53, $75, $63, $68, $20,
$73, $74, $61, $74, $65, $6d, $65, $6e, $74, $73, $20, $69, $6e, $63, $6c, $75,
$64, $65, $20, $6f, $72, $61, $6c, $20, $73, $74, $61, $74, $65, $6d, $65, $6e,
$74, $73, $20, $69, $6e, $20, $49, $45, $54, $46, $20, $73, $65, $73, $73, $69,
$6f, $6e, $73, $2c, $20, $61, $73, $20, $77, $65, $6c, $6c, $20, $61, $73, $20,
$77, $72, $69, $74, $74, $65, $6e, $20, $61, $6e, $64, $20, $65, $6c, $65, $63,
$74, $72, $6f, $6e, $69, $63, $20, $63, $6f, $6d, $6d, $75, $6e, $69, $63, $61,
$74, $69, $6f, $6e, $73, $20, $6d, $61, $64, $65, $20, $61, $74, $20, $61, $6e,
$79, $20, $74, $69, $6d, $65, $20, $6f, $72, $20, $70, $6c, $61, $63, $65, $2c,
$20, $77, $68, $69, $63, $68, $20, $61, $72, $65, $20, $61, $64, $64, $72, $65,
$73, $73, $65, $64, $20, $74, $6f
);
EXPECTED_CIPHERTEXT2:
array[0..374]
of Byte = (
$a3, $
fb, $f0, $7d, $f3, $fa, $2f, $de, $4f, $37, $6c, $a2, $3e, $82, $73, $70,
$41, $60, $5d, $9f, $4f, $4f, $57, $bd, $8c, $ff, $2c, $1d, $4b, $79, $55, $
ec,
$2a, $97, $94, $8b, $d3, $72, $29, $15, $c8, $f3, $d3, $37, $f7, $d3, $70, $05,
$0e, $9e, $96, $d6, $47, $b7, $c3, $9f, $56, $e0, $31, $ca, $5e, $b6, $25, $0d,
$40, $42, $e0, $27, $85, $
ec, $
ec, $fa, $4b, $4b, $b5, $e8, $ea, $d0, $44, $0e,
$20, $b6, $e8, $
db, $09, $d8, $81, $a7, $c6, $13, $2f, $42, $0e, $52, $79, $50,
$42, $bd, $fa, $77, $73, $d8, $a9, $05, $14, $47, $b3, $29, $1c, $e1, $41, $1c,
$68, $04, $65, $55, $2a, $a6, $c4, $05, $b7, $76, $4d, $5e, $87, $be, $a8, $5a,
$d0, $0f, $84, $49, $ed, $8f, $72, $d0, $d6, $62, $ab, $05, $26, $91, $ca, $66,
$42, $4b, $c8, $6d, $2d, $f8, $0e, $a4, $1f, $43, $ab, $f9, $37, $d3, $25, $9d,
$c4, $b2, $d0, $
df, $b4, $8a, $6c, $91, $39, $dd, $d7, $f7, $69, $66, $e9, $28,
$e6, $35, $55, $3b, $a7, $6c, $5c, $87, $9d, $7b, $35, $d4, $9e, $b2, $e6, $2b,
$08, $71, $cd, $ac, $63, $89, $39, $e2, $5e, $8a, $1e, $0e, $f9, $d5, $28, $0f,
$a8, $ca, $32, $8b, $35, $1c, $3c, $76, $59, $89, $cb, $cf, $3d, $aa, $8b, $6c,
$
cc, $3a, $af, $9f, $39, $79, $c9, $2b, $37, $20, $fc, $88, $
dc, $95, $ed, $84,
$a1, $be, $05, $9c, $64, $99, $b9, $fd, $a2, $36, $e7, $e8, $18, $b0, $4b, $0b,
$c3, $9c, $1e, $87, $6b, $19, $3b, $fe, $55, $69, $75, $3f, $88, $12, $8c, $c0,
$8a, $aa, $9b, $63, $d1, $a1, $6f, $80, $ef, $25, $54, $d7, $18, $9c, $41, $1f,
$58, $69, $ca, $52, $c5, $b8, $3f, $a3, $6f, $f2, $16, $b9, $c1, $d3, $00, $62,
$be, $bc, $fd, $2d, $c5, $bc, $e0, $91, $19, $34, $fd, $a7, $9a, $86, $f6, $e6,
$98, $ce, $d7, $59, $c3, $ff, $9b, $64, $77, $33, $8f, $3d, $a4, $f9, $cd, $85,
$14, $ea, $99, $82, $
cc, $af, $b3, $41, $b2, $38, $4d, $d9, $02, $f3, $d1, $ab,
$7a, $c6, $1d, $d2, $9c, $6f, $21, $ba, $5b, $86, $2f, $37, $30, $e3, $7c, $fd,
$c4, $fd, $80, $6c, $22, $f2, $21
);
var
ChaCha: TChaCha;
Input, Output: TBytes;
I: Integer;
Pass: Boolean;
begin
ChaCha := TChaCha.Create;
try
SetLength(Input, Length(PLAINTEXT1));
Move(PLAINTEXT1, Input[0], Length(PLAINTEXT1));
ChaCha.Encrypt(KEY1, NONCE1, 1, cr20, Input, Output);
Pass := True;
if Length(Output) <> Length(EXPECTED_CIPHERTEXT1)
then
Pass := False
else
for I := 0
to Length(EXPECTED_CIPHERTEXT1) - 1
do
if Output[I] <> EXPECTED_CIPHERTEXT1[I]
then
begin
Pass := False;
WriteLn(Format('
Test Vector 1: Mismatch at byte %d: got %02x, expected %02x', [I, Output[I], EXPECTED_CIPHERTEXT1[I]]));
Break;
end;
if not Pass
then
raise Exception.Create('
ChaCha20 Test Vector 1 failed!');
SetLength(Input, Length(PLAINTEXT2));
Move(PLAINTEXT2, Input[0], Length(PLAINTEXT2));
ChaCha.Encrypt(KEY2, NONCE2, 1, cr20, Input, Output);
Pass := True;
if Length(Output) <> Length(EXPECTED_CIPHERTEXT2)
then
Pass := False
else
for I := 0
to Length(EXPECTED_CIPHERTEXT2) - 1
do
if Output[I] <> EXPECTED_CIPHERTEXT2[I]
then
begin
Pass := False;
WriteLn(Format('
Test Vector 2: Mismatch at byte %d: got %02x, expected %02x', [I, Output[I], EXPECTED_CIPHERTEXT2[I]]));
Break;
end;
if not Pass
then
raise Exception.Create('
ChaCha20 Test Vector 2 failed!');
SetLength(Input, Length(PLAINTEXT1));
Move(PLAINTEXT1, Input[0], Length(PLAINTEXT1));
ChaCha.Encrypt(KEY1, NONCE1, 1, cr8, Input, Output);
ChaCha.Encrypt(KEY1, NONCE1, 1, cr12, Input, Output);
WriteLn('
ChaCha self-test passed successfully.');
finally
ChaCha.Free;
end;
end;
end.