Recent

Author Topic: Anyone interested in helping with a new random number generator?  (Read 27887 times)

ad1mt

  • Sr. Member
  • ****
  • Posts: 465
    • Mark Taylor's Home Page
I'm working on the design of a new random number generator, and was wondering if there were any mathematically minded people who would like to look at it and criticise it.
I'm using it for cryptography, so I'm particularly interested in any ideas about how it could be broken/reverse-engineered.
I think it would also be useful as a very long-running RNG and for use in large numbers of parallel threads, because it can be configured to have an infinite period.
« Last Edit: August 19, 2025, 11:26:33 pm by ad1mt »

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #1 on: August 19, 2025, 07:05:00 pm »
Last week I made a cspring using chacha20 and splitmix64. (from very recent literature)
I will post the code. It is a bit raw but tested against fips.
For linux: don't bother, because reading from /dev/urandom is already cspring quality.
For windows, you can use bcrypt api, which is also a cspring quality.
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. unit ChaCha20;
  3. interface
  4. {$if not defined(TBytes)}
  5. type
  6.   TBytes = array of byte;
  7. {$ifend}
  8.  
  9. type
  10.   { 256-bit key (8 x 32-bit) }
  11.   TChaChaKey   = array[0..7]  of dword;
  12.   { 96-bit nonce }
  13.   TChaChaNonce = array[0..2]  of dword;
  14.   TChaChaState = array[0..15] of dword;
  15.  
  16. procedure ChaCha20Block(const Key: TChaChaKey;const Nonce: TChaChaNonce;  
  17.                            BlockCount: dword;out Output: TBytes);
  18.  
  19. implementation
  20.  
  21. { Quarter-round operation (core of ChaCha20) }
  22. procedure QR(var a, b, c, d: dword);inline;
  23. begin
  24.   a := a + b; d := RolDWord(d xor a, 16);
  25.   c := c + d; b := RolDWord(b xor c, 12);
  26.   a := a + b; d := RolDWord(d xor a, 8);
  27.   c := c + d; b := RolDWord(b xor c, 7);
  28. end;
  29.  
  30. { Generate a ChaCha20 keystream block }
  31. procedure ChaCha20Block(const Key: TChaChaKey;const Nonce: TChaChaNonce;  
  32.                            BlockCount: dword;out Output: TBytes);
  33. {$push}{$J-} // .rodata
  34. const
  35.   CONSTANTS: array[0..3] of dword = (
  36.     $61707865, $3320646e, $79622d32, $6b206574 { "expand 32-byte k" }
  37.   );
  38. {$pop}
  39. var
  40.   state, working: TChaChaState;
  41.   i: Integer;
  42. begin
  43.   { Initialize state }
  44.   state[0] := CONSTANTS[0];
  45.   state[1] := CONSTANTS[1];
  46.   state[2] := CONSTANTS[2];
  47.   state[3] := CONSTANTS[3];
  48.   Move(Key[0], state[4], 8 * SizeOf(dword));   // Key
  49.   state[12] := BlockCount;                      // Block counter
  50.   Move(Nonce[0], state[13], 3 * SizeOf(dword));// Nonce
  51.  
  52.   working := state;
  53.  
  54.   { Perform 20 rounds (10 iterations of 8 quarter-rounds) }
  55.   for i := 0 to 9 do begin
  56.     { Column rounds }
  57.     QR(working[0], working[4], working[8], working[12]);
  58.     QR(working[1], working[5], working[9], working[13]);
  59.     QR(working[2], working[6], working[10], working[14]);
  60.     QR(working[3], working[7], working[11], working[15]);
  61.     { Diagonal rounds }
  62.     QR(working[0], working[5], working[10], working[15]);
  63.     QR(working[1], working[6], working[11], working[12]);
  64.     QR(working[2], working[7], working[8], working[13]);
  65.     QR(working[3], working[4], working[9], working[14]);
  66.   end;
  67.  
  68.   { Add initial state to the working state }
  69.   for i := 0 to 15 do
  70.     working[i] := working[i] + state[i];
  71.  
  72.   { Serialize the state to a keystream block }
  73.   SetLength(Output, 64);
  74.   Move(working[0], Output[0], 64);
  75. end;
  76.  
  77. end.
