1 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r
2 For conditions of distribution and use, see copyright notice in zlib_license.txt
\r
3 which is included in the package
\r
4 ----------------------------------------------------------------------------- }
\r
12 {$include lcoreconfig.inc}
\r
15 written by Bas Steendijk (beware)
\r
17 the aim of this unit is to provide randomness in a consistent way, using OS specific methods for seeding
\r
19 this unit uses MD5 for performance and code size, but it is made so it is easy to use a different hash,
\r
20 as long as it is at least 128 bits, and a multiple of the "word size" (32 bits)
\r
24 - for the code to be:
\r
25 - relatively simple and small
\r
28 - for the numbers to be
\r
29 - random: pass diehard and similar tests
\r
30 - unique: generate UUIDs
\r
31 - secure: difficult for a remote attacker to guess the internal state, even
\r
32 when given some output
\r
34 typical intended uses:
\r
35 - anything that needs random numbers without extreme demands on security or
\r
36 speed should be able to use this
\r
37 - seeding other (faster) RNGs
\r
38 - generation of passwords, UUIDs, cookies, and session keys
\r
39 - randomizing protocol fields to protect against spoofing attacks
\r
40 - randomness for games
\r
42 this is not intended to be directly used for:
\r
43 - high security purposes (generating RSA root keys etc)
\r
44 - needing random numbers at very high rates (disk wiping, some simulations, etc)
\r
47 - 24 MB/s on 2.2 GHz athlon64 core on windows XP 32 bits
\r
48 - 6.4 MB/s on 1 GHz p3 on linux
\r
51 - fpc 2.2, linux: fastmd5: 12 kb; lcorernd: 6 kb.
\r
52 - delphi 6: fastmd5: 3 kb; lcorernd: 2 kb
\r
54 reasoning behind the security of this RNG:
\r
57 1: i assume that any attacker has no local access to the machine. if one gained
\r
58 this, then there are more seriousness weaknesses to consider.
\r
59 2: i attempt to use enough seeding to be difficult to guess.
\r
60 on windows: GUID, various readouts of hi res timestamps, heap stats, cursor
\r
62 on *nix: i assume /dev/(u)random output is secure and difficult to guess. if
\r
63 it misses, i use /dev/wtmp, which typically has as lot of entropy in it. i also use hi res timestamps.
\r
64 3: on a state compromise, one can easily get up to the hash size worth of previous output, beyond that one has
\r
65 to invert the hash operation.
\r
67 - mixing/expansion: a secure hash random walk on a buffer with a constant secret and a changing exposed part,
\r
68 the big secret part serves to make it difficult for an attacker to predict next and previous output.
\r
69 the secret part is changed during a reseed.
\r
74 <wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww>
\r
75 ____________________________ ________________________________________________
\r
77 [hashsize][hashsize][hashsize]
\r
78 <rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>
\r
81 <wwwwwwwwwwwwwwwwww>
\r
82 <rrrrrrrrrrrrrrrrrrrrrrrrrrrr>
\r
86 [ output ][ secret ]
\r
89 this needs testing on platforms other than i386
\r
92 these routines are called by everything else in lcore, and if the app coder desires, by the app.
\r
93 because one may want to use their own random number source, the PRNG here can be excluded from linking,
\r
94 and the routines here can be hooked.
\r
97 {$include uint32.inc}
\r
99 {return a dword with 32 random bits}
\r
104 randomdword:function:wordtype;
\r
106 {fill a buffer with random bytes}
\r
107 procedure fillrandom(var buf;length:integer);
\r
109 {generate an integer of 0 <= N < i}
\r
110 function randominteger(i:longint):longint;
\r
112 {generate an integer with the lowest b bits being random}
\r
113 function randombits(b:integer):longint;
\r
115 {generate a version 4 random uuid}
\r
116 function generate_uuid:ansistring;
\r
118 {$ifndef nolcorernd}
\r
120 {call this to mix seeding into the pool. is normally done automatically and does not have to be called
\r
121 but can be done if one desires more security, for example for key generation}
\r
122 procedure seedpool;
\r
124 {get some raw OS specific randomness. the output is not mixed so it should not be used directly as random numbers}
\r
125 function collect_seeding(var output;const bufsize:integer):integer;
\r
127 function internalrandomdword:wordtype;
\r
130 reseedinterval:integer=64;
\r
135 {$include pgtypes.inc}
\r
137 {$ifndef nolcorernd}
\r
139 {$ifdef mswindows}windows,activex,{$endif}
\r
144 baseunix,unix,unixutil,sockets,
\r
155 wordsize=1 shl wordsizeshift;
\r
157 {$ifndef nolcorernd}
\r
159 {$ifdef unix}{$include unixstuff.inc}{$endif}
\r
161 procedure rdtsc(buf: pointer);
\r
165 db $0f; db $31 {rdtsc}
\r
179 {hashtype must be array of bytes}
\r
183 //wordsize check commented out for d3 compatibility
\r
184 //{ $if (wordsize <> sizeof(wordtype))}'wordsizeshift must be setcorrectly'{ $ifend}
\r
185 hashsize=sizeof(hashtype);
\r
186 halfhashsize=hashsize div 2;
\r
187 hashdwords=hashsize div wordsize;
\r
188 pooldwords=3*hashdwords;
\r
190 hashpasssize=48; {this number has to be small enough that hashing this size uses only one block transform}
\r
193 //the seed part of this buffer must be at least as big as the OS seed (windows: 120 bytes for 32 bits, 160 bytes for 64 bits, unix: 36 bytes)
\r
194 pool:array[0..(pooldwords+seeddwords-1)] of wordtype;
\r
195 reseedcountdown:integer;
\r
199 systemfunction036:function(var v; c:cardinal): boolean; stdcall;
\r
200 rtlgenrandominited:boolean;
\r
202 procedure initrtlgenrandom;
\r
206 rtlgenrandominited := true;
\r
207 systemfunction036 := nil;
\r
208 h := loadlibrary('advapi32.dll');
\r
209 if (h <> 0) then begin
\r
210 systemfunction036 := GetProcAddress(h,'SystemFunction036');
\r
214 function collect_seeding(var output;const bufsize:integer):integer;
\r
217 rtlgenrandom:array[0..3] of longint;
\r
218 guid:array[0..3] of longint;
\r
219 qpcbuf:array[0..1] of longint;
\r
220 rdtscbuf:array[0..1] of longint;
\r
221 systemtimebuf:array[0..3] of longint;
\r
226 end absolute output;
\r
229 if (bufsize < sizeof(l)) then exit;
\r
230 result := sizeof(l);
\r
232 l.pid := GetCurrentProcessId;
\r
233 l.tid := GetCurrentThreadId;
\r
236 cocreateguid(tguid(l.guid));
\r
238 {QUERYPERFORMANCECOUNTER}
\r
239 queryperformancecounter(tlargeinteger(l.qpcbuf));
\r
242 rdtsc(@l.rdtscbuf);
\r
245 getsystemtime(tsystemtime(l.systemtimebuf));
\r
248 getcursorpos(l.cursor);
\r
250 l.hs := getheapstatus;
\r
253 if not rtlgenrandominited then initrtlgenrandom;
\r
254 if assigned(@systemfunction036) then systemfunction036(l.rtlgenrandom,sizeof(l.rtlgenrandom));
\r
261 wtmpinited:boolean;
\r
262 wtmpcached:hashtype;
\r
264 procedure wtmphash;
\r
267 buf:array[0..4095] of byte;
\r
271 if wtmpinited then exit;
\r
273 assignfile(f,'/var/log/wtmp');
\r
275 {$i-}reset(f,1);{$i+}
\r
276 if (ioresult <> 0) then exit;
\r
278 while not eof(f) do begin
\r
279 blockread(f,buf,sizeof(buf),numread);
\r
280 md5process(state,buf,numread);
\r
283 md5finish(state,wtmpcached);
\r
284 wtmpinited := true;
\r
289 function arc4random: cardinal; cdecl; external 'c' name 'arc4random';
\r
290 procedure arc4random_buf(buf: Pointer; nbytes: SizeUInt); cdecl; external 'c' name 'arc4random_buf';
\r
291 function arc4random_uniform(upper_bound: cardinal): cardinal; cdecl; external 'c' name 'arc4random_uniform';
\r
297 const sys_getrandom = 355;
\r
301 const sys_getrandom = 318;
\r
306 function collect_seeding(var output;const bufsize:integer):integer;
\r
311 devrnd:array[0..7] of integer;
\r
312 rdtscbuf:array[0..1] of integer;
\r
315 end absolute output;
\r
319 if (bufsize < sizeof(l)) then exit;
\r
320 result := sizeof(l);
\r
324 a := do_syscall(sys_getrandom,tsysparam(@l.devrnd),sizeof(l.devrnd),0);
\r
328 a := sizeof(l.devrnd);
\r
329 arc4random_buf(@l.devrnd, a);
\r
332 if (a < sizeof(l.devrnd)) then begin
\r
333 {if syscall misses or fails, fall back to /dev/urandom}
\r
334 assignfile(f,'/dev/urandom');
\r
336 {$i-}reset(f,1);{$i+}
\r
338 if (a <> 0) then begin
\r
339 assignfile(f,'/dev/random');
\r
340 {$i-}reset(f,1);{$i+}
\r
343 if (a = 0) then begin
\r
344 blockread(f,l.devrnd,sizeof(l.devrnd));
\r
347 {the OS we are on has no /dev/random or /dev/urandom, get a hash from /var/log/wtmp}
\r
349 move(wtmpcached,l.devrnd,sizeof(l.devrnd));
\r
352 {get more randomness in case there's no /dev/random}
\r
353 rdtsc(@l.rdtscbuf);
\r
355 gettimeofday(l.tv);
\r
360 {this produces a hash which is twice the native hash size (32 bytes for MD5)}
\r
361 procedure bighash(const input;len:integer;var output);
\r
363 inarr:array[0..65535] of byte absolute input;
\r
364 outarr:array[0..65535] of byte absolute output;
\r
366 h1,h2,h3,h4:hashtype;
\r
371 getmd5(inarr[0],a,h1);
\r
372 getmd5(inarr[a],len-a,h2);
\r
374 move(h1[0],h3[0],halfhashsize);
\r
375 move(h2[0],h3[halfhashsize],halfhashsize);
\r
376 move(h1[halfhashsize],h4[0],halfhashsize);
\r
377 move(h2[halfhashsize],h4[halfhashsize],halfhashsize);
\r
379 getmd5(h3,hashsize,outarr[0]);
\r
380 getmd5(h4,hashsize,outarr[hashsize]);
\r
383 procedure seedpool;
\r
387 a := collect_seeding(pool[pooldwords],seeddwords*wordsize);
\r
388 if (a = 0) then halt;
\r
389 bighash(pool[hashdwords],(2*hashsize)+a,pool[hashdwords]);
\r
390 getmd5(pool[0],hashpasssize,pool[0]);
\r
393 function internalrandomdword;
\r
395 if (reseedcountdown <= 0) then begin
\r
397 reseedcountdown := reseedinterval * hashdwords;
\r
398 end else if ((reseedcountdown mod hashdwords) = 0) then begin;
\r
399 getmd5(pool[0],hashpasssize,pool[0]);
\r
401 dec(reseedcountdown);
\r
403 result := pool[reseedcountdown mod hashdwords];
\r
407 procedure fillrandom(var buf;length:integer);
\r
410 buf_:array[0..16383] of uint32 absolute buf;
\r
414 for a := (length shr wordsizeshift)-1 downto 0 do begin
\r
415 buf_[b] := randomdword;
\r
418 length := length and (wordsize-1);
\r
419 if length <> 0 then begin
\r
421 move(a,buf_[b],length);
\r
428 function randombits(b:integer):longint;
\r
430 result := randomdword;
\r
431 result := result and (-1 shr (wordsizebits-b));
\r
432 if (b = 0) then result := 0;
\r
435 function randominteger(i:longint):longint;
\r
440 //bitscounter := bitscounter + numofbitsininteger(i);
\r
441 if (i = 0) then begin
\r
445 {find number of bits needed}
\r
447 if (j < 0) then begin
\r
448 result := randombits(wordsizebits);
\r
450 end else if (j >= (1 shl (wordsizebits-2))) then begin
\r
451 b := wordsizebits-1
\r
454 for a := 0 to (wordsizebits-2) do begin
\r
455 if j < 1 shl a then begin
\r
462 result := randombits(b);
\r
467 ch:array[0..15] of ansichar='0123456789abcdef';
\r
469 function generate_uuid:ansistring;
\r
471 buf:array[0..7] of word;
\r
472 function inttohex(w:word):ansistring;
\r
474 result := ch[w shr 12] + ch[(w shr 8) and $f] + ch[(w shr 4) and $f] + ch[w and $f];
\r
477 fillrandom(buf,sizeof(buf));
\r
480 buf[3] := (buf[3] and $fff) or $4000;
\r
483 buf[4] := (buf[4] and $3fff) or $8000;
\r
485 result := inttohex(buf[0]) + inttohex(buf[1]) + '-' + inttohex(buf[2]) +'-'+ inttohex(buf[3]) + '-' + inttohex(buf[4])
\r
486 + '-' + inttohex(buf[5]) + inttohex(buf[6]) + inttohex(buf[7]);
\r
489 {$ifndef nolcorernd}
\r
490 initialization randomdword := @internalrandomdword;
\r