Part I
The Windows CryptoAPI function CryptGenRandom is currently based on an internal function RtlGenRandom.
The seed for this function is generated internally - we are not required to supply one. The entropy of this seed is got from a particularly comprehensive list of sources sufficient to produce a cryptographic value. The list includes the current process ID, the current thread ID, the ticks since boot, the current time, various high-precision counters (QueryPerformanceCounter), an MD4 hash of the user's environment block, high-precision internal CPU counters such as RDTSC, RDMSR and RDPMC. In addition, there is a long list of low-level system information.
The generated seed is placed in the registry at
%HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\RNG with Seed as a 80 byte REG_BINARY.
There is precious little mentioned of this key anywhere and has not been found at microsoft.com.
Running Process Monitor from Windows Sysinternals with a filter of RNG\Seed it was found that Seed is refreshed each time a process opens. This is analogous to our including RANDOMIZE in each of our applications. With PowerBASIC each thread has its own, independent random number seed. With Windows Seed isn't even application specific - it is machine wide.
The following code simply reads the above registry key into a string in either binary or hex format. A small and otherwise useless exe, "DoNowt.exe", is then opened and allowed to close without further ado and the registry key read again. In practice, we would, of course, not use "DoNowt.exe" - it is used here simply to illustrate the 'new process - new Seed' behaviour.
Before running, "DoNowt.exe" will have to be compiled to the same directory as the main code and is no more than
#Compile Exe "DoNowt.exe"
Function PBMain() As Long
END FUNCTION
Here is a snapshot of Process Monitor executed before an early version of ReadSeed, the main code.

Seed is refreshed eight times before we get a chance to read it. There is a short delay between the first and second refresh. Further refreshing is very rapid. This behaviour is the most common. Sometimes Seed is refreshed once and will not be refreshed again until some work is done by an application.
No information has been found to explain this behaviour but what can be said is that Seed is refreshed at least once each time a process is opened. Second and subsequent instances of a process will also see Seed refreshed at least once.
So, if an application requires random bytes and no more than 80 are needed then we need do no more than read Seed. A bonus is that these random bytes are cryptographic and can therefore be used for such things as HMAC shared keys, for example.
Sometime ago a discussion in the forums considered using the system Time Stamp for seeding RND instead of using TIMER. The main code here finishes with using Seed to format a single precision value for seeding RND. A SINGLE is the preferred input according to Bob Zale.
Part II will look at a two pass implementation of CryptGenRandom and Part III will look at an implementation of RtlGenRandom, thus avoiding any CryptoAPI overhead.
Part IV looks at a method to provide random Bytes, Singles, Dwords, Longs and so on via a single function.
The Windows CryptoAPI function CryptGenRandom is currently based on an internal function RtlGenRandom.
The seed for this function is generated internally - we are not required to supply one. The entropy of this seed is got from a particularly comprehensive list of sources sufficient to produce a cryptographic value. The list includes the current process ID, the current thread ID, the ticks since boot, the current time, various high-precision counters (QueryPerformanceCounter), an MD4 hash of the user's environment block, high-precision internal CPU counters such as RDTSC, RDMSR and RDPMC. In addition, there is a long list of low-level system information.
The generated seed is placed in the registry at
%HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\RNG with Seed as a 80 byte REG_BINARY.
There is precious little mentioned of this key anywhere and has not been found at microsoft.com.
Running Process Monitor from Windows Sysinternals with a filter of RNG\Seed it was found that Seed is refreshed each time a process opens. This is analogous to our including RANDOMIZE in each of our applications. With PowerBASIC each thread has its own, independent random number seed. With Windows Seed isn't even application specific - it is machine wide.
The following code simply reads the above registry key into a string in either binary or hex format. A small and otherwise useless exe, "DoNowt.exe", is then opened and allowed to close without further ado and the registry key read again. In practice, we would, of course, not use "DoNowt.exe" - it is used here simply to illustrate the 'new process - new Seed' behaviour.
Before running, "DoNowt.exe" will have to be compiled to the same directory as the main code and is no more than
#Compile Exe "DoNowt.exe"
Function PBMain() As Long
END FUNCTION
Here is a snapshot of Process Monitor executed before an early version of ReadSeed, the main code.

