-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
crypto/rand: Currently using deprecated API for random number generation on Windows #33542
Comments
CC @FiloSottile @agl |
I noticed, recently, that crypto/rand used CryptGenRandom instead of (BCryptGenRandom). So I decided to call it manually. In case anyone is interested:
|
The RNG function that we should be using on Windows is actually RtlGenRandom. I'll submit a patch to use that. We already have a wrapper around it in |
Change https://golang.org/cl/210057 mentions this issue: |
Most confusing documentation ever, Microsoft... |
Out of curiosity (forgive my minimal experience in this area), why RtlGenRandom? I was curious and looked at the docs for these APIs, and it seems like the docs for RtlGenRandom say "use CryptGenRandom" (the thing that this issue is saying is deprecated), and the docs for CryptGenRandom say "use CNG", i.e. BCryptGenRandom. |
@zikaeroh According to this rust issue CryptGenRandom eventually makes call to RtlGenRandom. Another comment, in that issue, makes a more compelling argument: rust-random/rand#111 (comment). The comment explains that Microsoft uses it in their C standard library function Looking at rust source I see references to both RtlGenRandom and BCryptGenRandom. |
Time to reverse engineer some stuff. RtlGenRandom/SystemFunction036 in advapi32.dll is a stub for cryptbase.dll, which has it as: bool SystemFunction036(void *buf, uint32_t len)
{
return ProcessPrng(buf, len);
} ProcessPrng lives in bcryptPrimitives.dll as: bool ProcessPrng(void *buf, uint64_t len)
{
bool ret = true;
uint64_t bytes_to_read;
int64_t out;
uint128_t *aes_key;
success_1 = 1;
if (g_rngState == 1)
{
ret = false;
out = 0;
AesRNGState_select(&aes_key);
for (; len; len -= bytes_to_read)
{
bytes_to_read = 0x10000;
if (len < 0x10000)
bytes_to_read = len;
buf += bytes_to_read;
ret = AesRNGState_generate(aes_key, buf, bytes_to_read, &out);
}
}
else
{
if (g_rngState != 2)
{
for (;;)
*(unsigned int *)0 = 0x78676E72;
}
EmergencyRng(buf, len);
}
return ret;
} From there I assume that this is some sort of per-process and per-cpu expansion machine mostly in userspace, seeded with some kernel entropy. |
The RtlGenRandom doco https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom suggests that we should use CryptGenRandom
@zx2c4 why do you prefer RtlGenRandom to current CryptGenRandom? I am fine with either. Also, if we follow CryptGenRandom doco https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
We should, probably, use BCryptGenRandom. No? Alex |
For reference it seems that:
|
with latest Golang not supporting Windows XP would it be fine to replace the backend stdlib is using to BCryptGenRandom? go/src/crypto/rand/rand_windows.go Line 51 in 1961d8d
if yes, i can send the change for that.
the DLL export of SystemFunction036 is not guaranteed in the future, so i don't think this function should be used. |
SystemFunction036 isn't going to be going away. Not a chance Microsoft would break decades of applications. That's now proper API. |
it's hard to trust this based on the documentation for the function. Windows APIs have definitely gone away in the past. are there actual benefits of using RtlGenRandom over BCryptGenRandom? |
Change https://golang.org/cl/232860 mentions this issue: |
see https://go-review.googlesource.com/c/go/+/232860
|
The existing function that is used is CryptGenRandom. This function and the whole underling API is deprecated. Use the function BCryptGenRandom from the new recommended API called "Cryptography API: Next Generation (CNG)". Fixes golang#33542
The existing function that is used is CryptGenRandom. This function and the whole underling API is deprecated. Use the function BCryptGenRandom from the new recommended API called "Cryptography API: Next Generation (CNG)". Fixes golang#33542
We have 2 alternative CLs to replace CryptGenRandom CL 210057 uses RtlGenRandom (aka advapi32.SystemFunction036) CL 232860 uses BCryptGenRandom Lets decide here which one to pick. I don't have preference. What about others? Thank you. Alex |
RtlGenRandom:
BCryptGenRandom:
The boringssl authors did heavy research here and contacted the owners of the bcrypt code. They decided to stick with RtlGenRandom and only fall back to BCryptGenRandom when absolutely desperate, for the UWP case, which Go does not support anyway. I suggest we follow their lead, and stick with RtlGenRandom. When (if?) we eventually port to UWP, we can reinvestigate, and also see the status of boringssl, to see whether we should still prefer RtlGenRandom and only fallback if necessary, or switch to BCryptGenRandom entirely. But that time is not now, and so I'd suggest we stick with the tried and true RtlGenRandom instead getting too caught up in whatever microsoft's new crypto api of the year presently is. |
Thank you, Jason. (You can be pretty good salesmen!) Anyone wants to say few good words about BCryptGenRandom? Apart from this page https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom vaguely recommends it as a replacement for CryptGenRandom (which we use now). Alex |
@alexbrainman hello, when is the deadline for one of the changes to merge with respect to the golang 1.15 release cycle? on Monday, i can try asking someone from the Microsoft cryptography team to add an up-to-date comment here to facilitate your mediation. @zx2c4 hello, i'm not convinced the ticket in question landed on a good rationale for the default usage for RtlGenRandom on non-UWP platforms for boringssl.
i did some benchmarks on a couple of systems with different versions of Windows and different CPUs and bellow i have posted the results. please let me know if you think my tests are flawed in any way. results from a Windows 7 system with Intel-i3:
results from a Windows 10 system with Intel-i7:
TL;DR summaryThe functions behave close in performance where the Windows 10 / i7 system gives better results for RtlGenRandom, while the Windows 7 / i3 system gives better results for BCryptGenRandom with BCRYPT_USE_SYSTEM_PREFERRED_RNG. source code:// performance comparison between RtlGenRandom and BCryptGenRandom
// with BCRYPT_USE_SYSTEM_PREFERRED_RNG.
//
// gcc -O3 -std=c99 -Wall test.c -lbcrypt && a
#include <windows.h>
#include <stdio.h>
#include <bcrypt.h>
BOOLEAN(APIENTRY *RtlGenRandom)
(void *, ULONG);
DOUBLE GetTime()
{
LARGE_INTEGER t, f;
QueryPerformanceCounter(&t);
QueryPerformanceFrequency(&f);
return (DOUBLE) t.QuadPart / (DOUBLE) f.QuadPart;
}
int PrepareRtlGenRandom()
{
LoadLibrary("advapi32.dll");
HMODULE advapi = GetModuleHandle("advapi32.dll");
if (!advapi) {
puts("advapi32 load error");
return 1;
}
RtlGenRandom = (BOOLEAN(APIENTRY*)(void *, ULONG)) GetProcAddress(advapi, "SystemFunction036");
if (!RtlGenRandom) {
puts("RtlGenRandom proc error");
return 1;
}
return 0;
}
int RunSingleTest(PBYTE Buffer, DWORD BufferSize, DWORD TestIterations)
{
DOUBLE StartTime;
printf("\nBufferSize: %lu\n", BufferSize);
// test BCryptGenRandom
StartTime = GetTime();
for (int i = 0; i < TestIterations; i++) {
if (BCryptGenRandom(
NULL,
Buffer,
BufferSize,
BCRYPT_USE_SYSTEM_PREFERRED_RNG)) {
puts("BCryptGenRandom failed");
return 1;
}
}
printf("BCryptGenRandom: %fs\n", GetTime() - StartTime);
// -------------------------------------------------------------------------
// test RtlGenRandom
StartTime = GetTime();
for (int i = 0; i < TestIterations; i++) {
if (!RtlGenRandom(Buffer, BufferSize)) {
puts("RtlGenRandom failed");
return 1;
}
}
printf("RtlGenRandom: %fs\n", GetTime() - StartTime);
return 0;
}
int main(void)
{
const DWORD TestIterations = 100000;
BYTE Buffer[16384];
DWORD MaxBufferSize = sizeof(Buffer);
memset(Buffer, 0, MaxBufferSize);
// -------------------------------------------------------------------------
// verify the timer
DOUBLE StartTime = GetTime();
Sleep(1000);
printf("Veryfing timer precision; result should be close to 1.0, got %f\n", GetTime() - StartTime);
// -------------------------------------------------------------------------
// prepare RtlGenRandom
if (PrepareRtlGenRandom()) {
return 1;
}
// -------------------------------------------------------------------------
// test various buffer sizes
printf("TestIterations: %lu\n", TestIterations);
for (DWORD BufferSize = 1; BufferSize <= MaxBufferSize; BufferSize *= 2) {
if (RunSingleTest(Buffer, BufferSize, TestIterations)) {
return 1;
}
}
} |
The existing function that is used is CryptGenRandom. This function and the whole underling API is deprecated. Use the function BCryptGenRandom from the new recommended API called "Cryptography API: Next Generation (CNG)". Preload and use the BCRYPT_RNG_ALGORITHM provider. It follows the standards: FIPS 186-2, FIPS 140-2, NIST SP 800-90 Fixes golang#33542
I think deadline for 1.15 is already past. Perhaps it is OK to finish existing CL. I do not know. @ianlancetaylor asked on PS3 of https://go-review.googlesource.com/c/go/+/210057/
Perhaps it is OK to resolve this issue for 1.15.
I am not the one who will decide. I don't consider myself a security expert. I will let others decide. Alex |
The existing function that is used is CryptGenRandom. This function and the whole underling API is deprecated. Use the function BCryptGenRandom from the new recommended API called "Cryptography API: Next Generation (CNG)". Preload and use the BCRYPT_RNG_ALGORITHM provider. It follows the standards: FIPS 186-2, FIPS 140-2, NIST SP 800-90 Fixes golang#33542
The existing function that is used is CryptGenRandom. This function and the whole underling API is deprecated. Use the function BCryptGenRandom from the new recommended API called "Cryptography API: Next Generation (CNG)". Preload and use the BCRYPT_RNG_ALGORITHM provider. It follows the standards: FIPS 186-2, FIPS 140-2, NIST SP 800-90 Fixes golang#33542
Looks like BoringSSL, Chromium, Firefox, and Rust all use |
For the record: CL 536235 replaced |
What version of Go are you using (
go version
)?N/A
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?Any release of Windows supported by Microsoft
What did you do?
Reviewing cryptographic protocols for a downstream project
What did you expect to see?
Use of a modern Windows API to retrieve entropy, namely BCryptGenRandom from CryptoNG, which is compliant with CTR_DRBG from NIST SP800-90.
What did you see instead?
Go calls CryptGenRandom from a deprecated API. The algorithm is not fully documented and is only known in part.
The text was updated successfully, but these errors were encountered: