ChaCha20.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. unit ChaCha20;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. ChaCha20 and XChaCha20 encryption/decryption
  8. Initially based on https://github.com/Ginurx/chacha20-c/tree/master
  9. }
  10. interface
  11. type
  12. TChaCha20Ctx = array[0..15] of Cardinal;
  13. TChaCha20Context = record
  14. ctx, keystream: TChaCha20Ctx;
  15. position: 0..64;
  16. count64: Boolean;
  17. end;
  18. procedure ChaCha20Init(var Context: TChaCha20Context; const Key;
  19. const KeyLength: Cardinal; const Nonce; const NonceLength: Cardinal;
  20. const Count: Cardinal);
  21. procedure ChaCha20Crypt(var Context: TChaCha20Context; const InBuffer;
  22. var OutBuffer; const Length: Cardinal);
  23. procedure XChaCha20Init(var Context: TChaCha20Context; const Key;
  24. const KeyLength: Cardinal; const Nonce; const NonceLength: Cardinal;
  25. const Count: Cardinal);
  26. procedure XChaCha20Crypt(var Context: TChaCha20Context; const InBuffer;
  27. var OutBuffer; const Length: Cardinal);
  28. implementation
  29. uses
  30. System.SysUtils,
  31. UnsignedFunc;
  32. {$C+}
  33. procedure ChaCha20InitCtx(var ctx: TChaCha20Ctx; const Key;
  34. const KeyLength: Cardinal; const Nonce; const NonceLength: Cardinal;
  35. const Count: Cardinal);
  36. begin
  37. Assert(KeyLength = 32);
  38. Assert(NonceLength in [0, 8, 12]);
  39. {$IFDEF DEBUG}
  40. FillChar(ctx[0], SizeOf(ctx), 1);
  41. {$ENDIF}
  42. ctx[0] := $61707865;
  43. ctx[1] := $3320646e;
  44. ctx[2] := $79622d32;
  45. ctx[3] := $6b206574;
  46. UMove(Key, ctx[4], KeyLength);
  47. ctx[12] := Count;
  48. if NonceLength = 12 then
  49. Move(Nonce, ctx[13], 12)
  50. else if NonceLength = 8 then begin
  51. ctx[13] := 0;
  52. Move(Nonce, ctx[14], 8)
  53. end else
  54. FillChar(ctx[13], 12, 0);
  55. end;
  56. procedure ChaCha20Init(var Context: TChaCha20Context; const Key;
  57. const KeyLength: Cardinal; const Nonce; const NonceLength: Cardinal;
  58. const Count: Cardinal);
  59. begin
  60. ChaCha20InitCtx(Context.ctx, Key, KeyLength, Nonce, NonceLength, Count);
  61. Context.position := 64;
  62. Context.count64 := NonceLength <> 12;
  63. end;
  64. procedure ChaCha20RunRounds(var ctx, keystream: TChaCha20Ctx);
  65. function ROTL(const x: Cardinal; const n: Byte): Cardinal;
  66. begin
  67. Result := (x shl n) or (x shr (32 - n));
  68. end;
  69. procedure CHACHA20_QR(var a, b, c, d: Cardinal);
  70. begin
  71. Inc(a, b); d := d xor a; d := ROTL(d, 16);
  72. Inc(c, d); b := b xor c; b := ROTL(b, 12);
  73. Inc(a, b); d := d xor a; d := ROTL(d, 8);
  74. Inc(c, d); b := b xor c; b := ROTL(b, 7);
  75. end;
  76. begin
  77. Move(ctx, keystream, SizeOf(ctx));
  78. for var i := 0 to 9 do begin
  79. CHACHA20_QR(keystream[0], keystream[4], keystream[8], keystream[12]); // column 0
  80. CHACHA20_QR(keystream[1], keystream[5], keystream[9], keystream[13]); // column 1
  81. CHACHA20_QR(keystream[2], keystream[6], keystream[10], keystream[14]); // column 2
  82. CHACHA20_QR(keystream[3], keystream[7], keystream[11], keystream[15]); // column 3
  83. CHACHA20_QR(keystream[0], keystream[5], keystream[10], keystream[15]); // diagonal 1 (main diagonal)
  84. CHACHA20_QR(keystream[1], keystream[6], keystream[11], keystream[12]); // diagonal 2
  85. CHACHA20_QR(keystream[2], keystream[7], keystream[8], keystream[13]); // diagonal 3
  86. CHACHA20_QR(keystream[3], keystream[4], keystream[9], keystream[14]); // diagonal 4
  87. end;
  88. end;
  89. procedure ChaCha20Crypt(var Context: TChaCha20Context; const InBuffer;
  90. var OutBuffer; const Length: Cardinal);
  91. procedure ChaCha20BlockNext(var ctx, keystream: TChaCha20Ctx; const count64: Boolean);
  92. begin
  93. ChaCha20RunRounds(ctx, keystream);
  94. for var i := 0 to 15 do
  95. keystream[i] := keystream[i] + ctx[i];
  96. if count64 then begin
  97. if ctx[12] < High(Cardinal) then
  98. ctx[12] := ctx[12] + 1
  99. else begin
  100. ctx[12] := 0;
  101. Assert(ctx[13] < High(Cardinal));
  102. ctx[13] := ctx[13] + 1;
  103. end;
  104. end else begin
  105. Assert(ctx[12] < High(Cardinal));
  106. ctx[12] := ctx[12] + 1;
  107. end;
  108. end;
  109. begin
  110. if Length = 0 then
  111. Exit;
  112. var InBuf: PByte := @InBuffer;
  113. var OutBuf: PByte := @OutBuffer;
  114. var KeyStream := PByte(@Context.keystream);
  115. for var I := 0 to Length-1 do begin
  116. if Context.position >= 64 then begin
  117. ChaCha20BlockNext(Context.ctx, Context.keystream, Context.count64);
  118. Context.position := 0;
  119. end;
  120. OutBuf[I] := InBuf[I] xor KeyStream[Context.position];
  121. Inc(Context.position);
  122. end;
  123. end;
  124. procedure HChaCha20(const Key; const KeyLength: Cardinal; const Nonce;
  125. const NonceLength: Cardinal; out SubKey: TBytes);
  126. begin
  127. Assert(NonceLength = 16);
  128. var NonceBytes: PByte := @Nonce;
  129. var ctx: TChaCha20Ctx;
  130. ChaCha20InitCtx(ctx, Key, KeyLength, NonceBytes[4], 12, PCardinal(NonceBytes)^);
  131. var keystream: TChaCha20Ctx;
  132. ChaCha20RunRounds(ctx, keystream);
  133. SetLength(SubKey, 32);
  134. Move(keystream[0], SubKey[0], 16);
  135. Move(keystream[12], SubKey[16], 16);
  136. end;
  137. procedure XChaCha20Init(var Context: TChaCha20Context; const Key;
  138. const KeyLength: Cardinal; const Nonce; const NonceLength: Cardinal;
  139. const Count: Cardinal);
  140. begin
  141. Assert(NonceLength = 24);
  142. var SubKey: TBytes;
  143. HChaCha20(Key, KeyLength, Nonce, 16, SubKey);
  144. var NonceBytes: PByte := @Nonce;
  145. ChaCha20Init(Context, SubKey[0], ULength(SubKey), NonceBytes[16], 8, Count);
  146. end;
  147. procedure XChaCha20Crypt(var Context: TChaCha20Context; const InBuffer;
  148. var OutBuffer; const Length: Cardinal);
  149. begin
  150. ChaCha20Crypt(Context, InBuffer, OutBuffer, Length);
  151. end;
  152. {.$DEFINE TEST}
  153. {$IFDEF TEST}
  154. procedure TestChaCha20;
  155. begin
  156. //https://datatracker.ietf.org/doc/html/rfc7539#section-2.4.2
  157. var Buf: AnsiString := 'Ladies and Gentlemen of the class of ''99: If I could offer you only one tip for the future, sunscreen would be it.';
  158. var BufSize := Length(Buf)*SizeOf(Buf[1]);
  159. var Key: TBytes := [$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];
  160. var Nonce: TBytes := [$00, $00, $00, $00, $00, $00, $00, $4a, $00, $00, $00, $00];
  161. var Counter := 1;
  162. var Ctx: TChaCha20Context;
  163. ChaCha20Init(Ctx, Key[0], Length(Key), Nonce[0], Length(Nonce), Counter);
  164. ChaCha20Crypt(Ctx, Buf[1], Buf[1], 10);
  165. ChaCha20Crypt(Ctx, Buf[11], Buf[11], BufSize-10);
  166. var CipherText: TBytes := [$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];
  167. Assert(Length(Buf) = Length(CipherText));
  168. for var I := 0 to Length(Buf)-1 do
  169. Assert(Byte(Buf[I+1]) = CipherText[I]);
  170. end;
  171. procedure TestHChaCha20;
  172. begin
  173. //https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03#section-2.2.1
  174. var Key: TBytes := [$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];
  175. var Nonce: TBytes := [$00, $00, $00, $09, $00, $00, $00, $4a, $00, $00, $00, $00, $31, $41, $59, $27];
  176. var SubKey: TBytes;
  177. HChaCha20(Key[0], Length(Key), Nonce[0], Length(Nonce), SubKey);
  178. var ExpectedSubKey: TBytes := [$82, $41, $3b, $42, $27, $b2, $7b, $fe, $d3, $0e, $42, $50, $8a, $87, $7d, $73, $a0, $f9, $e4, $d5, $8a, $74, $a8, $53, $c1, $2e, $c4, $13, $26, $d3, $ec, $dc];
  179. Assert(Length(SubKey) = Length(ExpectedSubKey));
  180. for var I := 0 to Length(SubKey)-1 do
  181. Assert(SubKey[I] = ExpectedSubKey[I]);
  182. end;
  183. procedure TestXChaCha20;
  184. begin
  185. //https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03#appendix-A.2
  186. var Buf: AnsiString := 'The dhole (pronounced "dole") is also known as the Asiatic wild dog, red dog, and whistling dog.'+' It is about the size of a German shepherd but looks more like a long-legged fox. This highly elusive and skilled jumper is classified with wolves, coyotes, jackals, and foxes in the taxonomic family Canidae.';
  187. var BufSize := Length(Buf)*SizeOf(Buf[1]);
  188. var Key: TBytes := [$80, $81, $82, $83, $84, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8d, $8e, $8f, $90, $91, $92, $93, $94, $95, $96, $97, $98, $99, $9a, $9b, $9c, $9d, $9e, $9f];
  189. var Nonce: TBytes := [$40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4a, $4b, $4c, $4d, $4e, $4f, $50, $51, $52, $53, $54, $55, $56, $58];
  190. var Counter := 0;
  191. var Ctx: TChaCha20Context;
  192. XChaCha20Init(Ctx, Key[0], Length(Key), Nonce[0], Length(Nonce), Counter);
  193. XChaCha20Crypt(Ctx, Buf[1], Buf[1], BufSize);
  194. var CipherText: TBytes := [$45, $59, $ab, $ba, $4e, $48, $c1, $61, $02, $e8, $bb, $2c, $05, $e6, $94, $7f, $50, $a7, $86, $de, $16, $2f, $9b, $0b, $7e, $59, $2a, $9b, $53, $d0, $d4, $e9, $8d, $8d, $64, $10, $d5, $40, $a1, $a6, $37, $5b, $26, $d8, $0d, $ac, $e4, $fa, $b5, $23, $84, $c7, $31, $ac, $bf, $16, $a5, $92, $3c, $0c, $48, $d3, $57, $5d, $4d, $0d, $2c, $67, $3b, $66, $6f, $aa, $73, $10, $61, $27, $77, $01, $09, $3a, $6b, $f7, $a1, $58, $a8, $86, $42, $92, $a4, $1c, $48, $e3, $a9, $b4, $c0, $da, $ec, $e0, $f8, $d9, $8d, $0d, $7e, $05, $b3, $7a, $30, $7b, $bb, $66, $33, $31, $64, $ec, $9e, $1b, $24, $ea, $0d, $6c, $3f, $fd, $dc, $ec, $4f, $68, $e7, $44, $30, $56, $19, $3a, $03, $c8, $10, $e1, $13, $44, $ca, $06, $d8, $ed, $8a, $2b, $fb, $1e, $8d, $48, $cf, $a6, $bc, $0e, $b4, $e2, $46, $4b, $74, $81, $42, $40, $7c, $9f, $43, $1a, $ee, $76, $99, $60, $e1, $5b, $a8, $b9, $68, $90, $46, $6e, $f2, $45, $75, $99, $85, $23, $85, $c6, $61, $f7, $52, $ce, $20, $f9, $da, $0c, $09, $ab, $6b, $19, $df, $74, $e7, $6a, $95, $96, $74, $46, $f8, $d0, $fd, $41, $5e, $7b, $ee, $2a, $12, $a1, $14, $c2, $0e, $b5, $29, $2a, $e7, $a3, $49, $ae, $57, $78, $20, $d5, $52, $0a, $1f, $3f, $b6, $2a, $17, $ce, $6a, $7e, $68, $fa, $7c, $79, $11, $1d, $88, $60, $92, $0b, $c0, $48, $ef, $43, $fe, $84, $48, $6c, $cb, $87, $c2, $5f, $0a, $e0, $45, $f0, $cc, $e1, $e7, $98, $9a, $9a, $a2, $20, $a2, $8b, $dd, $48, $27, $e7, $51, $a2, $4a, $6d, $5c, $62, $d7, $90, $a6, $63, $93, $b9, $31, $11, $c1, $a5, $5d, $d7, $42, $1a, $10, $18, $49, $74, $c7, $c5];
  195. Assert(Length(Buf) = Length(CipherText));
  196. for var I := 0 to Length(Buf)-1 do
  197. Assert(Byte(Buf[I+1]) = CipherText[I]);
  198. end;
  199. initialization
  200. TestChaCha20;
  201. TestHChaCha20;
  202. TestXChaCha20;
  203. {$ENDIF}
  204. end.