Demo:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. uses sysutils, dateutils, chacha20;
  3. { This splitmix version is a prng suitable as seed generator for
  4.   csprng's.
  5.   Uses unix time as a one time seed, that is because Gettickcount64
  6.   is deterministic on OS restarts and unix time is not. }
  7.  
  8. function SplitMix64: UInt64; inline;
  9. const
  10.   GOLDEN_GAMMA = $9E3779B97F4A7C15; // const
  11. {$push}{$J+}  
  12.   state:Uint64 = 0;//static variable
  13. {$pop}
  14. var
  15.   Z: UInt64;
  16. begin
  17.   { We should NOT reseed, that's one-time-shot }
  18.   if state = 0 then
  19.     state := DateTimeToUnix(Now);
  20.  
  21.   Z := state;
  22.   state := state + GOLDEN_GAMMA;
  23.  
  24.   Z := (Z xor (Z shr 30)) * $BF58476D1CE4E5B9;
  25.   Z := (Z xor (Z shr 27)) * $94D049BB133111EB;
  26.   Result := Z xor (Z shr 31);
  27. end;
  28.  
  29. var
  30.   key: TChaChaKey;
  31.   { we only generate random so a nonce can be zero. }
  32.   nonce: TChaChaNonce = (0,0,0);
  33.   keystream, plaintext, ciphertext: TBytes;
  34.   i: Integer;
  35. begin
  36.   { as per specification plaintext should not be empty.}
  37.   PlainText:=BytesOf('We need some random string here');
  38.   for i := 0 to 7 do Key[i] := SplitMix64;
  39.  
  40.   { Generate keystream for a specific block }
  41.   ChaCha20Block(key, nonce, 0, keystream);
  42.  
  43.   { XOR keystream with plaintext }
  44.   SetLength(ciphertext, Length(plaintext));
  45.   for i := 0 to High(plaintext) do
  46.   begin
  47.     ciphertext[i] := plaintext[i] xor keystream[i];
  48.     write(ciphertext[i]:4);;
  49.   end;
  50. end.
This is merely for multiplatform use, because both linux and windows already have good cryptographically secure random number generators.
The above code is fast for a cspring.

You can always ask for advice. It is one of my main interests.
The above passes ALL tests for random that I know of. (quite a few)
In the above code, we don't really need a nonce, because we only want to generate random numbers.
But chacha20 on its own can also be used for symmetric encryption/decryption, for which it was designed.
The csprng application as above came later and is well documented.

The above is very much suited for e.g. embedded. Fast and secure.

https://en.wikipedia.org/wiki/ChaCha20-Poly1305


« Last Edit: August 19, 2025, 07:20:38 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

ad1mt

  • Sr. Member
  • ****
  • Posts: 465
    • Mark Taylor's Home Page
Re: Anyone interested in helping with a new random number generator?
« Reply #2 on: August 19, 2025, 07:28:31 pm »
The above passes ALL tests for random that I know of. (quite a few)
Which tests have you been able to get working?  Do you have a working copy of the "Big Crush" test?  I've heard the Big Crush test is very difficult to pass, and I'd like to try it.

One of the problems I have encountered is finding RNG tests that are easy to use, and actually work.
The only one I've found so far is Practrand, so that is only testing I've done. I've tried to use others, like Dieharder, but cannot get them to run reliably, or they run extremely slowly.
There was someone I conversed with on the reddit cryptography channel that designed genetic algorithms for testing RNGs. He gave me the link to his code, but it was beyond my understanding. If you're interested, I can give you a link.

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #3 on: August 19, 2025, 08:06:00 pm »
Yes, if you are also on linux there are multiple sources. Big crush is one of the least tests to pass though.
Here are the sources: https://simul.iro.umontreal.ca/testu01/tu01.html

If you have trouble compiling them, just ask and I will send you a binary for your platform.

And of course the above passes bigcrush.

You may also be interested in the wiki entry I wrote about prng's.
There are test results, but I did not add the above code , because csprng <> prng. A prng has repeatable order with equal seed. A csprng can not be repeated unless by accident.
« Last Edit: August 19, 2025, 08:20:45 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

LeP

  • Jr. Member
  • **
  • Posts: 57
Re: Anyone interested in helping with a new random number generator?
« Reply #4 on: August 19, 2025, 11:13:10 pm »
If you want to have another suggestion (for Intel and Amd processors, not ARM), you can use that: it use hardware chipset generator random number.

Should works with Windows and Linux (not tested), with x86  or x64 apps. It doesn't use any library.

Note: AMD hardware reference should be read when used with AMD processors (should be the same as Intel ....)

Quote form Intel:
Quote
RDRAND returns random numbers that are supplied by a cryptographically secure, deterministic random bit generator
DRBG. The DRBG is designed to meet the NIST SP 800-90A standard. The DRBG is re-seeded frequently from
an on-chip non-deterministic entropy source to guarantee data returned by RDRAND is statistically uniform, nonperiodic
and non-deterministic.
Quote
RDSEED returns random numbers that are supplied by a cryptographically secure, enhanced non-deterministic
random bit generator (Enhanced NRBG). The NRBG is designed to meet the NIST SP 800-90B and NIST SP800-90C
standards.

These is the unit:
Code: Pascal  [Select][+][-]
  1. unit URDRANDandRDSEED;
  2.  
  3. {$mode Delphi}
  4.  
  5. interface
  6.  
  7. uses
  8.   SysUtils;
  9.  
  10. //Explicity check if RDRAND and RDSEED ara avilable
  11.  
  12. type
  13.   TCheck_RDRAND_RDSEED = record
  14.    tc_RDRAND: boolean;
  15.    tc_RDSEED: boolean;
  16. end;
  17.  
  18. type
  19.   TRandomAsm = record
  20.     strict private
  21.       function GetRandomRDRAND: UInt64; register;
  22.       function GetRandomRDSEED: UInt64; register;
  23.       class function Is_CPUID_Valid: boolean; register; static;
  24.       class procedure CPUID_GeneralCall(InEAX: cardinal; InECX: cardinal; out Reg_EAX, Reg_EBX, Reg_ECX, Reg_EDX); stdcall; static;
  25.     private
  26.       class function CPUID_RDRAND_RDSEED_Check: TCheck_RDRAND_RDSEED; static;
  27.     public
  28.       function GetRandomNumber(const USE_RNDSEED: boolean = false): UInt64;
  29.   end;
  30.  
  31. var
  32.   RDRAND_RDSEED_Available: TCheck_RDRAND_RDSEED = (tc_RDRAND: false; tc_RDSEED: false);
  33.  
  34. implementation
  35.  
  36. const
  37.   //ID To identify CPU Vendor, the are a multitude .. but we focalize on this
  38.   VendorIDxIntel: array [0..11] of AnsiChar = 'GenuineIntel';
  39.   VendorIDxAMD: array [0..11] of AnsiChar = 'AuthenticAMD';
  40.  
  41. { TRandomAsm }
  42.  
  43. function TRandomAsm.GetRandomNumber(const USE_RNDSEED: boolean = false): UInt64;
  44. begin
  45.   if (RDRAND_RDSEED_Available.tc_RDSEED and USE_RNDSEED) or (RDRAND_RDSEED_Available.tc_RDSEED and (not RDRAND_RDSEED_Available.tc_RDRAND)) then
  46.     result := GetRandomRDSEED
  47.   else
  48.     if RDRAND_RDSEED_Available.tc_RDRAND  then
  49.       result := GetRandomRDRAND
  50.     else
  51.       result := 0;
  52. end;
  53.  
  54. function TRandomAsm.GetRandomRDRAND: UInt64; register;
  55. asm
  56.   {$IFDEF CPUX64}
  57.     mov RCX, $0
  58.     mov RAX, $FFFFFFFFFFFFFFFF
  59.     @here:
  60.     //inc RCX
  61.     rdrand RAX       //RAX return value 64 bit (WIN32)
  62.     jnc @here
  63.     //or RAX, RCX
  64.   {$ELSE}
  65.     mov ECX, $0
  66.     mov EDX, $FFFFFFFF
  67.     mov EAX, EDX
  68.     @here:
  69.     //inc ECX
  70.     //mov EDX, EAX
  71.     rdseed EAX       //EAX return value 32 bit (WIN32)
  72.     jnc @here
  73.     @here1:
  74.     //inc ECX
  75.     rdrand EDX       //EDX:EAX return value 64 bit (WIN32)
  76.     jnc @here1
  77.     //or EAX, ECX
  78.   {$ENDIF}
  79. end;
  80.  
  81. function TRandomAsm.GetRandomRDSEED: UInt64; register;
  82. asm
  83.   {$IFDEF CPUX64}
  84.     mov RCX, $0
  85.     mov RAX, $FFFFFFFFFFFFFFFF
  86.     @here:
  87.     //inc RCX
  88.     rdseed RAX       //RAX return value 64 bit (WIN32)
  89.     jnc @here
  90.     //or RAX, RCX
  91.   {$ELSE}
  92.     mov ECX, $0
  93.     mov EDX, $FFFFFFFF
  94.     mov EAX, EDX
  95.     @here:
  96.     //inc ECX
  97.     //mov EDX, EAX
  98.     rdseed EAX       //EAX return value 32 bit (WIN32)
  99.     jnc @here
  100.     @here1:
  101.     //inc ECX
  102.     rdseed EDX       //EDX:EAX return value 64 bit (WIN32)
  103.     jnc @here1
  104.     //or EAX, ECX
  105.   {$ENDIF}
  106. end;
  107.  
  108. //Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit), not tested in real adress mode
  109. //The Intel Documentation has more detail about CPUID
  110. //Jedi project has implemented TCPUInfo with more details.
  111.  
  112. //First check that the CPU supports CPUID instructions. There are some exceptions with this rule,
  113. //but with very very old processors
  114. class function TRandomAsm.Is_CPUID_Valid: boolean; register;
  115. asm
  116.   {$IFDEF CPUX64}
  117.     pushfq                               //Save EFLAGS
  118.     pushfq                               //Store EFLAGS
  119.     xor qword [esp], $00200000           //Invert the ID bit in stored EFLAGS
  120.     popfq                                //Load stored EFLAGS (with ID bit inverted)
  121.     pushfq                               //Store EFLAGS again (ID bit may or may not be inverted)
  122.     pop rax                              //eax = modified EFLAGS (ID bit may or may not be inverted)
  123.     xor rax, qword [esp]                 //eax = whichever bits were changed
  124.     popfq                                //Restore original EFLAGS
  125.     and RAX, $00200000                   //eax = zero if ID bit can't be changed, else non-zero
  126.     jz @quit
  127.     mov RAX, $01                         //If the Result is boolean, the return parameter should be in A??? (true if A??? <> 0)
  128.     @quit:
  129.   {$ELSE}
  130.     pushfd                               //Save EFLAGS
  131.     pushfd                               //Store EFLAGS
  132.     xor dword [esp], $00200000           //Invert the ID bit in stored EFLAGS
  133.     popfd                                //Load stored EFLAGS (with ID bit inverted)
  134.     pushfd                               //Store EFLAGS again (ID bit may or may not be inverted)
  135.     pop eax                              //eax = modified EFLAGS (ID bit may or may not be inverted)
  136.     xor eax,[esp]                        //eax = whichever bits were changed
  137.     popfd                                //Restore original EFLAGS
  138.     and eax, $00200000                   //eax = zero if ID bit can't be changed, else non-zero
  139.     jz @quit
  140.     mov EAX, $01                         //If the Result is boolean, the return parameter should be in AL (true if AL <> 0)
  141.     @quit:
  142.   {$ENDIF}
  143. end;
  144.  
  145. //1) Check that the CPU is an INTEL CPU, we don't know nothing about other's
  146. //   We can presume the AMD modern processors have the same check of INTEL, but only for some instructions.
  147. //   No test were made to verify this (no AMD processor available)
  148. //
  149. //2) Catch the features of the CPU in use
  150. //
  151. //3) Catch the new features of the CPU in use
  152. //
  153. class procedure TRandomAsm.CPUID_GeneralCall(InEAX: cardinal; InECX: cardinal; out Reg_EAX, Reg_EBX, Reg_ECX, Reg_EDX); stdcall;
  154. asm
  155.   {$IFDEF CPUX64}
  156.     // save context
  157.     PUSH RBX
  158.     // CPUID
  159.     MOV EAX, InEAX           //Generic function
  160.     MOV ECX, InECX           //Generic sub function
  161.     //
  162.     //For CPU VENDOR STRING EAX := $0
  163.     //ECX is not used when EAX = $0
  164.     //
  165.     //For CPU Extension EAX := $01
  166.     //ECX is not used when EAX = $01
  167.     //
  168.     //For CPU New Extension EAX := $07
  169.     //ECX should be $00 to read if RDSEED is available
  170.     //
  171.     CPUID
  172.     // store results
  173.     MOV R8, Reg_EAX
  174.     MOV R9, Reg_EBX
  175.     MOV R10, Reg_ECX
  176.     MOV R11, Reg_EDX
  177.     MOV Cardinal PTR [R8], EAX
  178.     MOV Cardinal PTR [R9], EBX
  179.     MOV Cardinal PTR [R10], ECX
  180.     MOV Cardinal PTR [R11], EDX
  181.     // restore context
  182.     POP RBX
  183.   {$ELSE}
  184.     // save context
  185.     PUSH    EDI
  186.     PUSH    EBX
  187.     // CPUID
  188.     MOV EAX, InEAX           //Generic function
  189.     MOV ECX, InECX           //Generic sub function
  190.     //
  191.     //For CPU VENDOR STRING EAX := $0
  192.     //ECX is not used when EAX = $0
  193.     //
  194.     //For CPU Extension EAX := $01
  195.     //ECX is not used when EAX = $01
  196.     //
  197.     //For CPU New Extension EAX := $07
  198.     //ECX should be $00 to read if RDSEED is available
  199.     //
  200.     CPUID
  201.     // store results
  202.     MOV EDI, Reg_EAX
  203.     MOV Cardinal PTR [EDI], EAX
  204.     MOV EAX, Reg_EBX
  205.     MOV EDI, Reg_ECX
  206.     MOV Cardinal PTR [EAX], EBX
  207.     MOV Cardinal PTR [EDI], ECX
  208.     MOV EAX, Reg_EDX
  209.     MOV Cardinal PTR [EAX], EDX
  210.     // restore context
  211.     POP EBX
  212.     POP EDI
  213.   {$ENDIF}
  214. end;
  215.  
  216. //Check if RDRAND and RDSEED are available
  217. class function TRandomAsm.CPUID_RDRAND_RDSEED_Check: TCheck_RDRAND_RDSEED;
  218. var
  219.   tempVendorId: array [0..11] of AnsiChar;
  220.   HighValBase: Cardinal;
  221.   HighValExt1: Cardinal;
  222.   VersionInfo: Cardinal;
  223.   AdditionalInfo: Cardinal;
  224.   ExFeatures: Cardinal;
  225.   StdFeatures: Cardinal;
  226.   UnUsed1, UnUsed2: Cardinal;
  227.   NewFeatures: Cardinal;
  228. begin
  229.   Result.tc_RDRAND := false;
  230.   Result.tc_RDSEED := false;
  231.   //Check if CPUID istruction is valid testing the bit 21 of EFLAGS
  232.   if TRandomAsm.Is_CPUID_Valid then
  233.     begin
  234.       //Get the Vendor string with EAX = 0 and ECX = 0
  235.       CPUID_GeneralCall(0, 0, HighValBase, tempVendorID[0], tempVendorID[8], tempVendorID[4]);
  236.       //Verifiy that we are on CPU that we support
  237.       if (tempVendorId = VendorIDxIntel) OR (tempVendorId = VendorIDxAMD) then
  238.         begin
  239.           //Now check if RDRAND and RDSEED is supported inside the extended CPUID flags
  240.           if HighValbase >=1 then  //Supports extensions
  241.             begin
  242.               //With EAX = 1 AND ECX = 0 the Extension and the available of RDRAND can be read
  243.               CPUID_GeneralCall(1, 0, VersionInfo, AdditionalInfo, ExFeatures, StdFeatures);
  244.               //ExFeatures (ECX register) bit 30 is 1 if RDRAND is available
  245.               if (ExFeatures and ($1 shl 30)) <> 0 then
  246.                 Result.tc_RDRAND := true;
  247.               if HighValBase >= 7 then
  248.                 begin
  249.                   //With EAX = 7 AND ECX = 0 the NEW Extension and the available of RDSEED can be read
  250.                   CPUID_GeneralCall(7, 0, HighValExt1, NewFeatures, UnUsed1, UnUsed2);
  251.                   //New Features (EBX register) bit 18 is 1 if RDSEED is available
  252.                   if (NewFeatures and ($1 shl 18)) <> 0 then
  253.                     Result.tc_RDSEED := true;
  254.                 end;
  255.             end;
  256.         end;
  257.     end;
  258. end;
  259.  
  260. Initialization
  261.   begin
  262.     RDRAND_RDSEED_Available := TRandomAsm.CPUID_RDRAND_RDSEED_Check;
  263.   end;
  264.  
  265. end.

This is for usage, the form has two buttons normally disabled with their Onclick events and a FromCreate event):
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Button2: TButton;
  17.     procedure Button1Click(Sender: TObject);
  18.     procedure Button2Click(Sender: TObject);
  19.     procedure FormCreate(Sender: TObject);
  20.   private
  21.  
  22.   public
  23.  
  24.   end;
  25.  
  26. var
  27.   Form1: TForm1;
  28.  
  29. implementation
  30.  
  31. {$R *.lfm}
  32.  
  33. Uses URDRANDandRDSEED;
  34. var TestRandom: TRandomAsm;
  35.  
  36. { TForm1 }
  37.  
  38. procedure TForm1.Button1Click(Sender: TObject);
  39. begin
  40.   ShowMessage('RDRAND 64 bit number: '+inttoHex(TestRandom.GetRandomNumber(false)));
  41. end;
  42.  
  43. procedure TForm1.Button2Click(Sender: TObject);
  44. begin
  45.   ShowMessage('RDSEEK 64 bit number: '+inttoHex(TestRandom.GetRandomNumber(true)));
  46. end;
  47.  
  48. procedure TForm1.FormCreate(Sender: TObject);
  49. begin
  50.   Button1.Enabled := RDRAND_RDSEED_Available.tc_RDRAND;
  51.   Button2.Enabled := RDRAND_RDSEED_Available.tc_RDSEED;
  52. end;
  53.  
  54. end.

