unit URDRANDandRDSEED;
{$mode Delphi}
interface
uses
SysUtils;
//Explicity check if RDRAND and RDSEED ara avilable
type
TCheck_RDRAND_RDSEED = record
tc_RDRAND: boolean;
tc_RDSEED: boolean;
end;
type
TRandomAsm = record
strict private
function GetRandomRDRAND: UInt64; register;
function GetRandomRDSEED: UInt64; register;
class function Is_CPUID_Valid: boolean; register; static;
class procedure CPUID_GeneralCall(InEAX: cardinal; InECX: cardinal; out Reg_EAX, Reg_EBX, Reg_ECX, Reg_EDX); stdcall; static;
private
class function CPUID_RDRAND_RDSEED_Check: TCheck_RDRAND_RDSEED; static;
public
function GetRandomNumber(const USE_RNDSEED: boolean = false): UInt64;
end;
var
RDRAND_RDSEED_Available: TCheck_RDRAND_RDSEED = (tc_RDRAND: false; tc_RDSEED: false);
implementation
const
//ID To identify CPU Vendor, the are a multitude .. but we focalize on this
VendorIDxIntel: array [0..11] of AnsiChar = 'GenuineIntel';
VendorIDxAMD: array [0..11] of AnsiChar = 'AuthenticAMD';
{ TRandomAsm }
function TRandomAsm.GetRandomNumber(const USE_RNDSEED: boolean = false): UInt64;
begin
if (RDRAND_RDSEED_Available.tc_RDSEED and USE_RNDSEED) or (RDRAND_RDSEED_Available.tc_RDSEED and (not RDRAND_RDSEED_Available.tc_RDRAND)) then
result := GetRandomRDSEED
else
if RDRAND_RDSEED_Available.tc_RDRAND then
result := GetRandomRDRAND
else
result := 0;
end;
function TRandomAsm.GetRandomRDRAND: UInt64; register;
asm
{$IFDEF CPUX64}
mov RCX, $0
mov RAX, $FFFFFFFFFFFFFFFF
@here:
//inc RCX
rdrand RAX //RAX return value 64 bit (WIN32)
jnc @here
//or RAX, RCX
{$ELSE}
mov ECX, $0
mov EDX, $FFFFFFFF
mov EAX, EDX
@here:
//inc ECX
//mov EDX, EAX
rdseed EAX //EAX return value 32 bit (WIN32)
jnc @here
@here1:
//inc ECX
rdrand EDX //EDX:EAX return value 64 bit (WIN32)
jnc @here1
//or EAX, ECX
{$ENDIF}
end;
function TRandomAsm.GetRandomRDSEED: UInt64; register;
asm
{$IFDEF CPUX64}
mov RCX, $0
mov RAX, $FFFFFFFFFFFFFFFF
@here:
//inc RCX
rdseed RAX //RAX return value 64 bit (WIN32)
jnc @here
//or RAX, RCX
{$ELSE}
mov ECX, $0
mov EDX, $FFFFFFFF
mov EAX, EDX
@here:
//inc ECX
//mov EDX, EAX
rdseed EAX //EAX return value 32 bit (WIN32)
jnc @here
@here1:
//inc ECX
rdseed EDX //EDX:EAX return value 64 bit (WIN32)
jnc @here1
//or EAX, ECX
{$ENDIF}
end;
//Tested in Win32 and Win64 Protected Mode, tested in virtual mode (WINXP - WIN11 32 bit and 64 bit), not tested in real adress mode
//The Intel Documentation has more detail about CPUID
//Jedi project has implemented TCPUInfo with more details.
//First check that the CPU supports CPUID instructions. There are some exceptions with this rule,
//but with very very old processors
class function TRandomAsm.Is_CPUID_Valid: boolean; register;
asm
{$IFDEF CPUX64}
pushfq //Save EFLAGS
pushfq //Store EFLAGS
xor qword [esp], $00200000 //Invert the ID bit in stored EFLAGS
popfq //Load stored EFLAGS (with ID bit inverted)
pushfq //Store EFLAGS again (ID bit may or may not be inverted)
pop rax //eax = modified EFLAGS (ID bit may or may not be inverted)
xor rax, qword [esp] //eax = whichever bits were changed
popfq //Restore original EFLAGS
and RAX, $00200000 //eax = zero if ID bit can't be changed, else non-zero
jz @quit
mov RAX, $01 //If the Result is boolean, the return parameter should be in A??? (true if A??? <> 0)
@quit:
{$ELSE}
pushfd //Save EFLAGS
pushfd //Store EFLAGS
xor dword [esp], $00200000 //Invert the ID bit in stored EFLAGS
popfd //Load stored EFLAGS (with ID bit inverted)
pushfd //Store EFLAGS again (ID bit may or may not be inverted)
pop eax //eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] //eax = whichever bits were changed
popfd //Restore original EFLAGS
and eax, $00200000 //eax = zero if ID bit can't be changed, else non-zero
jz @quit
mov EAX, $01 //If the Result is boolean, the return parameter should be in AL (true if AL <> 0)
@quit:
{$ENDIF}
end;
//1) Check that the CPU is an INTEL CPU, we don't know nothing about other's
// We can presume the AMD modern processors have the same check of INTEL, but only for some instructions.
// No test were made to verify this (no AMD processor available)
//
//2) Catch the features of the CPU in use
//
//3) Catch the new features of the CPU in use
//
class procedure TRandomAsm.CPUID_GeneralCall(InEAX: cardinal; InECX: cardinal; out Reg_EAX, Reg_EBX, Reg_ECX, Reg_EDX); stdcall;
asm
{$IFDEF CPUX64}
// save context
PUSH RBX
// CPUID
MOV EAX, InEAX //Generic function
MOV ECX, InECX //Generic sub function
//
//For CPU VENDOR STRING EAX := $0
//ECX is not used when EAX = $0
//
//For CPU Extension EAX := $01
//ECX is not used when EAX = $01
//
//For CPU New Extension EAX := $07
//ECX should be $00 to read if RDSEED is available
//
CPUID
// store results
MOV R8, Reg_EAX
MOV R9, Reg_EBX
MOV R10, Reg_ECX
MOV R11, Reg_EDX
MOV Cardinal PTR [R8], EAX
MOV Cardinal PTR [R9], EBX
MOV Cardinal PTR [R10], ECX
MOV Cardinal PTR [R11], EDX
// restore context
POP RBX
{$ELSE}
// save context
PUSH EDI
PUSH EBX
// CPUID
MOV EAX, InEAX //Generic function
MOV ECX, InECX //Generic sub function
//
//For CPU VENDOR STRING EAX := $0
//ECX is not used when EAX = $0
//
//For CPU Extension EAX := $01
//ECX is not used when EAX = $01
//
//For CPU New Extension EAX := $07
//ECX should be $00 to read if RDSEED is available
//
CPUID
// store results
MOV EDI, Reg_EAX
MOV Cardinal PTR [EDI], EAX
MOV EAX, Reg_EBX
MOV EDI, Reg_ECX
MOV Cardinal PTR [EAX], EBX
MOV Cardinal PTR [EDI], ECX
MOV EAX, Reg_EDX
MOV Cardinal PTR [EAX], EDX
// restore context
POP EBX
POP EDI
{$ENDIF}
end;
//Check if RDRAND and RDSEED are available
class function TRandomAsm.CPUID_RDRAND_RDSEED_Check: TCheck_RDRAND_RDSEED;
var
tempVendorId: array [0..11] of AnsiChar;
HighValBase: Cardinal;
HighValExt1: Cardinal;
VersionInfo: Cardinal;
AdditionalInfo: Cardinal;
ExFeatures: Cardinal;
StdFeatures: Cardinal;
UnUsed1, UnUsed2: Cardinal;
NewFeatures: Cardinal;
begin
Result.tc_RDRAND := false;
Result.tc_RDSEED := false;
//Check if CPUID istruction is valid testing the bit 21 of EFLAGS
if TRandomAsm.Is_CPUID_Valid then
begin
//Get the Vendor string with EAX = 0 and ECX = 0
CPUID_GeneralCall(0, 0, HighValBase, tempVendorID[0], tempVendorID[8], tempVendorID[4]);
//Verifiy that we are on CPU that we support
if (tempVendorId = VendorIDxIntel) OR (tempVendorId = VendorIDxAMD) then
begin
//Now check if RDRAND and RDSEED is supported inside the extended CPUID flags
if HighValbase >=1 then //Supports extensions
begin
//With EAX = 1 AND ECX = 0 the Extension and the available of RDRAND can be read
CPUID_GeneralCall(1, 0, VersionInfo, AdditionalInfo, ExFeatures, StdFeatures);
//ExFeatures (ECX register) bit 30 is 1 if RDRAND is available
if (ExFeatures and ($1 shl 30)) <> 0 then
Result.tc_RDRAND := true;
if HighValBase >= 7 then
begin
//With EAX = 7 AND ECX = 0 the NEW Extension and the available of RDSEED can be read
CPUID_GeneralCall(7, 0, HighValExt1, NewFeatures, UnUsed1, UnUsed2);
//New Features (EBX register) bit 18 is 1 if RDSEED is available
if (NewFeatures and ($1 shl 18)) <> 0 then
Result.tc_RDSEED := true;
end;
end;
end;
end;
end;
Initialization
begin
RDRAND_RDSEED_Available := TRandomAsm.CPUID_RDRAND_RDSEED_Check;
end;
end.