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 atleat 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 UUID's
\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) RNG's
\r
36 - generation of passwords, UUID's, 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 securirity 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:string;
\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 {$ifndef nolcorernd}
\r
135 {$ifdef win32}windows,activex,{$endif}
\r
136 {$ifdef unix}baseunix,unix,unixutil,{$endif}
\r
139 {$ifdef unix}{$include unixstuff.inc}{$endif}
\r
142 {hashtype must be array of bytes}
\r
147 wordsize=1 shl wordsizeshift;
\r
148 //wordsize check commented out for d3 compatibility
\r
149 //{ $if (wordsize <> sizeof(wordtype))}'wordsizeshift must be setcorrectly'{ $ifend}
\r
150 hashsize=sizeof(hashtype);
\r
151 halfhashsize=hashsize div 2;
\r
152 hashdwords=hashsize div wordsize;
\r
153 pooldwords=3*hashdwords;
\r
155 hashpasssize=48; {this number has to be small enough that hashing this size uses only one block transform}
\r
158 {the seed part of this buffer must be atleast as big as the OS seed (windows: 104 bytes, unix: 36 bytes)}
\r
159 pool:array[0..(pooldwords+seeddwords-1)] of wordtype;
\r
160 reseedcountdown:integer;
\r
163 function collect_seeding(var output;const bufsize:integer):integer;
\r
166 guid:array[0..3] of longint;
\r
167 qpcbuf:array[0..1] of longint;
\r
168 rdtscbuf:array[0..1] of longint;
\r
169 systemtimebuf:array[0..3] of longint;
\r
174 end absolute output;
\r
175 rdtsc_0,rdtsc_1:integer;
\r
178 if (bufsize < sizeof(l)) then exit;
\r
179 result := sizeof(l);
\r
181 l.pid := GetCurrentProcessId;
\r
182 l.tid := GetCurrentThreadId;
\r
185 cocreateguid(tguid(l.guid));
\r
187 {QUERYPERFORMANCECOUNTER}
\r
188 queryperformancecounter(tlargeinteger(l.qpcbuf));
\r
197 l.rdtscbuf[0] := rdtsc_0;
\r
198 l.rdtscbuf[1] := rdtsc_1;
\r
201 getsystemtime(tsystemtime(l.systemtimebuf));
\r
204 getcursorpos(l.cursor);
\r
206 l.hs := getheapstatus;
\r
213 wtmpinited:boolean;
\r
214 wtmpcached:hashtype;
\r
216 procedure wtmphash;
\r
219 buf:array[0..4095] of byte;
\r
223 if wtmpinited then exit;
\r
225 assignfile(f,'/var/log/wtmp');
\r
227 {$i-}reset(f,1);{$i+}
\r
228 if (ioresult <> 0) then exit;
\r
230 while not eof(f) do begin
\r
231 blockread(f,buf,sizeof(buf),numread);
\r
232 md5process(state,buf,numread);
\r
235 md5finish(state,wtmpcached);
\r
236 wtmpinited := true;
\r
240 function collect_seeding(var output;const bufsize:integer):integer;
\r
245 devrnd:array[0..3] of integer;
\r
246 rdtscbuf:array[0..1] of integer;
\r
249 end absolute output;
\r
250 rdtsc_0,rdtsc_1:integer;
\r
254 if (bufsize < sizeof(l)) then exit;
\r
255 result := sizeof(l);
\r
259 assignfile(f,'/dev/urandom');
\r
261 {$i-}reset(f,1);{$i+}
\r
263 if (a <> 0) then begin
\r
264 assignfile(f,'/dev/random');
\r
265 {$i-}reset(f,1);{$i+}
\r
268 if (a = 0) then begin
\r
269 blockread(f,l.devrnd,sizeof(l.devrnd));
\r
272 {the OS we are on has no /dev/random or /dev/urandom, get a hash from /var/log/wtmp}
\r
274 move(wtmpcached,l.devrnd,sizeof(l.devrnd));
\r
276 {get more randomness in case there's no /dev/random}
\r
277 {$ifdef cpu386}{$ASMMODE intel}
\r
283 l.rdtscbuf[0] := rdtsc_0;
\r
284 l.rdtscbuf[1] := rdtsc_1;
\r
287 gettimeofday(l.tv);
\r
292 {this produces a hash which is twice the native hash size (32 bytes for MD5)}
\r
293 procedure bighash(const input;len:integer;var output);
\r
295 inarr:array[0..65535] of byte absolute input;
\r
296 outarr:array[0..65535] of byte absolute output;
\r
298 h1,h2,h3,h4:hashtype;
\r
303 getmd5(inarr[0],a,h1);
\r
304 getmd5(inarr[a],len-a,h2);
\r
306 move(h1[0],h3[0],halfhashsize);
\r
307 move(h2[0],h3[halfhashsize],halfhashsize);
\r
308 move(h1[halfhashsize],h4[0],halfhashsize);
\r
309 move(h2[halfhashsize],h4[halfhashsize],halfhashsize);
\r
311 getmd5(h3,hashsize,outarr[0]);
\r
312 getmd5(h4,hashsize,outarr[hashsize]);
\r
315 procedure seedpool;
\r
319 a := collect_seeding(pool[pooldwords],seeddwords*wordsize);
\r
320 if (a = 0) then halt;
\r
321 bighash(pool[hashdwords],(2*hashsize)+a,pool[hashdwords]);
\r
322 getmd5(pool[0],hashpasssize,pool[0]);
\r
325 function internalrandomdword;
\r
327 if (reseedcountdown <= 0) then begin
\r
329 reseedcountdown := reseedinterval * hashdwords;
\r
330 end else if ((reseedcountdown mod hashdwords) = 0) then begin;
\r
331 getmd5(pool[0],hashpasssize,pool[0]);
\r
333 dec(reseedcountdown);
\r
335 result := pool[reseedcountdown mod hashdwords];
\r
339 procedure fillrandom(var buf;length:integer);
\r
342 buf_:array[0..16383] of uint32 absolute buf;
\r
346 for a := (length shr wordsizeshift)-1 downto 0 do begin
\r
347 buf_[b] := randomdword;
\r
350 length := length and (wordsize-1);
\r
351 if length <> 0 then begin
\r
353 move(a,buf_[b],length);
\r
360 function randombits(b:integer):longint;
\r
362 result := randomdword;
\r
363 result := result and (-1 shr (wordsizebits-b));
\r
364 if (b = 0) then result := 0;
\r
367 function randominteger(i:longint):longint;
\r
372 //bitscounter := bitscounter + numofbitsininteger(i);
\r
373 if (i = 0) then begin
\r
377 {find number of bits needed}
\r
379 if (j < 0) then begin
\r
380 result := randombits(wordsizebits);
\r
382 end else if (j >= (1 shl (wordsizebits-2))) then begin
\r
383 b := wordsizebits-1
\r
386 for a := 0 to (wordsizebits-2) do begin
\r
387 if j < 1 shl a then begin
\r
394 result := randombits(b);
\r
399 ch:array[0..15] of char='0123456789abcdef';
\r
401 function generate_uuid:string;
\r
403 buf:array[0..7] of word;
\r
404 function inttohex(w:word):string;
\r
406 result := ch[w shr 12] + ch[(w shr 8) and $f] + ch[(w shr 4) and $f] + ch[w and $f];
\r
409 fillrandom(buf,sizeof(buf));
\r
412 buf[3] := (buf[3] and $fff) or $4000;
\r
415 buf[4] := (buf[4] and $3fff) or $8000;
\r
417 result := inttohex(buf[0]) + inttohex(buf[1]) + '-' + inttohex(buf[2]) +'-'+ inttohex(buf[3]) + '-' + inttohex(buf[4])
\r
418 + '-' + inttohex(buf[5]) + inttohex(buf[6]) + inttohex(buf[7]);
\r
421 {$ifndef nolcorernd}
\r
422 initialization randomdword := @internalrandomdword;
\r