I haven't tested it with Lazarus / FPC (the code was manually adapted) but I have no doubt that it works.

Of course this can be improved.
« Last Edit: August 19, 2025, 11:18:20 pm by LeP »

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #5 on: August 20, 2025, 06:39:59 am »
The above suggestion from LeP is pretty much what I use on Raspberry Pi's, that have a hardware rdrand device called hwrand. Must be on the forum somewhere. I wrote it about 7-8 years ago and posted it here. On RPi 1, you have to enable it. On later versions it is enabled by default and available as /dev/hwrand. Note, as I wrote before, /dev/urandom is just as good with Linux kernels from 4.8 upwards. urandom also takes entropy from hardware resources if available. On modern windows (from vista onwards), the - standard - bcrypt librarry does the same thing, taking entropy from hardware resources.
There really is no need to use LeP's code although it is a good read and it works on 32 bit intel systems only.
Good csprng's have evolved quickly over the last years as has the theory behind it.

I have some more code for you.
First, the - partial - bcrypt API:
Code: Pascal  [Select][+][-]
  1. unit BCrypt;
  2.  
  3. interface
  4. { this partial bcrypt translation covers only the cryptographically
  5.   secure random generator (csprng) part. Takes some entropy from hardware }
  6. uses
  7.   Windows;
  8.  
  9. const
  10.   BCRYPT_LIB = 'bcrypt.dll';
  11.   { NTSTATUS values }
  12.   STATUS_SUCCESS = $00000000;
  13.   { Algorithm Identifiers }
  14.   BCRYPT_RNG_ALGORITHM       = 'RNG';
  15.   BCRYPT_RNG_FIPS186_DSA_ALGORITHM = 'FIPS186DSARNG';
  16.   BCRYPT_RNG_DUAL_EC_ALGORITHM = 'DUALECRNG';
  17.   { Flags }
  18.   BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = $00000001;
  19.   BCRYPT_USE_SYSTEM_PREFERRED_RNG  = $00000002;
  20.  
  21. type
  22.   BCRYPT_ALG_HANDLE = THandle;
  23.   PBCRYPT_ALG_HANDLE = ^BCRYPT_ALG_HANDLE;
  24.   NTSTATUS = LongInt;
  25.  
  26. // Function prototypes
  27. function BCryptOpenAlgorithmProvider(
  28.   phAlgorithm: PBCRYPT_ALG_HANDLE;
  29.   pszAlgId: LPCWSTR;
  30.   pszImplementation: LPCWSTR;
  31.   dwFlags: DWORD
  32. ): NTSTATUS; winapi; external BCRYPT_LIB;
  33.  
  34. function BCryptCloseAlgorithmProvider(
  35.   hAlgorithm: BCRYPT_ALG_HANDLE;
  36.   dwFlags: DWORD
  37. ): NTSTATUS; winapi; external BCRYPT_LIB;
  38.  
  39. function BCryptGenRandom(
  40.   hAlgorithm: BCRYPT_ALG_HANDLE;
  41.   pbBuffer: PUCHAR;
  42.   cbBuffer: ULONG;
  43.   dwFlags: DWORD
  44. ): NTSTATUS; winapi; external BCRYPT_LIB;
  45.  
  46. implementation
  47. end.
