You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lcorernd.pas 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. { Copyright (C) 2005 Bas Steendijk and Peter Green
  2. For conditions of distribution and use, see copyright notice in zlib_license.txt
  3. which is included in the package
  4. ----------------------------------------------------------------------------- }
  5. unit lcorernd;
  6. interface
  7. {$include lcoreconfig.inc}
  8. {
  9. written by Bas Steendijk (beware)
  10. the aim of this unit is to provide randomness in a consistent way, using OS specific methods for seeding
  11. this unit uses MD5 for performance and code size, but it is made so it is easy to use a different hash,
  12. as long as it is atleat 128 bits, and a multiple of the "word size" (32 bits)
  13. goals:
  14. - for the code to be:
  15. - relatively simple and small
  16. - reasonably fast
  17. - for the numbers to be
  18. - random: pass diehard and similar tests
  19. - unique: generate UUID's
  20. - secure: difficult for a remote attacker to guess the internal state, even
  21. when given some output
  22. typical intended uses:
  23. - anything that needs random numbers without extreme demands on security or
  24. speed should be able to use this
  25. - seeding other (faster) RNG's
  26. - generation of passwords, UUID's, cookies, and session keys
  27. - randomizing protocol fields to protect against spoofing attacks
  28. - randomness for games
  29. this is not intended to be directly used for:
  30. - high securirity purposes (generating RSA root keys etc)
  31. - needing random numbers at very high rates (disk wiping, some simulations, etc)
  32. performance:
  33. - 24 MB/s on 2.2 GHz athlon64 core on windows XP 32 bits
  34. - 6.4 MB/s on 1 GHz p3 on linux
  35. exe size:
  36. - fpc 2.2, linux: fastmd5: 12 kb; lcorernd: 6 kb.
  37. - delphi 6: fastmd5: 3 kb; lcorernd: 2 kb
  38. reasoning behind the security of this RNG:
  39. - seeding:
  40. 1: i assume that any attacker has no local access to the machine. if one gained
  41. this, then there are more seriousness weaknesses to consider.
  42. 2: i attempt to use enough seeding to be difficult to guess.
  43. on windows: GUID, various readouts of hi res timestamps, heap stats, cursor
  44. position
  45. on *nix: i assume /dev/(u)random output is secure and difficult to guess. if
  46. it misses, i use /dev/wtmp, which typically has as lot of entropy in it. i also use hi res timestamps.
  47. 3: on a state compromise, one can easily get up to the hash size worth of previous output, beyond that one has
  48. to invert the hash operation.
  49. - mixing/expansion: a secure hash random walk on a buffer with a constant secret and a changing exposed part,
  50. the big secret part serves to make it difficult for an attacker to predict next and previous output.
  51. the secret part is changed during a reseed.
  52. OS randomness
  53. v
  54. <wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww>
  55. ____________________________ ________________________________________________
  56. [ pool ][ seed ]
  57. [hashsize][hashsize][hashsize]
  58. <rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr>
  59. bighash() seeding
  60. v
  61. <wwwwwwwwwwwwwwwwww>
  62. <rrrrrrrrrrrrrrrrrrrrrrrrrrrr>
  63. hash() random walk
  64. v
  65. <wwwwwwww>
  66. [ output ][ secret ]
  67. this needs testing on platforms other than i386
  68. these routines are called by everything else in lcore, and if the app coder desires, by the app.
  69. because one may want to use their own random number source, the PRNG here can be excluded from linking,
  70. and the routines here can be hooked.
  71. }
  72. {$include uint32.inc}
  73. {return a dword with 32 random bits}
  74. type
  75. wordtype=uint32;
  76. var
  77. randomdword:function:wordtype;
  78. {fill a buffer with random bytes}
  79. procedure fillrandom(var buf;length:integer);
  80. {generate an integer of 0 <= N < i}
  81. function randominteger(i:longint):longint;
  82. {generate an integer with the lowest b bits being random}
  83. function randombits(b:integer):longint;
  84. {generate a version 4 random uuid}
  85. function generate_uuid:string;
  86. {$ifndef nolcorernd}
  87. {call this to mix seeding into the pool. is normally done automatically and does not have to be called
  88. but can be done if one desires more security, for example for key generation}
  89. procedure seedpool;
  90. {get some raw OS specific randomness. the output is not mixed so it should not be used directly as random numbers}
  91. function collect_seeding(var output;const bufsize:integer):integer;
  92. function internalrandomdword:wordtype;
  93. var
  94. reseedinterval:integer=64;
  95. {$endif}
  96. implementation
  97. {$ifndef nolcorernd}
  98. uses
  99. {$ifdef win32}windows,activex,{$endif}
  100. {$ifdef unix}
  101. {$ifdef ver1_0}
  102. linux,
  103. {$else}
  104. baseunix,unix,unixutil,
  105. {$endif}
  106. {$endif}
  107. fastmd5,sysutils;
  108. {$ifdef unix}{$include unixstuff.inc}{$endif}
  109. type
  110. {hashtype must be array of bytes}
  111. hashtype=tmd5;
  112. const
  113. wordsizeshift=2;
  114. wordsize=1 shl wordsizeshift;
  115. //wordsize check commented out for d3 compatibility
  116. //{ $if (wordsize <> sizeof(wordtype))}'wordsizeshift must be setcorrectly'{ $ifend}
  117. hashsize=sizeof(hashtype);
  118. halfhashsize=hashsize div 2;
  119. hashdwords=hashsize div wordsize;
  120. pooldwords=3*hashdwords;
  121. seeddwords=32;
  122. hashpasssize=48; {this number has to be small enough that hashing this size uses only one block transform}
  123. var
  124. {the seed part of this buffer must be atleast as big as the OS seed (windows: 104 bytes, unix: 36 bytes)}
  125. pool:array[0..(pooldwords+seeddwords-1)] of wordtype;
  126. reseedcountdown:integer;
  127. {$ifdef win32}
  128. function collect_seeding(var output;const bufsize:integer):integer;
  129. var
  130. l:packed record
  131. guid:array[0..3] of longint;
  132. qpcbuf:array[0..1] of longint;
  133. rdtscbuf:array[0..1] of longint;
  134. systemtimebuf:array[0..3] of longint;
  135. pid:longint;
  136. tid:longint;
  137. cursor:tpoint;
  138. hs:theapstatus;
  139. end absolute output;
  140. rdtsc_0,rdtsc_1:integer;
  141. begin
  142. result := 0;
  143. if (bufsize < sizeof(l)) then exit;
  144. result := sizeof(l);
  145. {PID}
  146. l.pid := GetCurrentProcessId;
  147. l.tid := GetCurrentThreadId;
  148. {COCREATEGUID}
  149. cocreateguid(tguid(l.guid));
  150. {QUERYPERFORMANCECOUNTER}
  151. queryperformancecounter(tlargeinteger(l.qpcbuf));
  152. {RDTSC}
  153. {$ifdef cpu386}
  154. asm
  155. db $0F; db $31
  156. mov rdtsc_0,eax
  157. mov rdtsc_1,edx
  158. end;
  159. l.rdtscbuf[0] := rdtsc_0;
  160. l.rdtscbuf[1] := rdtsc_1;
  161. {$endif}
  162. {GETSYSTEMTIME}
  163. getsystemtime(tsystemtime(l.systemtimebuf));
  164. {cursor position}
  165. getcursorpos(l.cursor);
  166. l.hs := getheapstatus;
  167. end;
  168. {$endif}
  169. {$ifdef unix}
  170. var
  171. wtmpinited:boolean;
  172. wtmpcached:hashtype;
  173. procedure wtmphash;
  174. var
  175. f:file;
  176. buf:array[0..4095] of byte;
  177. numread:integer;
  178. state:tmd5state;
  179. begin
  180. if wtmpinited then exit;
  181. assignfile(f,'/var/log/wtmp');
  182. filemode := 0;
  183. {$i-}reset(f,1);{$i+}
  184. if (ioresult <> 0) then exit;
  185. md5init(state);
  186. while not eof(f) do begin
  187. blockread(f,buf,sizeof(buf),numread);
  188. md5process(state,buf,numread);
  189. end;
  190. closefile(f);
  191. md5finish(state,wtmpcached);
  192. wtmpinited := true;
  193. end;
  194. function collect_seeding(var output;const bufsize:integer):integer;
  195. var
  196. f:file;
  197. a:integer;
  198. l:packed record
  199. devrnd:array[0..3] of integer;
  200. rdtscbuf:array[0..1] of integer;
  201. tv:ttimeval;
  202. pid:integer;
  203. end absolute output;
  204. rdtsc_0,rdtsc_1:integer;
  205. begin
  206. result := 0;
  207. if (bufsize < sizeof(l)) then exit;
  208. result := sizeof(l);
  209. {/DEV/URANDOM}
  210. a := 1;
  211. assignfile(f,'/dev/urandom');
  212. filemode := 0;
  213. {$i-}reset(f,1);{$i+}
  214. a := ioresult;
  215. if (a <> 0) then begin
  216. assignfile(f,'/dev/random');
  217. {$i-}reset(f,1);{$i+}
  218. a := ioresult;
  219. end;
  220. if (a = 0) then begin
  221. blockread(f,l.devrnd,sizeof(l.devrnd));
  222. closefile(f);
  223. end else begin
  224. {the OS we are on has no /dev/random or /dev/urandom, get a hash from /var/log/wtmp}
  225. wtmphash;
  226. move(wtmpcached,l.devrnd,sizeof(l.devrnd));
  227. end;
  228. {get more randomness in case there's no /dev/random}
  229. {$ifdef cpu386}{$ASMMODE intel}
  230. asm
  231. db $0F; db $31
  232. mov rdtsc_0,eax
  233. mov rdtsc_1,edx
  234. end;
  235. l.rdtscbuf[0] := rdtsc_0;
  236. l.rdtscbuf[1] := rdtsc_1;
  237. {$endif}
  238. gettimeofday(l.tv);
  239. l.pid := getpid;
  240. end;
  241. {$endif}
  242. {this produces a hash which is twice the native hash size (32 bytes for MD5)}
  243. procedure bighash(const input;len:integer;var output);
  244. var
  245. inarr:array[0..65535] of byte absolute input;
  246. outarr:array[0..65535] of byte absolute output;
  247. h1,h2,h3,h4:hashtype;
  248. a:integer;
  249. begin
  250. a := len div 2;
  251. {first hash round}
  252. getmd5(inarr[0],a,h1);
  253. getmd5(inarr[a],len-a,h2);
  254. move(h1[0],h3[0],halfhashsize);
  255. move(h2[0],h3[halfhashsize],halfhashsize);
  256. move(h1[halfhashsize],h4[0],halfhashsize);
  257. move(h2[halfhashsize],h4[halfhashsize],halfhashsize);
  258. getmd5(h3,hashsize,outarr[0]);
  259. getmd5(h4,hashsize,outarr[hashsize]);
  260. end;
  261. procedure seedpool;
  262. var
  263. a:integer;
  264. begin
  265. a := collect_seeding(pool[pooldwords],seeddwords*wordsize);
  266. if (a = 0) then halt;
  267. bighash(pool[hashdwords],(2*hashsize)+a,pool[hashdwords]);
  268. getmd5(pool[0],hashpasssize,pool[0]);
  269. end;
  270. function internalrandomdword;
  271. begin
  272. if (reseedcountdown <= 0) then begin
  273. seedpool;
  274. reseedcountdown := reseedinterval * hashdwords;
  275. end else if ((reseedcountdown mod hashdwords) = 0) then begin;
  276. getmd5(pool[0],hashpasssize,pool[0]);
  277. end;
  278. dec(reseedcountdown);
  279. result := pool[reseedcountdown mod hashdwords];
  280. end;
  281. {$endif}
  282. procedure fillrandom(var buf;length:integer);
  283. var
  284. a,b:integer;
  285. buf_:array[0..16383] of uint32 absolute buf;
  286. begin
  287. b := 0;
  288. for a := (length shr wordsizeshift)-1 downto 0 do begin
  289. buf_[b] := randomdword;
  290. inc(b);
  291. end;
  292. length := length and (wordsize-1);
  293. if length <> 0 then begin
  294. a := randomdword;
  295. move(a,buf_[b],length);
  296. end;
  297. end;
  298. const
  299. wordsizebits=32;
  300. function randombits(b:integer):longint;
  301. begin
  302. result := randomdword;
  303. result := result and (-1 shr (wordsizebits-b));
  304. if (b = 0) then result := 0;
  305. end;
  306. function randominteger(i:longint):longint;
  307. var
  308. a,b:integer;
  309. j:integer;
  310. begin
  311. //bitscounter := bitscounter + numofbitsininteger(i);
  312. if (i = 0) then begin
  313. result := 0;
  314. exit;
  315. end;
  316. {find number of bits needed}
  317. j := i-1;
  318. if (j < 0) then begin
  319. result := randombits(wordsizebits);
  320. exit
  321. end else if (j >= (1 shl (wordsizebits-2))) then begin
  322. b := wordsizebits-1
  323. end else begin
  324. b := -1;
  325. for a := 0 to (wordsizebits-2) do begin
  326. if j < 1 shl a then begin
  327. b := a;
  328. break;
  329. end;
  330. end;
  331. end;
  332. repeat
  333. result := randombits(b);
  334. until result < i;
  335. end;
  336. const
  337. ch:array[0..15] of char='0123456789abcdef';
  338. function generate_uuid:string;
  339. var
  340. buf:array[0..7] of word;
  341. function inttohex(w:word):string;
  342. begin
  343. result := ch[w shr 12] + ch[(w shr 8) and $f] + ch[(w shr 4) and $f] + ch[w and $f];
  344. end;
  345. begin
  346. fillrandom(buf,sizeof(buf));
  347. {uuid version 4}
  348. buf[3] := (buf[3] and $fff) or $4000;
  349. {uuid version 4}
  350. buf[4] := (buf[4] and $3fff) or $8000;
  351. result := inttohex(buf[0]) + inttohex(buf[1]) + '-' + inttohex(buf[2]) +'-'+ inttohex(buf[3]) + '-' + inttohex(buf[4])
  352. + '-' + inttohex(buf[5]) + inttohex(buf[6]) + inttohex(buf[7]);
  353. end;
  354. {$ifndef nolcorernd}
  355. initialization randomdword := @internalrandomdword;
  356. {$endif}
  357. end.