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
10 {$include lcoreconfig.inc}
\r
13 written by Bas Steendijk (beware)
\r
15 the aim of this unit is to provide randomness in a consistent way, using OS specific methods for seeding
\r
17 this unit uses MD5 for performance and code size, but it is made so it is easy to use a different hash,
\r
18 as long as it is at least 128 bits, and a multiple of the "word size" (32 bits)
\r
22 - for the code to be:
\r
23 - relatively simple and small
\r
26 - for the numbers to be
\r
27 - random: pass diehard and similar tests
\r
28 - unique: generate UUIDs
\r
29 - secure: difficult for a remote attacker to guess the internal state, even
\r
30 when given some output
\r
32 typical intended uses:
\r
33 - anything that needs random numbers without extreme demands on security or
\r
34 speed should be able to use this
\r
35 - seeding other (faster) RNGs
\r
36 - generation of passwords, UUIDs, cookies, and session keys
\r
37 - randomizing protocol fields to protect against spoofing attacks
\r
38 - randomness for games
\r
40 this is not intended to be directly used for:
\r
41 - high security purposes (generating RSA root keys etc)
\r
42 - needing random numbers at very high rates (disk wiping, some simulations, etc)
\r
45 - 24 MB/s on 2.2 GHz athlon64 core on windows XP 32 bits
\r
46 - 6.4 MB/s on 1 GHz p3 on linux
\r
49 - fpc 2.2, linux: fastmd5: 12 kb; lcorernd: 6 kb.
\r
50 - delphi 6: fastmd5: 3 kb; lcorernd: 2 kb
\r
52 reasoning behind the security of this RNG:
\r
55 1: i assume that any attacker has no local access to the machine. if one gained
\r
56 this, then there are more seriousness weaknesses to consider.
\r
57 2: i attempt to use enough seeding to be difficult to guess.
\r
58 on windows: GUID, various readouts of hi res timestamps, heap stats, cursor
\r
60 on *nix: i assume /dev/(u)random output is secure and difficult to guess. if
\r
61 it misses, i use /dev/wtmp, which typically has as lot of entropy in it. i also use hi res timestamps.
\r
62 3: on a state compromise, one can easily get up to the hash size worth of previous output, beyond that one has
\r
63 to invert the hash operation.
\r
65 - mixing/expansion: a secure hash random walk on a buffer with a constant secret and a changing exposed part,
\r
66 the big secret part serves to make it difficult for an attacker to predict next and previous output.
\r
67 the secret part is changed during a reseed.
\r
72 <wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww>
\r
73 ____________________________ ________________________________________________
\r
75 [hashsize][hashsize][hashsize]
\r
76 <rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>
\r
79 <wwwwwwwwwwwwwwwwww>
\r
80 <rrrrrrrrrrrrrrrrrrrrrrrrrrrr>
\r
84 [ output ][ secret ]
\r
87 this needs testing on platforms other than i386
\r
90 these routines are called by everything else in lcore, and if the app coder desires, by the app.
\r
91 because one may want to use their own random number source, the PRNG here can be excluded from linking,
\r
92 and the routines here can be hooked.
\r
95 {$include uint32.inc}
\r
97 {return a dword with 32 random bits}
\r
102 randomdword:function:wordtype;
\r
104 {fill a buffer with random bytes}
\r
105 procedure fillrandom(var buf;length:integer);
\r
107 {generate an integer of 0 <= N < i}
\r
108 function randominteger(i:longint):longint;
\r
110 {generate an integer with the lowest b bits being random}
\r
111 function randombits(b:integer):longint;
\r
113 {generate a version 4 random uuid}
\r
114 function generate_uuid:ansistring;
\r
116 {$ifndef nolcorernd}
\r
118 {call this to mix seeding into the pool. is normally done automatically and does not have to be called
\r
119 but can be done if one desires more security, for example for key generation}
\r
120 procedure seedpool;
\r
122 {get some raw OS specific randomness. the output is not mixed so it should not be used directly as random numbers}
\r
123 function collect_seeding(var output;const bufsize:integer):integer;
\r
125 function internalrandomdword:wordtype;
\r
128 reseedinterval:integer=64;
\r
133 {$include pgtypes.inc}
\r
135 {$ifndef nolcorernd}
\r
137 {$ifdef mswindows}windows,activex,{$endif}
\r
142 baseunix,unix,unixutil,sockets,
\r
147 {$ifdef unix}{$include unixstuff.inc}{$endif}
\r
149 procedure rdtsc(buf: pointer);
\r
153 db $0f; db $31 {rdtsc}
\r
167 {hashtype must be array of bytes}
\r
172 wordsize=1 shl wordsizeshift;
\r
173 //wordsize check commented out for d3 compatibility
\r
174 //{ $if (wordsize <> sizeof(wordtype))}'wordsizeshift must be setcorrectly'{ $ifend}
\r
175 hashsize=sizeof(hashtype);
\r
176 halfhashsize=hashsize div 2;
\r
177 hashdwords=hashsize div wordsize;
\r
178 pooldwords=3*hashdwords;
\r
180 hashpasssize=48; {this number has to be small enough that hashing this size uses only one block transform}
\r
183 {the seed part of this buffer must be at least as big as the OS seed (windows: 120 bytes, unix: 36 bytes)}
\r
184 pool:array[0..(pooldwords+seeddwords-1)] of wordtype;
\r
185 reseedcountdown:integer;
\r
189 systemfunction036:function(var v; c:cardinal): boolean; stdcall;
\r
190 rtlgenrandominited:boolean;
\r
192 procedure initrtlgenrandom;
\r
196 rtlgenrandominited := true;
\r
197 systemfunction036 := nil;
\r
198 h := loadlibrary('advapi32.dll');
\r
199 if (h <> 0) then begin
\r
200 systemfunction036 := GetProcAddress(h,'SystemFunction036');
\r
204 function collect_seeding(var output;const bufsize:integer):integer;
\r
207 rtlgenrandom:array[0..3] of longint;
\r
208 guid:array[0..3] of longint;
\r
209 qpcbuf:array[0..1] of longint;
\r
210 rdtscbuf:array[0..1] of longint;
\r
211 systemtimebuf:array[0..3] of longint;
\r
216 end absolute output;
\r
219 if (bufsize < sizeof(l)) then exit;
\r
220 result := sizeof(l);
\r
222 l.pid := GetCurrentProcessId;
\r
223 l.tid := GetCurrentThreadId;
\r
226 cocreateguid(tguid(l.guid));
\r
228 {QUERYPERFORMANCECOUNTER}
\r
229 queryperformancecounter(tlargeinteger(l.qpcbuf));
\r
232 rdtsc(@l.rdtscbuf);
\r
235 getsystemtime(tsystemtime(l.systemtimebuf));
\r
238 getcursorpos(l.cursor);
\r
240 l.hs := getheapstatus;
\r
243 if not rtlgenrandominited then initrtlgenrandom;
\r
244 if assigned(@systemfunction036) then systemfunction036(l.rtlgenrandom,sizeof(l.rtlgenrandom));
\r
251 wtmpinited:boolean;
\r
252 wtmpcached:hashtype;
\r
254 procedure wtmphash;
\r
257 buf:array[0..4095] of byte;
\r
261 if wtmpinited then exit;
\r
263 assignfile(f,'/var/log/wtmp');
\r
265 {$i-}reset(f,1);{$i+}
\r
266 if (ioresult <> 0) then exit;
\r
268 while not eof(f) do begin
\r
269 blockread(f,buf,sizeof(buf),numread);
\r
270 md5process(state,buf,numread);
\r
273 md5finish(state,wtmpcached);
\r
274 wtmpinited := true;
\r
278 function collect_seeding(var output;const bufsize:integer):integer;
\r
283 devrnd:array[0..7] of integer;
\r
284 rdtscbuf:array[0..1] of integer;
\r
287 end absolute output;
\r
291 if (bufsize < sizeof(l)) then exit;
\r
292 result := sizeof(l);
\r
296 assignfile(f,'/dev/urandom');
\r
298 {$i-}reset(f,1);{$i+}
\r
300 if (a <> 0) then begin
\r
301 assignfile(f,'/dev/random');
\r
302 {$i-}reset(f,1);{$i+}
\r
305 if (a = 0) then begin
\r
306 blockread(f,l.devrnd,sizeof(l.devrnd));
\r
309 {the OS we are on has no /dev/random or /dev/urandom, get a hash from /var/log/wtmp}
\r
311 move(wtmpcached,l.devrnd,sizeof(l.devrnd));
\r
313 {get more randomness in case there's no /dev/random}
\r
314 rdtsc(@l.rdtscbuf);
\r
316 gettimeofday(l.tv);
\r
321 {this produces a hash which is twice the native hash size (32 bytes for MD5)}
\r
322 procedure bighash(const input;len:integer;var output);
\r
324 inarr:array[0..65535] of byte absolute input;
\r
325 outarr:array[0..65535] of byte absolute output;
\r
327 h1,h2,h3,h4:hashtype;
\r
332 getmd5(inarr[0],a,h1);
\r
333 getmd5(inarr[a],len-a,h2);
\r
335 move(h1[0],h3[0],halfhashsize);
\r
336 move(h2[0],h3[halfhashsize],halfhashsize);
\r
337 move(h1[halfhashsize],h4[0],halfhashsize);
\r
338 move(h2[halfhashsize],h4[halfhashsize],halfhashsize);
\r
340 getmd5(h3,hashsize,outarr[0]);
\r
341 getmd5(h4,hashsize,outarr[hashsize]);
\r
344 procedure seedpool;
\r
348 a := collect_seeding(pool[pooldwords],seeddwords*wordsize);
\r
349 if (a = 0) then halt;
\r
350 bighash(pool[hashdwords],(2*hashsize)+a,pool[hashdwords]);
\r
351 getmd5(pool[0],hashpasssize,pool[0]);
\r
354 function internalrandomdword;
\r
356 if (reseedcountdown <= 0) then begin
\r
358 reseedcountdown := reseedinterval * hashdwords;
\r
359 end else if ((reseedcountdown mod hashdwords) = 0) then begin;
\r
360 getmd5(pool[0],hashpasssize,pool[0]);
\r
362 dec(reseedcountdown);
\r
364 result := pool[reseedcountdown mod hashdwords];
\r
368 procedure fillrandom(var buf;length:integer);
\r
371 buf_:array[0..16383] of uint32 absolute buf;
\r
375 for a := (length shr wordsizeshift)-1 downto 0 do begin
\r
376 buf_[b] := randomdword;
\r
379 length := length and (wordsize-1);
\r
380 if length <> 0 then begin
\r
382 move(a,buf_[b],length);
\r
389 function randombits(b:integer):longint;
\r
391 result := randomdword;
\r
392 result := result and (-1 shr (wordsizebits-b));
\r
393 if (b = 0) then result := 0;
\r
396 function randominteger(i:longint):longint;
\r
401 //bitscounter := bitscounter + numofbitsininteger(i);
\r
402 if (i = 0) then begin
\r
406 {find number of bits needed}
\r
408 if (j < 0) then begin
\r
409 result := randombits(wordsizebits);
\r
411 end else if (j >= (1 shl (wordsizebits-2))) then begin
\r
412 b := wordsizebits-1
\r
415 for a := 0 to (wordsizebits-2) do begin
\r
416 if j < 1 shl a then begin
\r
423 result := randombits(b);
\r
428 ch:array[0..15] of ansichar='0123456789abcdef';
\r
430 function generate_uuid:ansistring;
\r
432 buf:array[0..7] of word;
\r
433 function inttohex(w:word):ansistring;
\r
435 result := ch[w shr 12] + ch[(w shr 8) and $f] + ch[(w shr 4) and $f] + ch[w and $f];
\r
438 fillrandom(buf,sizeof(buf));
\r
441 buf[3] := (buf[3] and $fff) or $4000;
\r
444 buf[4] := (buf[4] and $3fff) or $8000;
\r
446 result := inttohex(buf[0]) + inttohex(buf[1]) + '-' + inttohex(buf[2]) +'-'+ inttohex(buf[3]) + '-' + inttohex(buf[4])
\r
447 + '-' + inttohex(buf[5]) + inttohex(buf[6]) + inttohex(buf[7]);
\r
450 {$ifndef nolcorernd}
\r
451 initialization randomdword := @internalrandomdword;
\r