{ /* * Argon2 reference source code package - reference C implementations * * Copyright 2015 * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves * * Pascal tranlastion in 2018 by Alexander Koblov (alexx2000@mail.ru) * * You may use this work under the terms of a Creative Commons CC0 1.0 * License/Waiver or the Apache Public License 2.0, at your option. The terms of * these licenses can be found at: * * - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 * - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 * * You should have received a copy of both of these licenses along with this * software. If not, they may be obtained at the above URLs. */ } unit Argon2; {$mode objfpc}{$H+} {.$define GENKAT} interface uses CTypes, DCblake2; const //* Number of synchronization points between lanes per pass */ ARGON2_SYNC_POINTS = cuint32(4); //* Flags to determine which fields are securely wiped (default = no wipe). */ ARGON2_DEFAULT_FLAGS = cuint32(0); ARGON2_FLAG_CLEAR_PASSWORD = (cuint32(1) shl 0); ARGON2_FLAG_CLEAR_SECRET = (cuint32(1) shl 1); const //* Error codes */ ARGON2_OK = 0; ARGON2_MEMORY_ALLOCATION_ERROR = -22; ARGON2_INCORRECT_PARAMETER = -25; ARGON2_THREAD_FAIL = -33; type Pargon2_context = ^Targon2_context; Targon2_context = record out_: pcuint8; //* output array */ outlen: cuint32; //* digest length */ pwd: pcuint8; //* password array */ pwdlen: cuint32; //* password length */ salt: pcuint8; //* salt array */ saltlen: cuint32; //* salt length */ secret: pcuint8; //* key array */ secretlen: cuint32; //* key length */ ad: pcuint8; //* associated data array */ adlen: cuint32; //* associated data length */ t_cost: cuint32; //* number of passes */ m_cost: cuint32; //* amount of memory requested (KB) */ lanes: cuint32; //* number of lanes */ threads: cuint32; //* maximum number of threads */ version: cuint32; //* version number */ flags: cuint32; //* array of bool options */ end; //* Argon2 primitive type */ Targon2_type = ( Argon2_d = 0, Argon2_i = 1, Argon2_id = 2 ); //* Version of the algorithm */ Targon2_version = ( ARGON2_VERSION_10 = $10, ARGON2_VERSION_13 = $13, ARGON2_VERSION_NUMBER = ARGON2_VERSION_13 ); function argon2d_kdf(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; hash: Pointer; const hashlen: csize_t): cint; function argon2id_kdf(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; hash: Pointer; const hashlen: csize_t): cint; function argon2_kdf(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; hash: Pointer; const hashlen: csize_t; type_: Targon2_type): cint; function argon2_hash(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; const secret: pansichar; const secretlen: csize_t; const ad: pansichar; const adlen: csize_t; hash: Pointer; const hashlen: csize_t; type_: Targon2_type; version: Targon2_version): cint; function argon2_selftest: Boolean; implementation {$R-}{$Q-} uses Math, Hash, SysUtils, StrUtils; //**********************Argon2 internal constants*******************************/ const //* Memory block size in bytes */ ARGON2_BLOCK_SIZE = 1024; ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE div 8; (* Number of pseudo-random values generated by one call to Blake in Argon2i to generate reference block positions *) ARGON2_ADDRESSES_IN_BLOCK = 128; //* Pre-hashing digest length and its extension*/ ARGON2_PREHASH_DIGEST_LENGTH = 64; ARGON2_PREHASH_SEED_LENGTH = 72; //*************************Argon2 internal data types***********************/ type (* * Structure for the (1KB) memory block implemented as 128 64-bit words. * Memory blocks can be copied, XORed. Internal words can be accessed by [] (no * bounds checking). *) Pblock = ^Tblock; Tblock = packed record v: packed array [0..ARGON2_QWORDS_IN_BLOCK-1] of cuint64; end; (* * Argon2 instance: memory pointer, number of passes, amount of memory, type, * and derived values. * Used to evaluate the number and location of blocks to construct in each * thread *) Pargon2_instance_t = ^Targon2_instance_t; Targon2_instance_t = record memory: Pblock; //* Memory pointer */ version: Targon2_version; passes: cuint32; //* Number of passes */ memory_blocks: cuint32; //* Number of blocks in memory */ segment_length: cuint32; lane_length: cuint32; lanes: cuint32; threads: cuint32; type_: Targon2_type; print_internals: cint; //* whether to print the memory blocks */ context_ptr: Pargon2_context; //* points back to original context */ end; (* * Argon2 position: where we construct the block right now. Used to distribute * work between threads. *) Pargon2_position_t = ^Targon2_position_t; Targon2_position_t = record pass: cuint32; lane: cuint32; slice: cuint8; index: cuint32; instance_ptr: Pargon2_instance_t; end; {$IFDEF GENKAT} procedure initial_kat(const blockhash: pcuint8; const context: Pargon2_context; type_: Targon2_type); var i: cuint32; begin if (blockhash <> nil) and (context <> nil) then begin WriteLn('======================================='); WriteLn(Format('%d version number %d', [type_, context^.version])); WriteLn('======================================='); WriteLn(Format('Memory: %u KiB, Iterations: %u, Parallelism: %u lanes, Tag length: %u bytes', [context^.m_cost, context^.t_cost, context^.lanes, context^.outlen])); Write(Format('Password[%u]: ', [context^.pwdlen])); if (context^.flags and ARGON2_FLAG_CLEAR_PASSWORD <> 0) then begin WriteLn('CLEARED'); end else begin for i := 0 to context^.pwdlen - 1 do Write(Format('%2.2x ', [context^.pwd[i]])); WriteLn; end; Write(Format('Salt[%u]: ', [context^.saltlen])); for i := 0 to context^.saltlen - 1 do begin Write(Format('%2.2x ', [context^.salt[i]])); end; WriteLn; (* printf("Secret[%u]: ", context->secretlen); if (context->flags & ARGON2_FLAG_CLEAR_SECRET) { printf("CLEARED\n"); } else { for (i = 0; i < context->secretlen; ++i) { printf("%2.2x ", ((unsigned char )context->secret)[i]); } printf("\n"); } printf("Associated data[%u]: ", context->adlen); for (i = 0; i < context->adlen; ++i) { printf("%2.2x ", ((unsigned char )context->ad)[i]); } printf("\n"); printf("Pre-hashing digest: "); for (i = 0; i < ARGON2_PREHASH_DIGEST_LENGTH; ++i) { printf("%2.2x ", ((unsigned char )blockhash)[i]); } printf("\n"); *) end; end; procedure print_tag(const out_: pcuint8; outlen: cuint32); var i: cuint32; begin if (out_ <> nil) then begin Write('Tag: '); for i := 0 to outlen - 1 do begin Write(Format('%2.2x ', [out_[i]])); end; WriteLn; end; end; procedure internal_kat(const instance: Pargon2_instance_t; pass: cuint32); var i, j: cuint32; how_many_words: cuint32; begin if (instance <> nil) then begin WriteLn(Format('After pass %u:', [pass])); for i := 0 to instance^.memory_blocks - 1 do begin how_many_words := IfThen(instance^.memory_blocks > ARGON2_QWORDS_IN_BLOCK, 1, ARGON2_QWORDS_IN_BLOCK); for j := 0 to how_many_words - 1 do WriteLn(Format('Block %.4u [%3u]: %s', [i, j, HexStr(instance^.memory[i].v[j], 16)])); end; end; end; {$ENDIF} function load32( const src: Pointer ): cuint32; inline; begin Result := NtoLE(pcuint32(src)^); end; function load64( const src: pointer ): cuint64; inline; begin Result := NtoLE(pcuint64(src)^); end; procedure store32( dst: pointer; w: cuint32 ); inline; begin pcuint32(dst)^ := LEtoN(w); end; procedure store64( dst: pointer; w: cuint64 ); inline; begin pcuint64(dst)^ := LEtoN(w); end; //* designed by the Lyra PHC team */ function fBlaMka(x, y: cuint64): cuint64; inline; const m = cuint64($FFFFFFFF); begin Result:= x + y + 2 * ((x and m) * (y and m)); end; procedure G(var a, b, c, d: cuint64); inline; begin a := fBlaMka(a, b); d := RorQWord(d xor a, 32); c := fBlaMka(c, d); b := RorQWord(b xor c, 24); a := fBlaMka(a, b); d := RorQWord(d xor a, 16); c := fBlaMka(c, d); b := RorQWord(b xor c, 63); end; procedure BLAKE2_ROUND_NOMSG(var v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15: cuint64); inline; begin G(v0, v4, v8, v12); G(v1, v5, v9, v13); G(v2, v6, v10, v14); G(v3, v7, v11, v15); G(v0, v5, v10, v15); G(v1, v6, v11, v12); G(v2, v7, v8, v13); G(v3, v4, v9, v14); end; //***************Instance and Position constructors**********/ procedure init_block_value(b: Pblock; in_: cuint8); inline; begin FillChar(b^, SizeOf(Tblock), in_); end; procedure copy_block(dst: Pblock; const src: Pblock); inline; begin Move(src^, dst^, SizeOf(Tblock)); end; procedure xor_block(dst: Pblock; const src: Pblock); var i: cint; begin for i := 0 to ARGON2_QWORDS_IN_BLOCK - 1 do dst^.v[i] := dst^.v[i] xor src^.v[i]; end; procedure load_block(dst: Pblock; const input: PByte); inline; begin Move(input^, dst^, SizeOf(Tblock)); end; procedure store_block(output: PByte; const src: Pblock); inline; begin Move(src^, output^, SizeOf(Tblock)); end; //***************Memory functions*****************/ procedure secure_wipe_memory(v: Pointer; n: csize_t); {$OPTIMIZATION OFF} begin FillChar(v^, n, 0); end; {$OPTIMIZATION DEFAULT} procedure clear_internal_memory(v: Pointer; n: csize_t); begin if (v <> nil) then secure_wipe_memory(v, n); end; function allocate_memory(memory: PPByte; num, size: csize_t): cint; var memory_size: csize_t; begin memory_size := num * size; if (memory = nil) then begin Exit(ARGON2_MEMORY_ALLOCATION_ERROR); end; //* Check for multiplication overflow */ if (size <> 0) and (memory_size div size <> num) then begin Exit(ARGON2_MEMORY_ALLOCATION_ERROR); end; memory^ := GetMem(memory_size); if (memory^ = nil) then begin Exit(ARGON2_MEMORY_ALLOCATION_ERROR); end; Result:= ARGON2_OK; end; procedure free_memory(memory: pcuint8; num, size: csize_t); var memory_size: csize_t; begin memory_size := num * size; clear_internal_memory(memory, memory_size); FreeMem(memory); end; (* * Function fills a new memory block and optionally XORs the old block over the new one. * @next_block must be initialized. * @param prev_block Pointer to the previous block * @param ref_block Pointer to the reference block * @param next_block Pointer to the block to be constructed * @param with_xor Whether to XOR into the new block (1) or just overwrite (0) * @pre all block pointers must be valid *) procedure fill_block(const prev_block: Pblock; const ref_block: Pblock; next_block: Pblock; with_xor: boolean); var i: cuint32; blockR, block_tmp: Tblock; begin copy_block(@blockR, ref_block); xor_block(@blockR, prev_block); copy_block(@block_tmp, @blockR); //* Now blockR = ref_block + prev_block and block_tmp = ref_block + prev_block */ if (with_xor) then begin //* Saving the next block contents for XOR over: */ xor_block(@block_tmp, next_block); (* Now blockR = ref_block + prev_block and block_tmp = ref_block + prev_block + next_block *) end; (* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then (16,17,..31)... finally (112,113,...127) *) for i := 0 to 7 do begin BLAKE2_ROUND_NOMSG( blockR.v[16 * i], blockR.v[16 * i + 1], blockR.v[16 * i + 2], blockR.v[16 * i + 3], blockR.v[16 * i + 4], blockR.v[16 * i + 5], blockR.v[16 * i + 6], blockR.v[16 * i + 7], blockR.v[16 * i + 8], blockR.v[16 * i + 9], blockR.v[16 * i + 10], blockR.v[16 * i + 11], blockR.v[16 * i + 12], blockR.v[16 * i + 13], blockR.v[16 * i + 14], blockR.v[16 * i + 15]); end; (* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then (2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) *) for i := 0 to 7 do begin BLAKE2_ROUND_NOMSG( blockR.v[2 * i], blockR.v[2 * i + 1], blockR.v[2 * i + 16], blockR.v[2 * i + 17], blockR.v[2 * i + 32], blockR.v[2 * i + 33], blockR.v[2 * i + 48], blockR.v[2 * i + 49], blockR.v[2 * i + 64], blockR.v[2 * i + 65], blockR.v[2 * i + 80], blockR.v[2 * i + 81], blockR.v[2 * i + 96], blockR.v[2 * i + 97], blockR.v[2 * i + 112], blockR.v[2 * i + 113]); end; copy_block(next_block, @block_tmp); xor_block(next_block, @blockR); end; function blake2b(out_: pcuint8; outlen: csize_t; const in_: pcuint8; inlen: csize_t): cint; var S: blake2b_state; begin if (blake2b_init(@S, outlen) = 0) then begin blake2b_update(@S, in_, inlen); blake2b_final(@S, out_, outlen); Exit(0); end; Result:= -1; end; procedure blake2b_long(pout: pointer; outlen: csize_t; const in_: pointer; inlen: csize_t); var out_: pcuint8; toproduce: cuint32; blake_state: blake2b_state; outlen_bytes: array [0..sizeof(cuint32)-1] of cuint8; in_buffer: array[0..Pred(BLAKE2B_OUTBYTES)] of cuint8; out_buffer: array[0..Pred(BLAKE2B_OUTBYTES)] of cuint8; begin out_:= pout; //* Ensure little-endian byte order! */ store32(@outlen_bytes[0], cuint32(outlen)); if (outlen <= BLAKE2B_OUTBYTES) then begin blake2b_init(@blake_state, outlen); blake2b_update(@blake_state, outlen_bytes, sizeof(outlen_bytes)); blake2b_update(@blake_state, in_, inlen); blake2b_final(@blake_state, out_, outlen); end else begin blake2b_init(@blake_state, BLAKE2B_OUTBYTES); blake2b_update(@blake_state, outlen_bytes, sizeof(outlen_bytes)); blake2b_update(@blake_state, in_, inlen); blake2b_final(@blake_state, out_buffer, BLAKE2B_OUTBYTES); Move(out_buffer[0], out_^, BLAKE2B_OUTBYTES div 2); out_ += BLAKE2B_OUTBYTES div 2; toproduce := cuint32(outlen) - BLAKE2B_OUTBYTES div 2; while (toproduce > BLAKE2B_OUTBYTES) do begin Move(out_buffer[0], in_buffer[0], BLAKE2B_OUTBYTES); blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer, BLAKE2B_OUTBYTES); Move(out_buffer[0], out_^, BLAKE2B_OUTBYTES div 2); out_ += BLAKE2B_OUTBYTES div 2; toproduce -= BLAKE2B_OUTBYTES div 2; end; Move(out_buffer[0], in_buffer[0], BLAKE2B_OUTBYTES); blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES); Move(out_buffer[0], out_^, toproduce); end; clear_internal_memory(@blake_state, sizeof(blake_state)); end; procedure next_addresses(address_block, input_block: Pblock; const zero_block: Pblock); begin Inc(input_block^.v[6]); fill_block(zero_block, input_block, address_block, false); fill_block(zero_block, address_block, address_block, false); end; function index_alpha(const instance: Pargon2_instance_t; const position: Pargon2_position_t; pseudo_rand: cuint32; same_lane: boolean): cuint32; var reference_area_size: cuint32; relative_position: cuint64; start_position, absolute_position: cuint32; begin (* * Pass 0: * This lane : all already finished segments plus already constructed * blocks in this segment * Other lanes : all already finished segments * Pass 1+: * This lane : (SYNC_POINTS - 1) last segments plus already constructed * blocks in this segment * Other lanes : (SYNC_POINTS - 1) last segments *) if (0 = position^.pass) then begin //* First pass */ if (0 = position^.slice) then begin //* First slice */ reference_area_size := position^.index - 1; //* all but the previous */ end else begin if (same_lane) then begin //* The same lane => add current segment */ reference_area_size := position^.slice * instance^.segment_length + position^.index - 1; end else begin reference_area_size := position^.slice * instance^.segment_length + IfThen((position^.index = 0), (-1), 0); end; end end else begin //* Second pass */ if (same_lane) then begin reference_area_size := instance^.lane_length - instance^.segment_length + position^.index - 1; end else begin reference_area_size := instance^.lane_length - instance^.segment_length + IfThen((position^.index = 0), (-1), 0); end; end; (* 1.2.4. Mapping pseudo_rand to 0.. and produce * relative position *) relative_position := pseudo_rand; relative_position := relative_position * relative_position shr 32; relative_position := reference_area_size - 1 - (reference_area_size * relative_position shr 32); //* 1.2.5 Computing starting position */ start_position := 0; if (0 <> position^.pass) then begin start_position := IfThen(position^.slice = ARGON2_SYNC_POINTS - 1, 0, (position^.slice + 1) * instance^.segment_length); end; //* 1.2.6. Computing absolute position */ absolute_position := (start_position + relative_position) mod instance^.lane_length; //* absolute position */ Result:= absolute_position; end; function fill_segment(Data: Pointer): PtrInt; var ref_block: Pblock = nil; curr_block: Pblock = nil; address_block, input_block, zero_block: Tblock; pseudo_rand, ref_index, ref_lane: cuint64; prev_offset, curr_offset: cuint32; starting_index: cuint32; i: cuint32; data_independent_addressing: boolean; position: Targon2_position_t; instance: Pargon2_instance_t absolute position.instance_ptr; begin Result:= 0; if (Data = nil) then Exit; position := Pargon2_position_t(Data)^; data_independent_addressing := (instance^.type_ = Argon2_i) or ((instance^.type_ = Argon2_id) and (position.pass = 0) and (position.slice < ARGON2_SYNC_POINTS div 2)); if (data_independent_addressing) then begin init_block_value(@zero_block, 0); init_block_value(@input_block, 0); input_block.v[0] := position.pass; input_block.v[1] := position.lane; input_block.v[2] := position.slice; input_block.v[3] := instance^.memory_blocks; input_block.v[4] := instance^.passes; input_block.v[5] := cuint64(instance^.type_); end; position.index := 0; starting_index := 0; if ((0 = position.pass) and (0 = position.slice)) then begin starting_index := 2; //* we have already generated the first two blocks */ //* Don't forget to generate the first block of addresses: */ if (data_independent_addressing) then begin next_addresses(@address_block, @input_block, @zero_block); end; end; //* Offset of the current block */ curr_offset := position.lane * instance^.lane_length + position.slice * instance^.segment_length + starting_index; if (0 = curr_offset mod instance^.lane_length) then begin //* Last block in this lane */ prev_offset := curr_offset + instance^.lane_length - 1; end else begin //* Previous block */ prev_offset := curr_offset - 1; end; for i := starting_index to instance^.segment_length - 1 do begin //*1.1 Rotating prev_offset if needed */ if (curr_offset mod instance^.lane_length = 1) then begin prev_offset := curr_offset - 1; end; //* 1.2 Computing the index of the reference block */ //* 1.2.1 Taking pseudo-random value from the previous block */ if (data_independent_addressing) then begin if (i mod ARGON2_ADDRESSES_IN_BLOCK = 0) then begin next_addresses(@address_block, @input_block, @zero_block); end; pseudo_rand := address_block.v[i mod ARGON2_ADDRESSES_IN_BLOCK]; end else begin pseudo_rand := instance^.memory[prev_offset].v[0]; end; //* 1.2.2 Computing the lane of the reference block */ ref_lane := ((pseudo_rand shr 32)) mod instance^.lanes; if ((position.pass = 0) and (position.slice = 0)) then begin //* Can not reference other lanes yet */ ref_lane := position.lane; end; //* 1.2.3 Computing the number of possible reference block within the lane. */ position.index := i; ref_index := index_alpha(instance, @position, pseudo_rand and $FFFFFFFF, ref_lane = position.lane); //* 2 Creating a new block */ ref_block := instance^.memory + instance^.lane_length * ref_lane + ref_index; curr_block := instance^.memory + curr_offset; if (ARGON2_VERSION_10 = instance^.version) then begin //* version 1.2.1 and earlier: overwrite, not XOR */ fill_block(instance^.memory + prev_offset, ref_block, curr_block, false); end else begin if (0 = position.pass) then begin fill_block(instance^.memory + prev_offset, ref_block, curr_block, false); end else begin fill_block(instance^.memory + prev_offset, ref_block, curr_block, true); end; end; Inc(curr_offset); Inc(prev_offset); end; end; procedure finalize(const context: Pargon2_context; instance: Pargon2_instance_t); var l: cuint32; blockhash: Tblock; last_block_in_lane: cuint32; blockhash_bytes: array [0..ARGON2_BLOCK_SIZE-1] of cuint8; begin if (context <> nil) and (instance <> nil) then begin copy_block(@blockhash, instance^.memory + instance^.lane_length - 1); //* XOR the last blocks */ for l := 1 to instance^.lanes - 1 do begin last_block_in_lane := l * instance^.lane_length + (instance^.lane_length - 1); xor_block(@blockhash, instance^.memory + last_block_in_lane); end; //* Hash the result */ begin store_block(@blockhash_bytes[0], @blockhash); blake2b_long(context^.out_, context^.outlen, @blockhash_bytes[0], ARGON2_BLOCK_SIZE); //* clear blockhash and blockhash_bytes */ clear_internal_memory(@blockhash.v[0], ARGON2_BLOCK_SIZE); clear_internal_memory(@blockhash_bytes[0], ARGON2_BLOCK_SIZE); end; {$IFDEF GENKAT} print_tag(context^.out_, context^.outlen); {$ENDIF} free_memory(pcuint8(instance^.memory), instance^.memory_blocks, sizeof(Tblock)); end; end; //* Single-threaded version for p=1 case */ function fill_memory_blocks_st(instance: Pargon2_instance_t): cint; var r, s, l: cuint32; position: Targon2_position_t; begin position.instance_ptr:= instance; for r := 0 to instance^.passes - 1 do begin position.pass:= r; for s := 0 to ARGON2_SYNC_POINTS - 1 do begin position.slice:= s; for l:= 0 to instance^.lanes - 1 do begin position.lane:= l; fill_segment(@position); end; end; {$IFDEF GENKAT} internal_kat(instance, r); //* Print all memory blocks */ {$ENDIF} end; Result:= ARGON2_OK; end; //* Multi-threaded version for p > 1 case */ function fill_memory_blocks_mt(instance: Pargon2_instance_t): cint; var r, s, l, ll: cuint32; threads: array of TThreadID; positions: array of Targon2_position_t; begin // 1. Allocating space for threads SetLength(threads, instance^.lanes); SetLength(positions, instance^.lanes); for r := 0 to instance^.passes - 1 do begin for s := 0 to ARGON2_SYNC_POINTS - 1 do begin // 2. Calling threads for l:= 0 to instance^.lanes - 1 do begin positions[l].pass:= r; positions[l].lane:= l; positions[l].slice:= s; positions[l].instance_ptr:= instance; threads[l]:= BeginThread(@fill_segment, @positions[l]); if (threads[l] = TThreadID(0)) then begin // Wait for already running threads for ll:= 0 to l - 1 do begin WaitForThreadTerminate(threads[ll], -1); CloseThread(threads[ll]); end; Exit(ARGON2_THREAD_FAIL); end; end; // 3. Joining remaining threads for l := instance^.lanes - instance^.threads to instance^.lanes - 1 do begin WaitForThreadTerminate(threads[l], -1); CloseThread(threads[l]); end; end; {$IFDEF GENKAT} internal_kat(instance, r); //* Print all memory blocks */ {$ENDIF} end; Result:= ARGON2_OK; end; function fill_memory_blocks(instance: Pargon2_instance_t): cint; begin if (instance = nil) or (instance^.lanes = 0) then begin Exit(ARGON2_INCORRECT_PARAMETER); end; if (instance^.threads > 1) then Result:= fill_memory_blocks_mt(instance) else begin Result:= fill_memory_blocks_st(instance); end; end; procedure fill_first_blocks(blockhash: pcuint8; const instance: pargon2_instance_t); var l: cuint32; blockhash_bytes: array[0..ARGON2_BLOCK_SIZE-1] of cuint8; begin //* Make the first and second block in each lane as G(H0||0||i) or G(H0||1||i) */ for l := 0 to instance^.lanes - 1 do begin store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0); store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l); blake2b_long(@blockhash_bytes[0], ARGON2_BLOCK_SIZE, blockhash, ARGON2_PREHASH_SEED_LENGTH); load_block(@instance^.memory[l * instance^.lane_length + 0], blockhash_bytes); store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1); blake2b_long(@blockhash_bytes[0], ARGON2_BLOCK_SIZE, blockhash, ARGON2_PREHASH_SEED_LENGTH); load_block(@instance^.memory[l * instance^.lane_length + 1], blockhash_bytes); end; clear_internal_memory(@blockhash_bytes[0], ARGON2_BLOCK_SIZE); end; procedure initial_hash(blockhash: pcuint8; context: Pargon2_context; type_: Targon2_type); var BlakeHash: blake2b_state; value: array[0..sizeof(cuint32)-1] of cuint8; begin if (nil = context) or (nil = blockhash) then Exit; blake2b_init(@BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH); store32(@value[0], context^.lanes); blake2b_update(@BlakeHash, @value[0], sizeof(value)); store32(@value[0], context^.outlen); blake2b_update(@BlakeHash, @value[0], sizeof(value)); store32(@value[0], context^.m_cost); blake2b_update(@BlakeHash, @value[0], sizeof(value)); store32(@value[0], context^.t_cost); blake2b_update(@BlakeHash, @value[0], sizeof(value)); store32(@value[0], context^.version); blake2b_update(@BlakeHash, @value[0], sizeof(value)); store32(@value[0], cuint32(type_)); blake2b_update(@BlakeHash, @value[0], sizeof(value)); store32(@value[0], context^.pwdlen); blake2b_update(@BlakeHash, @value[0], sizeof(value)); if (context^.pwd <> nil) then begin blake2b_update(@BlakeHash, context^.pwd, context^.pwdlen); if (context^.flags and ARGON2_FLAG_CLEAR_PASSWORD <> 0) then begin secure_wipe_memory(context^.pwd, context^.pwdlen); context^.pwdlen := 0; end; end; store32(@value[0], context^.saltlen); blake2b_update(@BlakeHash, @value[0], sizeof(value)); if (context^.salt <> nil) then begin blake2b_update(@BlakeHash, context^.salt, context^.saltlen); end; store32(@value[0], context^.secretlen); blake2b_update(@BlakeHash, @value[0], sizeof(value)); if (context^.secret <> nil) then begin blake2b_update(@BlakeHash, context^.secret, context^.secretlen); if (context^.flags and ARGON2_FLAG_CLEAR_SECRET <> 0) then begin secure_wipe_memory(context^.secret, context^.secretlen); context^.secretlen := 0; end; end; store32(@value[0], context^.adlen); blake2b_update(@BlakeHash, @value[0], sizeof(value)); if (context^.ad <> nil) then begin blake2b_update(@BlakeHash, context^.ad, context^.adlen); end; blake2b_final(@BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH); end; function initialize(instance: Pargon2_instance_t; context: Pargon2_context): cint; var blockhash: array[0..ARGON2_PREHASH_SEED_LENGTH-1] of cuint8; begin instance^.context_ptr := context; //* 1. Memory allocation */ result := allocate_memory(@(instance^.memory), instance^.memory_blocks, sizeof(Tblock)); if (result <> ARGON2_OK) then Exit; (* 2. Initial hashing */ /* H_0 + 8 extra bytes to produce the first blocks */ /* uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH]; */ /* Hashing all inputs *) initial_hash(blockhash, context, instance^.type_); //* Zeroing 8 extra bytes */ clear_internal_memory(@blockhash[ARGON2_PREHASH_DIGEST_LENGTH], ARGON2_PREHASH_SEED_LENGTH - ARGON2_PREHASH_DIGEST_LENGTH); {$IFDEF GENKAT} initial_kat(blockhash, context, instance^.type_); {$ENDIF} //* 3. Creating first blocks, we always have at least two blocks in a slice */ fill_first_blocks(blockhash, instance); //* Clearing the hash */ clear_internal_memory(@blockhash[0], ARGON2_PREHASH_SEED_LENGTH); Result:= ARGON2_OK; end; function argon2_ctx(context: Pargon2_context; type_: Targon2_type): cint; var memory_blocks, segment_length: cuint32; instance: Targon2_instance_t; begin (* //* 1. Validate all inputs */ int result = validate_inputs(context); if (ARGON2_OK != result) { return result; } if (Argon2_d != type && Argon2_i != type && Argon2_id != type) { return ARGON2_INCORRECT_TYPE; } *) //* 2. Align memory size */ //* Minimum memory_blocks = 8L blocks, where L is the number of lanes */ memory_blocks := context^.m_cost; if (memory_blocks < 2 * ARGON2_SYNC_POINTS * context^.lanes) then begin memory_blocks := 2 * ARGON2_SYNC_POINTS * context^.lanes; end; segment_length := memory_blocks div (context^.lanes * ARGON2_SYNC_POINTS); //* Ensure that all segments have equal length */ memory_blocks := segment_length * (context^.lanes * ARGON2_SYNC_POINTS); instance.version := Targon2_version(context^.version); instance.memory := nil; instance.passes := context^.t_cost; instance.memory_blocks := memory_blocks; instance.segment_length := segment_length; instance.lane_length := segment_length * ARGON2_SYNC_POINTS; instance.lanes := context^.lanes; instance.threads := context^.threads; instance.type_ := type_; if (instance.threads > instance.lanes) then begin instance.threads := instance.lanes; end; //* 3. Initialization: Hashing inputs, allocating memory, filling first blocks */ result := initialize(@instance, context); if (ARGON2_OK <> result) then Exit; //* 4. Filling memory */ result := fill_memory_blocks(@instance); if (ARGON2_OK <> result) then Exit; //* 5. Finalization */ finalize(context, @instance); Result:= ARGON2_OK; end; function argon2_hash(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; const secret: pansichar; const secretlen: csize_t; const ad: pansichar; const adlen: csize_t; hash: Pointer; const hashlen: csize_t; type_: Targon2_type; version: Targon2_version): cint; var context: Targon2_context; begin (* if (pwdlen > ARGON2_MAX_PWD_LENGTH) { return ARGON2_PWD_TOO_LONG; } if (saltlen > ARGON2_MAX_SALT_LENGTH) { return ARGON2_SALT_TOO_LONG; } if (hashlen > ARGON2_MAX_OUTLEN) { return ARGON2_OUTPUT_TOO_LONG; } if (hashlen < ARGON2_MIN_OUTLEN) { return ARGON2_OUTPUT_TOO_SHORT; } *) context.out_ := GetMem(hashlen); if (context.out_ = nil) then begin Exit(ARGON2_MEMORY_ALLOCATION_ERROR); end; context.outlen := cuint32(hashlen); context.pwd := pcuint8(pwd); context.pwdlen := cuint32(pwdlen); context.salt := pcuint8(salt); context.saltlen := cuint32(saltlen); context.secret := pcuint8(secret); context.secretlen := secretlen; context.ad := pcuint8(ad); context.adlen := adlen; context.t_cost := t_cost; context.m_cost := m_cost; context.lanes := parallelism; context.threads := parallelism; context.flags := ARGON2_DEFAULT_FLAGS; context.version := cuint32(version); result := argon2_ctx(@context, type_); //* if raw hash requested, write it */ if (result = ARGON2_OK) and (hash <> nil) then begin Move(context.out_^, hash^, hashlen); end; clear_internal_memory(context.out_, hashlen); FreeMem(context.out_); end; function argon2d_kdf(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; hash: Pointer; const hashlen: csize_t): cint; inline; begin Result:= argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, nil, 0, nil, 0, hash, hashlen, Argon2_d, ARGON2_VERSION_NUMBER); end; function argon2id_kdf(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; hash: Pointer; const hashlen: csize_t): cint; inline; begin Result:= argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, nil, 0, nil, 0, hash, hashlen, Argon2_id, ARGON2_VERSION_NUMBER); end; function argon2_kdf(const t_cost, m_cost, parallelism: cuint32; const pwd: pansichar; const pwdlen: csize_t; const salt: pansichar; const saltlen: csize_t; hash: Pointer; const hashlen: csize_t; type_: Targon2_type): cint; inline; begin Result:= argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, nil, 0, nil, 0, hash, hashlen, type_, ARGON2_VERSION_NUMBER); end; function argon2_selftest: Boolean; function hash_test(version: Targon2_version; type_: Targon2_type; t, m, p: cuint32; pwd, salt, hex: String): Boolean; const AName: array[Targon2_type] of String = ('Argon2d', 'Argon2i', 'Argon2id'); var Q: QWord; ret: Integer; out_: String; out_hex: String; out_len: Integer; begin WriteLn(AName[type_]); out_len:= Length(hex) div 2; WriteLn(Format('Hash test: $v=%d t=%d, m=%d, p=%d, pass=%s, salt=%s, result=%d', [version, t, m, p, pwd, salt, out_len])); SetLength(out_, out_len); Q:= GetTickCount64; ret:= argon2_hash(t, 1 shl m, p, Pointer(pwd), Length(pwd), Pointer(salt), Length(salt), nil, 0, nil, 0, Pointer(out_), OUT_LEN, type_, version); if (ret <> ARGON2_OK) then begin WriteLn('Error: ', ret); Exit(False); end; WriteLn('Time: ', GetTickCount64 - Q); SetLength(out_hex, OUT_LEN * 2); BinToHex(PAnsiChar(out_), PAnsiChar(out_hex), OUT_LEN); Result:= SameText(hex, out_hex); WriteLn('Must: ', hex); WriteLn('Have: ', out_hex); WriteLn('Result: ', Result); WriteLn('------------------------------------------------------------'); end; begin Result:= True; // Test Argon2i Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_i, 2, 16, 1, 'password', 'somesalt', 'c1628832147d9720c5bd1cfd61367078729f6dfb6f8fea9ff98158e0d7816ed0'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_i, 2, 16, 1, 'password', 'diffsalt', 'b0357cccfbef91f3860b0dba447b2348cbefecadaf990abfe9cc40726c521271'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_i, 2, 16, 2, 'The quick brown fox jumps over the lazy dog', '09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7', '81f1ba863be362444e3a22feca1d65e4d0ff53609ef9db5961d715552d38ac0d'); // Test Argon2d Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_d, 2, 16, 1, 'password', 'somesalt', '955e5d5b163a1b60bba35fc36d0496474fba4f6b59ad53628666f07fb2f93eaf'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_d, 4, 17, 4, 'The quick brown fox jumps over the lazy dog', '49d91010f3cadfca4964a1305132537e28a195cf7b0823763fa34d190f9b2559', '595193668d0ae6169235017f58d2a197d9cc485af5cb8f26357d95ee7eb991c4'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_d, 10, 16, 4, 'The quick brown fox jumps over the lazy dog', '49d91010f3cadfca4964a1305132537e28a195cf7b0823763fa34d190f9b2559', '49101d42bd15dc1559bfd978753ac957c239b2f6184b8de2042e03fdd4b6676c'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_d, 6, 17, 4, 'The quick brown fox jumps over the lazy dog', '49d91010f3cadfca4964a1305132537e28a195cf7b0823763fa34d190f9b2559', '13ea5db0e564b8719f7f3fc55559b8ca224dd063256f53051dd5bb682b48b5ac'); // Test Argon2id Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_id, 2, 16, 1, 'password', 'somesalt', '09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_id, 2, 16, 1, 'password', 'diffsalt', 'bdf32b05ccc42eb15d58fd19b1f856b113da1e9a5874fdcc544308565aa8141c'); Result:= Result and hash_test(ARGON2_VERSION_NUMBER, Argon2_id, 2, 16, 2, 'password', 'somesalt', '6f681ac1c3384a90119d2763a683f9ac79532d999abfab5644aa8aafd3d0d234'); // Print result WriteLn('Result: ', Result); end; end.