-
Notifications
You must be signed in to change notification settings - Fork 0
/
chacha20.pas
117 lines (94 loc) · 3.39 KB
/
chacha20.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
unit chacha20;
// ChaCha20 implementation for FPC
// Copyright (c) 2024 fibodevy https://github.com/fibodevy
// License: MIT
{$mode ObjFPC}{$H+}
// uncomment to use IETF version (96 bit nonce + 32 bit key instead of 64 bits for both)
//{$define IETF}
interface
type
chacha20state = record
state: array[0..15] of dword;
keystream: array[0..15] of dword;
position: dword;
end;
procedure chacha20_init(var state: chacha20state; key, nonce: string; counter: {$ifdef IETF}dword{$else}qword{$endif}=0);
procedure chacha20_set_counter(var state: chacha20state; counter: {$ifdef IETF}dword{$else}qword{$endif});
procedure chacha20_xor(var state: chacha20state; data: pointer; len: dword);
implementation
procedure chacha20_init(var state: chacha20state; key, nonce: string; counter: {$ifdef IETF}dword{$else}qword{$endif}=0);
const
magic = 'expand 32-byte k';
begin
fillchar(state, sizeof(state), 0);
// magic 16 bytes
move(magic[1], state.state[0], 16);
// key 32 bytes; if longer then cut it
if length(key) > 32 then setlength(key, 32);
if key <> '' then move(key[1], state.state[4], length(key));
// counter
chacha20_set_counter(state, counter);
// nonce 8 or 12 bytes; if longer then cut it
{$ifdef IETF}
if length(nonce) > 12 then setlength(nonce, 12);
if nonce <> '' then move(nonce[1], state.state[13], length(nonce));
{$else}
if length(nonce) > 8 then setlength(nonce, 8);
if nonce <> '' then move(nonce[1], state.state[14], length(nonce));
{$endif}
end;
procedure chacha20_set_counter(var state: chacha20state; counter: {$ifdef IETF}dword{$else}qword{$endif});
begin
move(counter, state.state[12], sizeof(counter));
state.position := 64;
end;
function rotl32(x, n: dword): dword; inline;
begin
result := (x shl n) or (x shr (32-n));
end;
procedure chacha20_quarterround(p: pdword; a, b, c, d: byte); inline;
begin
p[a] += p[b]; p[d] := rotl32(p[d] xor p[a], 16);
p[c] += p[d]; p[b] := rotl32(p[b] xor p[c], 12);
p[a] += p[b]; p[d] := rotl32(p[d] xor p[a], 8);
p[c] += p[d]; p[b] := rotl32(p[b] xor p[c], 7);
end;
procedure chacha20_next_block(var state: chacha20state);
var
i: integer;
begin
// copy state to keystream
move(state.state, state.keystream, 64);
// mix the bytes a lot and hope that nobody finds out how to undo it
for i := 1 to 10 do begin
chacha20_quarterround(@state.keystream, 0, 4, 8, 12);
chacha20_quarterround(@state.keystream, 1, 5, 9, 13);
chacha20_quarterround(@state.keystream, 2, 6, 10, 14);
chacha20_quarterround(@state.keystream, 3, 7, 11, 15);
chacha20_quarterround(@state.keystream, 0, 5, 10, 15);
chacha20_quarterround(@state.keystream, 1, 6, 11, 12);
chacha20_quarterround(@state.keystream, 2, 7, 8, 13);
chacha20_quarterround(@state.keystream, 3, 4, 9, 14);
end;
// add state dwords to keystream dwords
for i := 0 to high(state.keystream) do state.keystream[i] += state.state[i];
// increase counter
{$ifdef IETF}
pdword(@state.state[12])^ += 1;
{$else}
pqword(@state.state[12])^ += 1;
{$endif}
// reset position
state.position := 0;
end;
procedure chacha20_xor(var state: chacha20state; data: pointer; len: dword);
var
i: dword;
begin
for i := 0 to len-1 do begin
if state.position >= 64 then chacha20_next_block(state);
pbyte(data+i)^ := pbyte(data+i)^ xor pbyte(@state.keystream[0]+state.position)^;
inc(state.position);
end;
end;
end.