Use it like so:
Code: Pascal  [Select][+][-]
  1. function WinCSPRNG(var Buffer; Bytes: LongWord): Boolean;
  2. var
  3.   hAlgorithm: PBCRYPT_ALG_HANDLE;
  4. begin
  5.   Result := False;
  6.   if BCryptOpenAlgorithmProvider(@hAlgorithm, BCRYPT_RNG_ALGORITHM, nil, 0) = STATUS_SUCCESS then
  7.   try
  8.     if BCryptGenRandom(PtrUint(hAlgorithm), @Buffer, Bytes, 0) = STATUS_SUCCESS then
  9.       Result := True;
  10.   finally
  11.     BCryptCloseAlgorithmProvider(ptrUint(hAlgorithm), 0);
  12.   end;
  13. end;

Then some code for linux:
Code: Pascal  [Select][+][-]
  1. { The Linux CSPRNG can simply be read from /dev/urandom on modern kernels >= 4.8
  2.   and is cryptographically secure, the kernel random/urandom is equal to hwrng.
  3.   Older kernels, though, used a simpler less secure PRNG, without taking
  4.   entropy from the hardware }
  5. function UnixCSPRNG(var Buffer; Bytes: LongWord): Boolean;
  6. var
  7.   f: File of byte;
  8.   s:string;
  9. begin
  10.   Result := False;
  11.   System.Assign(f, '/dev/urandom'); // or /dev/hwrng if available
  12.   Reset(f, 1);
  13.   try
  14.     BlockRead(f, Buffer, Bytes);
  15.     Result := True;
  16.   finally
  17.     System.Close(f);
  18.   end;
  19. end;
