1 PACKAGE body FND_RANDOM_NUMBER AS
2 /* $Header: AFSCRNGB.pls 120.2 2005/07/02 03:09:22 appldev noship $ */
3
4 --
5 -- Tuneable constants:
6 --
7 -- C_BLOCK_SIZE The number of random values that can be generated off
8 -- of the cached value for the counter before we need to
9 -- go back to the table and sequence again. This number
10 -- times the C_REKEY_SIZE must be less than 2^20. This
11 -- must be greater than 2000/8 to satisfy the block get API.
12 -- C_REKEY_SIZE The number of random blocks that can be generated off
13 -- of a key before a recomputation of the key is required.
14 -- When a process goes to the table/sequence and detects
15 -- that this number of blocks has been used, that process
16 -- is responsible for recomputing the key and updating the
17 -- the table with the new values.
18 -- C_FLUSH_SIZE The number of random events that can be buffered in
19 -- the pools before the code must flush the entropy to
20 -- one-row table. This must be a multiple of 32 so that
21 -- table updates don't favor earlier pools with more
22 -- of the available entropy.
23 -- C_EVENT_SIZE The number of random events that must be collected in
24 -- the table before a reseeding operation can be performed.
25 --
26 C_BLOCK_SIZE constant number := 4096;
27 C_REKEY_SIZE constant number := 64;
28 C_FLUSH_SIZE constant number := 1024;
29 C_EVENT_SIZE constant number := 65536;
30 --
31 -- Static (class) variables, do not change
32 --
33 S_HEX_FORMAT constant varchar2(60) := 'FM0XXXXXXXXXXXXXXX';
34 S_MAX_COUNTER constant number := power(2, 64);
35 --
36 -- Table of MD5 values (raws)
37 --
38 type RAWTAB is table of raw(16) index by binary_integer;
39 --
40 -- Writable class member variables
41 --
42 M_NUM_RANDOMS number := 0;
43 M_COUNTER number := 0;
44 M_KEY raw(24);
45 M_POOLS RAWTAB;
46 M_EVENTS number := 0;
47
48 --
49 -- Internal function - generates an entropy raw based on the SYS_GUID,
50 -- SYSDATE, session ID, DBMS_RANDOM, and any other input entropy.
51 -- Concatenates this with an existing entropy value (if any).
52 --
53 function GENERATE_ENTROPY(P_ENTROPY in raw default null,
54 P_OLDVALUE in raw default null)
55 return raw
56 is
57 X_E1 raw(16);
58 X_E2 raw(16);
59 X_E3 raw(16);
60 X_E4 raw(16);
61 begin
62 -- select SYS_GUID() into X_E1 from dual;
63 -- replacing with the line below for bug 4082303
64 X_E1 := SYS_GUID();
65 X_E2 := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
66 UTL_RAW.CAST_TO_RAW(to_char(SYSDATE, 'YYYYMMDD HH24MISS')||' '||
67 USERENV('SESSIONID')));
68 X_E3 := hextoraw(to_char(DBMS_RANDOM.RANDOM+power(2,31),'FM0XXXXXXX')||
69 to_char(DBMS_RANDOM.RANDOM+power(2,31),'FM0XXXXXXX')||
70 to_char(DBMS_RANDOM.RANDOM+power(2,31),'FM0XXXXXXX')||
71 to_char(DBMS_RANDOM.RANDOM+power(2,31),'FM0XXXXXXX'));
72 if (P_ENTROPY is not null) then
73 X_E4 := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT => P_ENTROPY);
74 else
75 X_E4 := hextoraw('0123456789ABCDEF0123456789ABCDEF');
76 end if;
77 return(UTL_RAW.CONCAT(X_E1, X_E2, X_E3, X_E4, P_OLDVALUE));
78 end GENERATE_ENTROPY;
79
80 --
81 -- Add some entropy to the in-memory pools. Entropy is added in
82 -- round-robin fashion to 32 pools. Each call to this routine is
83 -- considered one event. Even if no entropy is passed in to this
84 -- routine, it will attempt to get some by using the GUID mechanism,
85 -- the SYSDATE and SESSIONID (together), and the current state of
86 -- DBMS_RANDOM. The entropy is added to the next pool in sequence.
87 -- Only the resulting MD5 is stored - there is no buffering of
88 -- intermediate amounts of entropy (seems pointless because we're
89 -- not getting much entropy from the above sources anyway).
90 --
91 procedure ADD_ENTROPY(P_ENTROPY in raw default null)
92 is
93 X_INDEX number;
94 begin
95 if (M_EVENTS = 0) then
96 for M_EVENTS in 1..32 loop
97 M_POOLS(M_EVENTS) := null;
98 end loop;
99 M_EVENTS := 0;
100 end if;
101 X_INDEX := mod(M_EVENTS, 32) + 1;
102 M_EVENTS := M_EVENTS + 1;
103 M_POOLS(X_INDEX) := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
104 UTL_RAW.CONCAT(GENERATE_ENTROPY(P_ENTROPY,
105 M_POOLS(X_INDEX))));
106 if (M_EVENTS >= C_FLUSH_SIZE) then
107 FLUSH_ENTROPY;
108 end if;
109 end ADD_ENTROPY;
110 --
111 -- Write any accumulated entropy to the table in an autonomous transaction.
112 -- The entropy collected is assumed to be evenly distributed across the 32
113 -- pools. If enough entropy has been accumulated in the table, a reseeding
114 -- of the generator is done.
115 --
116 procedure FLUSH_ENTROPY
117 is
118 pragma AUTONOMOUS_TRANSACTION;
119 X_COUNT number;
120 X_EVENTS number;
121 X_POOL RAWTAB;
122 begin
123 if (M_EVENTS = 0) then
124 return; -- No entropy available yet
125 end if;
126 -- Initialize the pool arrays
127 for X_BIT in 1..32 loop
128 X_POOL(X_BIT) := null;
129 end loop;
130 -- Get the current state of the entropy pools from disk
131 select EVENT_COUNT,
132 POOL0, POOL1, POOL2, POOL3, POOL4, POOL5, POOL6, POOL7,
133 POOL8, POOL9, POOL10, POOL11, POOL12, POOL13, POOL14, POOL15,
134 POOL16, POOL17, POOL18, POOL19, POOL20, POOL21, POOL22, POOL23,
135 POOL24, POOL25, POOL26, POOL27, POOL28, POOL29, POOL30, POOL31
136 into X_EVENTS,
137 X_POOL(1), X_POOL(2), X_POOL(3), X_POOL(4), X_POOL(5),
138 X_POOL(6), X_POOL(7), X_POOL(8), X_POOL(9), X_POOL(10),
139 X_POOL(11), X_POOL(12), X_POOL(13), X_POOL(14), X_POOL(15),
140 X_POOL(16), X_POOL(17), X_POOL(18), X_POOL(19), X_POOL(20),
141 X_POOL(21), X_POOL(22), X_POOL(23), X_POOL(24), X_POOL(25),
142 X_POOL(26), X_POOL(27), X_POOL(28), X_POOL(29), X_POOL(30),
143 X_POOL(31), X_POOL(32)
144 from FND_RAND_STATES
145 where LOCK_ID = 1
146 for update;
147 -- Merge as much entropy as available into the pools
148 for X_COUNT in 1..32 loop
149 if (M_POOLS(X_COUNT) is not null) then
150 X_POOL(X_COUNT) := DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
151 UTL_RAW.CONCAT(X_POOL(X_COUNT),M_POOLS(X_COUNT)));
152 M_POOLS(X_COUNT) := null;
153 end if;
154 end loop;
155 -- Update the pools and event counter
156 X_EVENTS := X_EVENTS + M_EVENTS;
157 update FND_RAND_STATES
158 set EVENT_COUNT = X_EVENTS,
159 POOL0 = X_POOL(1),
160 POOL1 = X_POOL(2),
161 POOL2 = X_POOL(3),
162 POOL3 = X_POOL(4),
163 POOL4 = X_POOL(5),
164 POOL5 = X_POOL(6),
165 POOL6 = X_POOL(7),
166 POOL7 = X_POOL(8),
167 POOL8 = X_POOL(9),
168 POOL9 = X_POOL(10),
169 POOL10 = X_POOL(11),
170 POOL11 = X_POOL(12),
171 POOL12 = X_POOL(13),
172 POOL13 = X_POOL(14),
173 POOL14 = X_POOL(15),
174 POOL15 = X_POOL(16),
175 POOL16 = X_POOL(17),
176 POOL17 = X_POOL(18),
177 POOL18 = X_POOL(19),
178 POOL19 = X_POOL(20),
179 POOL20 = X_POOL(21),
180 POOL21 = X_POOL(22),
181 POOL22 = X_POOL(23),
182 POOL23 = X_POOL(24),
183 POOL24 = X_POOL(25),
184 POOL25 = X_POOL(26),
185 POOL26 = X_POOL(27),
186 POOL27 = X_POOL(28),
187 POOL28 = X_POOL(29),
188 POOL29 = X_POOL(30),
189 POOL30 = X_POOL(31),
190 POOL31 = X_POOL(32)
191 where LOCK_ID = 1;
192 -- If enough events have been accumulated, reseed
193 if (X_EVENTS >= C_EVENT_SIZE) then
194 RESEED_GENERATOR;
195 end if;
196 commit;
197 -- Mark the in-memory entropy pools as empty
198 M_EVENTS := 0;
199 exception when OTHERS then
200 rollback;
201 end FLUSH_ENTROPY;
202 --
203 -- Reseed the random number generator using entropy from the table
204 -- If insufficient entropy is available, aborts the operation and
205 -- returns. This code runs in the same transaction as the caller;
206 -- the caller is responsible for committing or rolling back the
207 -- changes. Note that on returning, this code always leaves the
208 -- one-row table in a locked state (even if it aborts), so the caller
209 -- should always commit or rollback, preferably soon after the call.
210 --
211 procedure RESEED_GENERATOR
212 is
213 X_BIT number;
214 X_POOL RAWTAB;
215 X_COUNTER number;
216 X_EVENTS number;
217 X_SEQUENCE number;
218 X_KEY raw(24);
219 X_ENTROPY raw(512);
220 begin
221 -- Initialize the pool arrays
222 for X_BIT in 1..32 loop
223 X_POOL(X_BIT) := null;
224 end loop;
225 -- Get the current state of the key, seed counter, and entropy
226 select LAST_SEQUENCE, RANDOM_KEY, RESEED_COUNTER, EVENT_COUNT,
227 POOL0, POOL1, POOL2, POOL3, POOL4, POOL5, POOL6, POOL7,
228 POOL8, POOL9, POOL10, POOL11, POOL12, POOL13, POOL14, POOL15,
229 POOL16, POOL17, POOL18, POOL19, POOL20, POOL21, POOL22, POOL23,
230 POOL24, POOL25, POOL26, POOL27, POOL28, POOL29, POOL30, POOL31
231 into X_SEQUENCE, X_KEY, X_COUNTER, X_EVENTS,
232 X_POOL(1), X_POOL(2), X_POOL(3), X_POOL(4), X_POOL(5),
233 X_POOL(6), X_POOL(7), X_POOL(8), X_POOL(9), X_POOL(10),
234 X_POOL(11), X_POOL(12), X_POOL(13), X_POOL(14), X_POOL(15),
235 X_POOL(16), X_POOL(17), X_POOL(18), X_POOL(19), X_POOL(20),
236 X_POOL(21), X_POOL(22), X_POOL(23), X_POOL(24), X_POOL(25),
237 X_POOL(26), X_POOL(27), X_POOL(28), X_POOL(29), X_POOL(30),
238 X_POOL(31), X_POOL(32)
239 from FND_RAND_STATES
240 where LOCK_ID = 1
241 for update;
242 -- Draw entropy from the pools
243 X_ENTROPY := null;
244 for X_BIT in 0..31 loop
245 if (mod(X_COUNTER, power(2, X_BIT)) = 0) then
246 -- Use this pool for the reseed operation
247 if (X_POOL(X_BIT + 1) is not null) then
248 X_ENTROPY := UTL_RAW.CONCAT(X_ENTROPY, X_POOL(X_BIT + 1));
249 X_POOL(X_BIT + 1) := null;
250 end if;
251 end if;
252 end loop;
253 if (X_ENTROPY is null) then
254 -- Insufficient entropy available, abort the reseeding
255 return;
256 end if;
257 -- Recompute the key by taking the MD5 of the old key plus the entropy.
258 X_KEY := UTL_RAW.CONCAT(
259 UTL_RAW.SUBSTR(DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
260 UTL_RAW.CONCAT(hextoraw('1'), X_KEY, X_ENTROPY)), 1, 12),
261 UTL_RAW.SUBSTR(DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
262 UTL_RAW.CONCAT(hextoraw('2'), X_KEY, X_ENTROPY)), 1, 12));
263 -- Increment the seed counter
264 if (X_COUNTER = power(2,31)) then
265 X_COUNTER := 1;
266 else
267 X_COUNTER := X_COUNTER + 1;
268 end if;
269 -- Update the reseed counter, key, and entropy;
270 -- set the last sequence to 0 to force a re-initialization to occur
271 update FND_RAND_STATES
272 set LAST_SEQUENCE = 0,
273 EVENT_COUNT = 0,
274 RESEED_COUNTER = X_COUNTER,
275 RANDOM_KEY = X_KEY,
276 POOL0 = X_POOL(1),
277 POOL1 = X_POOL(2),
278 POOL2 = X_POOL(3),
279 POOL3 = X_POOL(4),
280 POOL4 = X_POOL(5),
281 POOL5 = X_POOL(6),
282 POOL6 = X_POOL(7),
283 POOL7 = X_POOL(8),
284 POOL8 = X_POOL(9),
285 POOL9 = X_POOL(10),
286 POOL10 = X_POOL(11),
287 POOL11 = X_POOL(12),
288 POOL12 = X_POOL(13),
289 POOL13 = X_POOL(14),
290 POOL14 = X_POOL(15),
291 POOL15 = X_POOL(16),
292 POOL16 = X_POOL(17),
293 POOL17 = X_POOL(18),
294 POOL18 = X_POOL(19),
295 POOL19 = X_POOL(20),
296 POOL20 = X_POOL(21),
297 POOL21 = X_POOL(22),
298 POOL22 = X_POOL(23),
299 POOL23 = X_POOL(24),
300 POOL24 = X_POOL(25),
301 POOL25 = X_POOL(26),
302 POOL26 = X_POOL(27),
303 POOL27 = X_POOL(28),
304 POOL28 = X_POOL(29),
305 POOL29 = X_POOL(30),
306 POOL30 = X_POOL(31),
307 POOL31 = X_POOL(32)
308 where LOCK_ID = 1;
309 exception when OTHERS then
310 rollback;
311 end RESEED_GENERATOR;
312
313 --
314 -- This routine updates the generator state stored in the one-row table
315 -- using an autonomous transaction. Since the counter is coming from a
316 -- sequence, that means that this routine really just updates the key
317 -- stored in the table. The update also stores the value of the sequence,
318 -- so that later readers of the table can determine how many blocks of
319 -- random values have been generated using the stored key.
320 --
321 procedure UPDATE_KEY(P_LASTCOUNT in number)
322 is
323 pragma AUTONOMOUS_TRANSACTION;
324 X_NEWKEY raw(24);
325 X_R1 raw(8);
326 X_R2 raw(8);
327 X_R3 raw(8);
328 X_LASTCOUNT number;
329 X_ENTROPY raw(512);
330 begin
331 -- Re-fetch the row to lock it for update
332 select LAST_SEQUENCE, RANDOM_KEY
333 into X_LASTCOUNT, M_KEY
334 from FND_RAND_STATES
335 where LOCK_ID = 1
336 for update;
337 -- Recheck it to make sure we need to update it; it's possible
338 -- that another process beat us to it.
339 if (X_LASTCOUNT = P_LASTCOUNT) then
340 if (P_LASTCOUNT = 0) then
341 -- This is the first process to touch the row, force a key change
342 X_ENTROPY := GENERATE_ENTROPY;
343 M_KEY := UTL_RAW.CONCAT(
344 UTL_RAW.SUBSTR(DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
345 UTL_RAW.CONCAT(hextoraw('1'), M_KEY, X_ENTROPY)), 1, 12),
346 UTL_RAW.SUBSTR(DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT =>
347 UTL_RAW.CONCAT(hextoraw('2'), M_KEY, X_ENTROPY)), 1, 12));
348 end if;
349 -- Update is required
350 X_LASTCOUNT := M_COUNTER;
351 M_COUNTER := mod(M_COUNTER * C_BLOCK_SIZE, S_MAX_COUNTER);
352 -- Now regenerate the key
353 X_R1 := hextoraw(to_char(M_COUNTER, S_HEX_FORMAT));
354 M_COUNTER := mod(M_COUNTER + 1, S_MAX_COUNTER);
355 X_R2 := hextoraw(to_char(M_COUNTER, S_HEX_FORMAT));
356 M_COUNTER := mod(M_COUNTER + 1, S_MAX_COUNTER);
357 X_R3 := hextoraw(to_char(M_COUNTER, S_HEX_FORMAT));
358 M_COUNTER := mod(M_COUNTER + 2, S_MAX_COUNTER);
359 X_NEWKEY := DBMS_OBFUSCATION_TOOLKIT.DES3ENCRYPT(
360 INPUT => UTL_RAW.CONCAT(X_R1, X_R2, X_R3), KEY => M_KEY);
361 -- Used the first 4 randoms from this block to regenerate the key
362 -- (one value was discarded to stay on an even count)
363 M_NUM_RANDOMS := C_BLOCK_SIZE - 4;
364 -- Update the counter and key
365 update FND_RAND_STATES
366 set LAST_SEQUENCE = X_LASTCOUNT,
367 RANDOM_KEY = X_NEWKEY
368 where LOCK_ID = 1;
369 commit;
370 else
371 -- Update was done by another process; unlock the one-row table
372 rollback;
373 M_COUNTER := mod(M_COUNTER * C_BLOCK_SIZE, S_MAX_COUNTER);
374 M_NUM_RANDOMS := C_BLOCK_SIZE;
375 end if;
376 exception when OTHERS then
377 rollback;
378 end UPDATE_KEY;
379 --
380 -- Initialize the in-memory generator by fetching the counter value from
381 -- the sequence and the key from the one-row table. This in-memory state
382 -- is then used as needed to generate random numbers (up to a pre-defined
383 -- block size). The fetch doesn't normally require that the one-row table
384 -- be locked (because the counter comes from a sequence). Howver, if this
385 -- code detects that enough blocks have been generated from the current key,
386 -- it forces a recomputation of the key using UPDATE_KEY; we don't want
387 -- that to happen too often because it requires locking the table.
388 --
389 procedure INITIALIZE_GENERATOR
390 is
391 X_NEWKEY raw(24);
392 X_LASTCOUNT number;
393 begin
394 -- Get the current state of the random counter and key
395 select LAST_SEQUENCE, RANDOM_KEY, FND_RAND_S.NEXTVAL
396 into X_LASTCOUNT, M_KEY, M_COUNTER
397 from FND_RAND_STATES
398 where LOCK_ID = 1;
399 if ((X_LASTCOUNT = 0) or (M_COUNTER < X_LASTCOUNT) or
400 ((M_COUNTER - X_LASTCOUNT) >= C_REKEY_SIZE)) then
401 -- If the sequence has advanced enough, recompute the key
402 UPDATE_KEY(X_LASTCOUNT);
403 else
404 -- Otherwise just consume the next block
405 M_COUNTER := mod(M_COUNTER * C_BLOCK_SIZE, S_MAX_COUNTER);
406 M_NUM_RANDOMS := C_BLOCK_SIZE;
407 end if;
408 end INITIALIZE_GENERATOR;
409 --
410 -- Get a random value as a 16-byte (128-bit) raw. Values normally come
411 -- by generating them off of an in-memory cache containing the counter
412 -- and key. This cache is used up to a pre-defined block size, after
413 -- which the cache is re-synced by calling INITIALIZE_GENERATOR. The
414 -- first call is forced to do this, which means it's forced to read
415 -- the one-row table (but hopefully not to lock/update it). Note that
416 -- this code runs the ADD_ENTROPY routine on every call in a pathetic
417 -- attempt to collect whatever entropy we can; this adds some cost to
418 -- the routine, and every so often will incur the additional cost of
419 -- updating the entropy in the table, or, worse, reseeding the key.
420 --
421 function GET_RANDOM return raw
422 is
423 begin
424 return(GET_RANDOM_BYTES(16));
425 end GET_RANDOM;
426 --
427 function GET_RANDOM_BYTES(P_NBYTES in number default null) return raw
428 is
429 X_NRAND number;
430 X_EXTRA number;
431 X_BYTES raw(2000);
432 begin
433 -- Reject invalid requests
434 if ((P_NBYTES is null) or (P_NBYTES < 1) or (P_NBYTES > 2000)) then
435 return(null);
436 end if;
437 -- Collect whatever entropy we can from GUID, SYSDATE, DBMS_RANDOM
438 ADD_ENTROPY;
439 -- Compute number of bytes to round up the request to 64-bit boundary
440 X_EXTRA := mod(P_NBYTES, 8);
441 if (X_EXTRA > 0) then
442 X_EXTRA := 8 - X_EXTRA;
443 end if;
444 X_NRAND := (P_NBYTES + X_EXTRA) / 8;
445 if (M_NUM_RANDOMS < X_NRAND) then
446 -- If the current block of randoms is too small, re-initialize
447 INITIALIZE_GENERATOR;
448 end if;
449 -- Set the buffer to the first counter value
450 M_NUM_RANDOMS := M_NUM_RANDOMS - X_NRAND;
451 X_BYTES := hextoraw(to_char(M_COUNTER, S_HEX_FORMAT));
452 X_NRAND := X_NRAND - 1;
453 M_COUNTER := mod(M_COUNTER + 1, S_MAX_COUNTER);
454 -- Concatenate additional counter values to fill the request
455 while (X_NRAND > 0) loop
456 X_BYTES := UTL_RAW.CONCAT(X_BYTES,
457 hextoraw(to_char(M_COUNTER, S_HEX_FORMAT)));
458 M_COUNTER := mod(M_COUNTER + 1, S_MAX_COUNTER);
459 X_NRAND := X_NRAND - 1;
460 end loop;
461 -- Return the encryption of the counter sequence
462 if (X_EXTRA > 0) then
463 -- If rounding was done, truncate unwanted bytes
464 return(UTL_RAW.SUBSTR(DBMS_OBFUSCATION_TOOLKIT.DES3ENCRYPT(
465 INPUT => X_BYTES, KEY => M_KEY),
466 1, P_NBYTES));
467 end if;
468 return(DBMS_OBFUSCATION_TOOLKIT.DES3ENCRYPT(INPUT => X_BYTES,
469 KEY => M_KEY));
470 end GET_RANDOM_BYTES;
471
472 procedure ADD_EXTERNAL_ENTROPY(P_E in raw default null)
473 is
474 pragma AUTONOMOUS_TRANSACTION;
475 X_ENTROPY raw(512);
476 X_NEWKEY raw(24);
477 begin
478 X_ENTROPY := GENERATE_ENTROPY(P_ENTROPY=>P_E);
479 select RANDOM_KEY
480 into X_NEWKEY
481 from FND_RAND_STATES
482 where LOCK_ID = 1
483 for update;
484 X_NEWKEY := DBMS_OBFUSCATION_TOOLKIT.MD5(
485 INPUT => UTL_RAW.CONCAT(X_NEWKEY,X_ENTROPY));
486 X_NEWKEY := UTL_RAW.SUBSTR(
487 UTL_RAW.CONCAT(X_NEWKEY,
488 DBMS_OBFUSCATION_TOOLKIT.MD5(
489 INPUT => UTL_RAW.CONCAT(X_NEWKEY, X_NEWKEY))),
490 1, 24);
491 update FND_RAND_STATES
492 set RANDOM_KEY = X_NEWKEY,
493 LAST_SEQUENCE = 0
494 where LOCK_ID = 1;
495 commit;
496 exception when OTHERS then
497 rollback;
498 end ADD_EXTERNAL_ENTROPY;
499
500 END FND_RANDOM_NUMBER;