Seed is refreshed eight times before we get a chance to read it. There is a short delay between the first and second refresh. Further refreshing is very rapid. This behaviour is the most common. Sometimes Seed is refreshed once and will not be refreshed again until some work is done by an application.
No information has been found to explain this behaviour but what can be said is that Seed is refreshed at least once each time a process is opened. Second and subsequent instances of a process will also see Seed refreshed at least once.
So, if an application requires random bytes and no more than 80 are needed then we need do no more than read Seed. A bonus is that these random bytes are cryptographic and can therefore be used for such things as HMAC shared keys, for example.
Sometime ago a discussion in the forums considered using the system Time Stamp for seeding RND instead of using TIMER. The main code here finishes with using Seed to format a single precision value for seeding RND. A SINGLE is the preferred input according to Bob Zale.
Part II will look at a two pass implementation of CryptGenRandom and Part III will look at an implementation of RtlGenRandom, thus avoiding any CryptoAPI overhead.
Part IV looks at a method to provide random Bytes, Singles, Dwords, Longs and so on via a single function.
Code:
#COMPILE EXE #REGISTER NONE #DIM ALL #TOOLS OFF %NOMMIDS = 1 %NOGDI = 1 $App = "ReadSeed" %Bin = 1 %Hex = 0 #INCLUDE "WIN32API.INC" MACRO SysErrMsg( API, Number ) = MessageBox 0, SystemErrorMessage( WinErr, API, FUNCNAME$, Number ), $App, %MB_ICONERROR + %MB_TOPMOST DECLARE FUNCTION GetCryptoSeed( BYVAL LONG ) AS STRING DECLARE FUNCTION SystemErrorMessage( BYVAL LONG, STRING, STRING, BYVAL LONG ) AS STRING FUNCTION PBMAIN( ) AS LONG LOCAL i AS LONG LOCAL sSeed, BinSeed, msgboxText AS STRING LOCAL RndSeed AS SINGLE msgboxText = "Before 'DoNowt.exe' executed" + $CRLF + $CRLF DO sSeed = GetCryptoSeed( %Hex ) msgboxText = msgboxText + MID$( sSeed, 1, 80 ) + $CRLF msgboxText = msgboxText + MID$( sSeed, 81, 160 ) + $CRLF + $CRLF INCR i : IF i = 2 THEN EXIT LOOP msgboxText = msgboxText + "After 'DoNowt.exe' executed" + $CRLF + $CRLF SHELL "DoNowt.exe", 0 LOOP sSeed = GetCryptoSeed( %Bin ) FOR i = 1 TO 77 BinSeed = MID$(sSeed, i, 4 ) ' Reject 'Infinity', '-Infinity', '0', '-0' and remember little endian ' Requiring this loop will be a very rare event but, nonetheless, possible IF ( BinSeed <> CHR$(0, 0, &H80, &H7F) ) AND ( BinSeed <> CHR$(0, 0, &H80, &HFF) ) _ AND ( BinSeed <> CHR$(0,0,0,0) ) AND ( BinSeed <> CHR$(0,0,0, &H80) ) THEN EXIT FOR NEXT RndSeed = CVS( BinSeed ) msgboxText = msgboxText + "Single precision seed for RND: " + STR$( RndSeed ) MSGBOX msgboxText END FUNCTION FUNCTION GetCryptoSeed( BYVAL Flag AS LONG) AS STRING LOCAL hKey, BinarySize AS DWORD LOCAL WinErr, i AS LONG LOCAL RegKey, RegValueName AS ASCIIZ * 256 DIM BinaryByte( ) AS BYTE DIM HexByte( ) AS STRING * 2 LOCAL hexString AS STRING BinarySize = 80 REDIM BinaryByte( BinarySize - 1 ) AS BYTE RegKey = "SOFTWARE\Microsoft\Cryptography\RNG" RegValueName = "Seed" WinErr = RegOpenKeyEx( %HKEY_LOCAL_MACHINE, RegKey, BYVAL %Null, %KEY_READ, hKey ) IF WinErr <> %ERROR_SUCCESS THEN SysErrMsg( "RegOpenKeyEx", 10 ) EXIT FUNCTION END IF WinErr = RegQueryValueEx( BYVAL hKey, RegValueName, BYVAL %Null, %REG_BINARY, BinaryByte( 0 ), BinarySize ) IF WinErr <> %ERROR_SUCCESS THEN SysErrMsg( "RegQueryValueEx", 20 ) EXIT FUNCTION END IF WinErr = RegCloseKey( BYVAL hKey ) IF WinErr <> %ERROR_SUCCESS THEN SysErrMsg( "RegCloseKey", 30 ) EXIT FUNCTION END IF IF Flag = %Bin THEN FUNCTION = PEEK$( VARPTR( BinaryByte( 0 ) ), BinarySize ) ELSE hexString = SPACE$( 2 * BinarySize ) REDIM HexByte( BinarySize - 1 ) AS STRING * 2 AT STRPTR( hexString ) ' Overlay hexString with HexByte() FOR i = 0 TO BinarySize - 1 HexByte( i ) = HEX$( BinaryByte( i ), 2 ) NEXT FUNCTION = hexString END IF END FUNCTION FUNCTION SystemErrorMessage( BYVAL WinErr AS LONG, API AS STRING, sFunction AS STRING, BYVAL Number AS LONG ) AS STRING LOCAL pBuffer AS ASCIIZ PTR, ncbBuffer AS DWORD DIM sText AS STRING ncbBuffer = FormatMessage( %FORMAT_MESSAGE_ALLOCATE_BUFFER OR %FORMAT_MESSAGE_FROM_SYSTEM OR %FORMAT_MESSAGE_IGNORE_INSERTS, _ BYVAL %Null, WinErr, %Null, BYVAL VARPTR( pBuffer ), 0, BYVAL %Null ) IF ncbBuffer THEN sText = PEEK$( pBuffer, ncbBuffer ) sText = REMOVE$( sText, ANY CHR$( 13 ) + CHR$( 10 ) ) FUNCTION = $CRLF + "Error" + FORMAT$( WinErr, " #####" ) + " during " + API + $CRLF + $CRLF + _ $DQ + sText + $DQ + $CRLF + $CRLF + "Location : " + sFunction + IIF$( Number, FORMAT$( Number, " ###" ), "" ) LocalFree pBuffer ELSE FUNCTION = "Unknown error - Code:" + FORMAT$( WinErr, " #####" ) END IF END FUNCTION
Comment