And a Shannon entropy test, widely used, including in bigcrush:
Code: Pascal  [Select][+][-]
  1. function CalcShannonEntropy(const Buffer:Array of byte): Double;
  2. var
  3.   Counts: array[0..255] of Cardinal;  // Frequency of each byte value
  4.   i: Integer;
  5.   TotalBytes: Cardinal;
  6.   Probability: Double;
  7.   Buf:TBytes;
  8. begin
  9.   SetLength(Buf,length(Buffer));
  10.   move(Buffer[0],Buf[0],length(Buffer));
  11.   if High(Buf) = 0 then
  12.     Exit(0.0);
  13.  
  14.   // 1. Count byte frequencies
  15.   FillChar(Counts, SizeOf(Counts), 0);
  16.   for i := 0 to High(Buf) do
  17.     Inc(Counts[Buf[i]]);
  18.  
  19.   // 2. Calculate entropy
  20.   Result := 0.0;
  21.   TotalBytes := Length(Buf);
  22.  
  23.   for i := Low(Counts) to High(Counts) do
  24.   begin
  25.     if Counts[i] > 0 then
  26.     begin
  27.       Probability := Counts[i] / TotalBytes;
  28.       Result := Result - Probability * (Ln(Probability) / Ln(2));
  29.     end;
  30.   end;
  31. end;
