diff --git a/data_safe_haven/functions/strings.py b/data_safe_haven/functions/strings.py index 776faeb412..7cca89e2e4 100644 --- a/data_safe_haven/functions/strings.py +++ b/data_safe_haven/functions/strings.py @@ -26,9 +26,18 @@ def b64encode(input_string: str) -> str: return base64.b64encode(input_string.encode("utf-8")).decode() -def bcrypt_encode(input_string: str) -> str: - """Use bcrypt to encrypt an input string""" - encrypted_bytes = bcrypt.hashpw(input_string.encode("utf-8"), bcrypt.gensalt()) +def bcrypt_encode(input_string: str, salt_generator: str) -> str: + """ + Use bcrypt to encrypt an input string. + See https://en.wikipedia.org/wiki/Bcrypt#Description for structure. + """ + # We must use between 4 and 31 hashing rounds + rounds = 4 + len(salt_generator) % 28 + # bcrypt uses a different Base64 algorithm + salt = bcrypt._bcrypt.encode_base64(seeded_uuid(salt_generator).bytes) + # This string is $algorithm$cost$salt + prefix_bytes = b"$".join([b"", b"2b", f"{rounds:02d}".encode("utf-8"), salt]) + encrypted_bytes = bcrypt.hashpw(input_string.encode("utf-8"), prefix_bytes) return encrypted_bytes.decode(encoding="utf-8") diff --git a/data_safe_haven/pulumi/components/sre_dns_server.py b/data_safe_haven/pulumi/components/sre_dns_server.py index e499e7c3d4..9466634f3f 100644 --- a/data_safe_haven/pulumi/components/sre_dns_server.py +++ b/data_safe_haven/pulumi/components/sre_dns_server.py @@ -74,7 +74,7 @@ def __init__( adguard_adguardhome_yaml_contents = Output.all( admin_username=props.admin_username, admin_password_encrypted=props.admin_password.apply( - lambda p: bcrypt_encode(p) + lambda passwd: bcrypt_encode(passwd, stack_name) ), # Use Azure virtual DNS server as upstream # https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16 diff --git a/typings/bcrypt/__init__.pyi b/typings/bcrypt/__init__.pyi index f8cb0e2bae..50e9efcca4 100644 --- a/typings/bcrypt/__init__.pyi +++ b/typings/bcrypt/__init__.pyi @@ -1,2 +1,3 @@ -def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes: ... +from . import _bcrypt as _bcrypt + def hashpw(password: bytes, salt: bytes) -> bytes: ... diff --git a/typings/bcrypt/_bcrypt.pyi b/typings/bcrypt/_bcrypt.pyi new file mode 100644 index 0000000000..9232e06eee --- /dev/null +++ b/typings/bcrypt/_bcrypt.pyi @@ -0,0 +1 @@ +def encode_base64(data: bytes) -> bytes: ...