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
149 {$ifdef unix}{$include unixstuff.inc}{$endif}
\r
151 procedure rdtsc(buf: pointer);
\r
155 db $0f; db $31 {rdtsc}
\r
169 {hashtype must be array of bytes}
\r
174 wordsize=1 shl wordsizeshift;
\r
175 //wordsize check commented out for d3 compatibility
\r
176 //{ $if (wordsize <> sizeof(wordtype))}'wordsizeshift must be setcorrectly'{ $ifend}
\r
177 hashsize=sizeof(hashtype);
\r
178 halfhashsize=hashsize div 2;
\r
179 hashdwords=hashsize div wordsize;
\r
180 pooldwords=3*hashdwords;
\r
182 hashpasssize=48; {this number has to be small enough that hashing this size uses only one block transform}
\r
185 //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
186 pool:array[0..(pooldwords+seeddwords-1)] of wordtype;
\r
187 reseedcountdown:integer;
\r
191 systemfunction036:function(var v; c:cardinal): boolean; stdcall;
\r
192 rtlgenrandominited:boolean;
\r
194 procedure initrtlgenrandom;
\r
198 rtlgenrandominited := true;
\r
199 systemfunction036 := nil;
\r
200 h := loadlibrary('advapi32.dll');
\r
201 if (h <> 0) then begin
\r
202 systemfunction036 := GetProcAddress(h,'SystemFunction036');
\r
206 function collect_seeding(var output;const bufsize:integer):integer;
\r
209 rtlgenrandom:array[0..3] of longint;
\r
210 guid:array[0..3] of longint;
\r
211 qpcbuf:array[0..1] of longint;
\r
212 rdtscbuf:array[0..1] of longint;
\r
213 systemtimebuf:array[0..3] of longint;
\r
218 end absolute output;
\r
221 if (bufsize < sizeof(l)) then exit;
\r
222 result := sizeof(l);
\r
224 l.pid := GetCurrentProcessId;
\r
225 l.tid := GetCurrentThreadId;
\r
228 cocreateguid(tguid(l.guid));
\r
230 {QUERYPERFORMANCECOUNTER}
\r
231 queryperformancecounter(tlargeinteger(l.qpcbuf));
\r
234 rdtsc(@l.rdtscbuf);
\r
237 getsystemtime(tsystemtime(l.systemtimebuf));
\r
240 getcursorpos(l.cursor);
\r
242 l.hs := getheapstatus;
\r
245 if not rtlgenrandominited then initrtlgenrandom;
\r
246 if assigned(@systemfunction036) then systemfunction036(l.rtlgenrandom,sizeof(l.rtlgenrandom));
\r
253 wtmpinited:boolean;
\r
254 wtmpcached:hashtype;
\r
256 procedure wtmphash;
\r
259 buf:array[0..4095] of byte;
\r
263 if wtmpinited then exit;
\r
265 assignfile(f,'/var/log/wtmp');
\r
267 {$i-}reset(f,1);{$i+}
\r
268 if (ioresult <> 0) then exit;
\r
270 while not eof(f) do begin
\r
271 blockread(f,buf,sizeof(buf),numread);
\r
272 md5process(state,buf,numread);
\r
275 md5finish(state,wtmpcached);
\r
276 wtmpinited := true;
\r
280 function collect_seeding(var output;const bufsize:integer):integer;
\r
285 devrnd:array[0..7] of integer;
\r
286 rdtscbuf:array[0..1] of integer;
\r
289 end absolute output;
\r
293 if (bufsize < sizeof(l)) then exit;
\r
294 result := sizeof(l);
\r
298 assignfile(f,'/dev/urandom');
\r
300 {$i-}reset(f,1);{$i+}
\r
302 if (a <> 0) then begin
\r
303 assignfile(f,'/dev/random');
\r
304 {$i-}reset(f,1);{$i+}
\r
307 if (a = 0) then begin
\r
308 blockread(f,l.devrnd,sizeof(l.devrnd));
\r
311 {the OS we are on has no /dev/random or /dev/urandom, get a hash from /var/log/wtmp}
\r
313 move(wtmpcached,l.devrnd,sizeof(l.devrnd));
\r
315 {get more randomness in case there's no /dev/random}
\r
316 rdtsc(@l.rdtscbuf);
\r
318 gettimeofday(l.tv);
\r
323 {this produces a hash which is twice the native hash size (32 bytes for MD5)}
\r
324 procedure bighash(const input;len:integer;var output);
\r
326 inarr:array[0..65535] of byte absolute input;
\r
327 outarr:array[0..65535] of byte absolute output;
\r
329 h1,h2,h3,h4:hashtype;
\r
334 getmd5(inarr[0],a,h1);
\r
335 getmd5(inarr[a],len-a,h2);
\r
337 move(h1[0],h3[0],halfhashsize);
\r
338 move(h2[0],h3[halfhashsize],halfhashsize);
\r
339 move(h1[halfhashsize],h4[0],halfhashsize);
\r
340 move(h2[halfhashsize],h4[halfhashsize],halfhashsize);
\r
342 getmd5(h3,hashsize,outarr[0]);
\r
343 getmd5(h4,hashsize,outarr[hashsize]);
\r
346 procedure seedpool;
\r
350 a := collect_seeding(pool[pooldwords],seeddwords*wordsize);
\r
351 if (a = 0) then halt;
\r
352 bighash(pool[hashdwords],(2*hashsize)+a,pool[hashdwords]);
\r
353 getmd5(pool[0],hashpasssize,pool[0]);
\r
356 function internalrandomdword;
\r
358 if (reseedcountdown <= 0) then begin
\r
360 reseedcountdown := reseedinterval * hashdwords;
\r
361 end else if ((reseedcountdown mod hashdwords) = 0) then begin;
\r
362 getmd5(pool[0],hashpasssize,pool[0]);
\r
364 dec(reseedcountdown);
\r
366 result := pool[reseedcountdown mod hashdwords];
\r
370 procedure fillrandom(var buf;length:integer);
\r
373 buf_:array[0..16383] of uint32 absolute buf;
\r
377 for a := (length shr wordsizeshift)-1 downto 0 do begin
\r
378 buf_[b] := randomdword;
\r
381 length := length and (wordsize-1);
\r
382 if length <> 0 then begin
\r
384 move(a,buf_[b],length);
\r
391 function randombits(b:integer):longint;
\r
393 result := randomdword;
\r
394 result := result and (-1 shr (wordsizebits-b));
\r
395 if (b = 0) then result := 0;
\r
398 function randominteger(i:longint):longint;
\r
403 //bitscounter := bitscounter + numofbitsininteger(i);
\r
404 if (i = 0) then begin
\r
408 {find number of bits needed}
\r
410 if (j < 0) then begin
\r
411 result := randombits(wordsizebits);
\r
413 end else if (j >= (1 shl (wordsizebits-2))) then begin
\r
414 b := wordsizebits-1
\r
417 for a := 0 to (wordsizebits-2) do begin
\r
418 if j < 1 shl a then begin
\r
425 result := randombits(b);
\r
430 ch:array[0..15] of ansichar='0123456789abcdef';
\r
432 function generate_uuid:ansistring;
\r
434 buf:array[0..7] of word;
\r
435 function inttohex(w:word):ansistring;
\r
437 result := ch[w shr 12] + ch[(w shr 8) and $f] + ch[(w shr 4) and $f] + ch[w and $f];
\r
440 fillrandom(buf,sizeof(buf));
\r
443 buf[3] := (buf[3] and $fff) or $4000;
\r
446 buf[4] := (buf[4] and $3fff) or $8000;
\r
448 result := inttohex(buf[0]) + inttohex(buf[1]) + '-' + inttohex(buf[2]) +'-'+ inttohex(buf[3]) + '-' + inttohex(buf[4])
\r
449 + '-' + inttohex(buf[5]) + inttohex(buf[6]) + inttohex(buf[7]);
\r
452 {$ifndef nolcorernd}
\r
453 initialization randomdword := @internalrandomdword;
\r