Shannon scores from zero, no entropy at all to 8: maximum entropy.
Scores above 7 can be assumed csprng validated. Note further testing is always necessary, because Shannon can be spoofed with a well crafted malicious "csprng" according to literature, but it is a strong indicator.

Will add a small example later. I have combined the above two functions and the chacha20 one into a x-platform unit, but that is on another machine and will post that later.
For now, you can use Shannon to see if your own implementation makes sense  :D , then run bigcrush and the likes for confirmation. (My chacha20 version scores really high)
For the theory:
https://en.wikipedia.org/wiki/Entropy_(information_theory)
From there you can conclude that if your new algorithm passes Shannon you are more than half way there.

Very interested and available for help.

« Last Edit: August 20, 2025, 10:27:50 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

LeP

  • Jr. Member
  • **
  • Posts: 57
Re: Anyone interested in helping with a new random number generator?
« Reply #6 on: August 20, 2025, 08:46:58 am »
....  and it works on 32 bit intel systems only.
Why did you say that? It should work on 32-bit and 64-bit systems and applications and doesn't require any libraries.

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #7 on: August 20, 2025, 08:55:00 am »
Doesn't work on ARM/AARCH64 for instance? (Doesn't work on my mac mini M4, nor on my Raspberry Pies)
But I did not say your code wasn't good, because it is. I wrote that.

