Skip to content

Commit c3f6099

Browse files
caladdbsanders
authored andcommitted
INTERNAL: Try to generate passwords in a more secure way
The included unit test covers the code path that I haven't been able to reach on actual systems. The `getrandom` system call will never block once the entropy pool has been initialized, which seems to happen before any of our code would ever run, probably due to only needing to collect 4096 bits of entropy, and modern CPUs having hardware random number seed generation. The RDRAND x86 instruction has been available since 2012 and the Linux kernel will use it to initialize the entropy pool.
1 parent 3dc1890 commit c3f6099

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

common/src/stack/pylib/stack/password.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# @copyright@
77

88
import crypt
9+
import os
910
import random
1011
import time
1112
import string
@@ -15,7 +16,20 @@ class Password:
1516
def get_rand(self, num_bytes=16, choices=string.ascii_letters + string.digits):
1617
password = ''
1718
for _ in range(num_bytes):
18-
random.seed(int(time.time() * pow(10, 9)))
19+
# Note: The `getrandom` system call will never block once the entropy
20+
# pool has been initialized, which seems to happen before any of our
21+
# code would ever run, probably due to only needing to collect 4096 bits
22+
# of entropy. The RDRAND x86 instruction has been available since 2012
23+
# and the Linux kernel will use it to initialize the entropy pool.
24+
25+
try:
26+
# Try seeding random in a non-blocking way
27+
# Note: Using 64 bits (8 bytes) of entropy per password byte
28+
random.seed(os.getrandom(8, flags=os.GRND_NONBLOCK))
29+
except BlockingIOError:
30+
# Blocked getting entropy from the OS, so use milliseconds instead
31+
random.seed(int(time.time() * pow(10, 9)))
32+
1933
password += random.choice(choices)
2034

2135
return password
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from stack.password import Password
2+
3+
from unittest.mock import patch
4+
5+
class TestPassword:
6+
@patch("os.getrandom")
7+
@patch("time.time")
8+
def test_get_rand_blocking_io(self, mock_time, mock_getrandom):
9+
mock_getrandom.side_effect = BlockingIOError
10+
11+
assert len(Password().get_rand(16)) == 16
12+
assert mock_time.call_count == 16

0 commit comments

Comments
 (0)