BTW: slightly improved Shannon test, uses the buffer directly:
Code: Pascal  [Select][+][-]
  1. function CalcShannonEntropy(const Buffer: array of Byte): Double;
  2. var
  3.   Counts: array[0..255] of Cardinal;  // Frequency of each byte value
  4.   i: Integer;
  5.   TotalBytes: Cardinal;
  6.   Probability: Double;
  7. begin
  8.   // Handle the edge case of an empty input
  9.   TotalBytes := Length(Buffer);
  10.   if TotalBytes = 0 then
  11.     Exit(0.0);
  12.  
  13.   // 1. Count byte frequencies
  14.   FillChar(Counts, SizeOf(Counts), 0);
  15.   for i := 0 to High(Buffer) do // Use the original Buffer directly!
  16.     Inc(Counts[Buffer[i]]);
  17.  
  18.   // 2. Calculate entropy
  19.   Result := 0.0;
  20.   for i := Low(Counts) to High(Counts) do
  21.   begin
  22.     if Counts[i] > 0 then
  23.     begin
  24.        Probability := Counts[i] / TotalBytes;
  25.        Result := Result - Probability * (Ln(Probability) / Ln(2));
  26.     end;
  27.   end;
  28. end;
Doesn't copy the buffer. Found that by code analyses from DeepSeek, re-validated by CoPilot. Both left my original code almost intact.


Also note the Linux version also does not need libraries and the windows version uses a system provided dll. (conceptionally about the same as a Unix provided standard device, at least to me)
Chacha20 is pure Pascal.
My only remark is cross-platform code. Your code isn't. But it is good.
Also note that your code is not really necessary anymore when using bcrypt although it might be slightly faster.
On linux it is slower. (Buffered entropy)
The code is fine, seems a misunderstanding.
« Last Edit: August 20, 2025, 09:25:36 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

LeP

  • Jr. Member
  • **
  • Posts: 57
Re: Anyone interested in helping with a new random number generator?
« Reply #8 on: August 20, 2025, 10:14:45 am »
Doesn't work on ARM/AARCH64 for instance? (Doesn't work on my mac mini M4, nor on my Raspberry Pies)
My only remark is cross-platform code. Your code isn't. But it is good.
Yes, and I wrote this in the first line of my post.
I know Arm-v7 but no such instructions exist.

And in Arm-v8 systems I think that these instructions (equivalent) were introduced recently (in the ARM-V8.5-A or M1 Apple ?) and they depends from external hardware.

Of course in the virtual environment (or simulated like "rosetta" o "rosetta 2") these instructions have meaning only if there is an equivalent hardware support.

I'm not concerned about criticism of the code. Every code has its own uses and can be good or bad depending on the uses, environments, and needs (and also the experiences of those who use it).
My goal, as I believe is that of this forum, is to learn and share knowledge.

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #9 on: August 20, 2025, 10:25:48 am »
On armv8.5 RNDR (similar to rdrand) and RNDRRS (similar to rdseed)
On Apple it is not documented, though.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

ad1mt

  • Sr. Member
  • ****
  • Posts: 465
    • Mark Taylor's Home Page
Re: Anyone interested in helping with a new random number generator?
« Reply #10 on: August 20, 2025, 08:24:47 pm »
There have been suggestions about using hardware RNGs, but that does not work in cryptography where each end might be running on a different OS and different hardware. The RNG must be software based, to guarantee identical behaviour at each end.

I keep being told that "I should not design my own encryption", but as long as I only use it in my own apps for my own purposes, then it's at my own risk. I find it interesting that the same people who say that, also seem to have no valid criticism of my algortihm.

I welcome all criticism of my algortihm.
« Last Edit: August 20, 2025, 09:07:39 pm by ad1mt »

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #11 on: August 20, 2025, 08:50:43 pm »
There have been suggestions about using hardware RNGs, but that does not work in cryptography where each end might be running on a different OS and different hardware. The RNG must be software based, to guarantee identical behaviour at each end.
Who taught you that? So what you really want is not a csprng, but a prng? That statement does not make sense. Those are very different.
A cryptographically secure random is not repeatable (only by statistical accident) while a Pseudo random is repeatable given the same seed. Which of the two do you want?
Quote
I keep being told that "I should not design my own encryption", but as long as I only use it in my own apps for my own purposes, and it's at my own risk. I find it interesting that the same people who say that, also seem to have no valid criticism of my algortihm.
If I can analyze it, I can show and prove it is correct or not. Show us the code.
« Last Edit: August 20, 2025, 08:56:24 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

ad1mt

  • Sr. Member
  • ****
  • Posts: 465
    • Mark Taylor's Home Page
Re: Anyone interested in helping with a new random number generator?
« Reply #12 on: August 20, 2025, 09:06:50 pm »
One of the questions I have is... how does one break a LGC generator?
I would like to see some code (preferrably Pascal rather than the dreaded C), which demonstrates how it can be done.

From the descriptions I've seen online, the method seems to be just a brute-force trial of each unknown variable, then comparing each calculated result, with the observed results.

However, even with only 4 variables, and assuming 32bit integers, that is 2^128 possibilities, which is a big number to brute-force.

ad1mt

  • Sr. Member
  • ****
  • Posts: 465
    • Mark Taylor's Home Page
Re: Anyone interested in helping with a new random number generator?
« Reply #13 on: August 20, 2025, 09:34:26 pm »
The reason I'm asking about LCGs, is that my algorithm consists of six LCGs running in parallel, with the final output random number calculated by an xor of all six seed values after each calculation round.

The question then is... which values do I choose for the six LCG variables multiplier, modulus & initial seed?  (I always use a constant of 1).

From much experimentation (but very little theory), I've discovered that if the values chosen for the variables are (a) odd, and (b) at least a factor of 1000 apart, then any numbers can be chosen. To give a reliable 16 bit output, the minimum value for each variable needs to be 131072.

Although I have not been able to test every combination, I have tested a large number of combinations, and they all pass statistical tests. And the period of each LCG combination is enormous (I've not been able to calculate the period).

Assuming 32bit integers, then to break this by a brute-force approach would have calculate (2^32)^(6*3) = 2^18 possibilities. That is a number with 174 digits.

The interesting thing about this scheme is: because of the extremely large number of possible combinations, each set of six LCG variables is never used more than once.  So in a cryptography context it is in effect a one-time-pad. Even if an adversary were to manage to break one encrypted message, that would not help to break any others, and they would have to start again.

My main doubt about this is whether the method used to break a single LCG can be applied to breaking six LCGs running in parallel, within a reasonable time.

The other interesting thing about this is if you choose the minimum value for each of the 18 variables, then cycle through all possible values one-at-a-time after a repetition is detected, then you effectively have a RNG with an infinite period.
« Last Edit: August 20, 2025, 09:44:14 pm by ad1mt »

Thaddy

  • Hero Member
  • *****
  • Posts: 18363
  • Here stood a man who saw the Elbe and jumped it.
Re: Anyone interested in helping with a new random number generator?
« Reply #14 on: August 21, 2025, 06:01:07 am »
If the LCG's are the same then if one is broken that follows that all can be broken even if seeded with different seeds.
I will give an example of such a parallel generator a bit later this morning.
If you use 6 different LCG's it becomes harder to break, e.g. take 6 algorithms from the Marsalia suite in our wiki.
You can use the six seeds as keys to make them repeatable. In my chacha20 generator you can add key and nonce and remove the unixtime seed to make it repeatable. (Code is there)

My wiki entry contains many prng's in a single class, devised by the famous Professor George Marsaglia.
https://wiki.freepascal.org/Marsaglia%27s_pseudo_random_number_generators
Look at the class I wrote there: you can take six different algorithms from that and run them in parallel.
Your observation about uneven numbers is correct. Best fits are e.g prime, pseudo prime and uneven Tau numbers.
Tau numbers are likely perfect squares. Here's some code to generate them:
Code: Pascal  [Select][+][-]
  1. program Tau_number;
  2. {$mode objfpc}{$I-}
  3. uses math;
  4. function CountDivisors(n: Integer): Integer;inline;
  5. var
  6.   i:integer = 1;
  7.   k:integer = 2;
  8.   j:integer;
  9. begin
  10.   Result := 0;
  11.   if (n mod 2) = 0 then
  12.     k := 1;
  13.   while i * i <= n do
  14.   begin
  15.     if (n mod i) = 0 then
  16.     begin
  17.       inc(Result);
  18.       j := n div i;
  19.       if j <> i then
  20.         inc(Result);
  21.     end;
  22.     inc(i, k);
  23.   end;
  24. end;
  25.  
  26. function IsPerfectSquare(n: Integer): Boolean; inline;
  27. begin
  28.   Result := Frac(Sqrt(n)) = 0;
  29. end;
  30.  
  31. var
  32.   count:integer = 0;
  33.   i:integer = 1;
  34.   tf:integer;
  35. begin
  36.   Writeln('The odd tau numbers in the first 100000 numbers are:');
  37.   while count < 100000 do
  38.   begin
  39.     tf := CountDivisors(i);
  40.     if i mod tf = 0 then
  41.     begin
  42.       if odd(i) then
  43.       begin
  44.          write(IsPerfectSquare(i));
  45.          writeln(i:10);
  46.       end;
  47.       inc(count);
  48.     end;
  49.     inc(i);
  50.   end;
  51.   readln;
  52. end.

« Last Edit: August 21, 2025, 06:37:24 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

 

TinyPortal © 2005-2018