mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
80 lines
2.1 KiB
Python
80 lines
2.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import hashlib
|
|
import secrets
|
|
|
|
|
|
def rfc2440_hash_password(password, salt=None):
|
|
# Match tor --hash-password
|
|
# secret_to_key_rfc2440
|
|
|
|
EXPBIAS = 6
|
|
c = 96
|
|
count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS)
|
|
|
|
if salt is None:
|
|
salt = secrets.token_bytes(8)
|
|
assert len(salt) == 8
|
|
|
|
hashbytes = salt + password.encode("utf-8")
|
|
len_hashbytes = len(hashbytes)
|
|
h = hashlib.sha1()
|
|
|
|
while count > 0:
|
|
if count >= len_hashbytes:
|
|
h.update(hashbytes)
|
|
count -= len_hashbytes
|
|
continue
|
|
h.update(hashbytes[:count])
|
|
break
|
|
rv = "16:" + salt.hex() + "60" + h.hexdigest()
|
|
return rv.upper()
|
|
|
|
|
|
def verify_rfc2440_password(stored_hash, provided_password):
|
|
"""
|
|
Verifies a password against a hash generated by rfc2440_hash_password.
|
|
|
|
Args:
|
|
stored_hash (str): The hash string stored (e.g., "16:<salt>60<hash>").
|
|
provided_password (str): The password attempt to verify.
|
|
|
|
Returns:
|
|
bool: True if the password matches the hash, False otherwise.
|
|
"""
|
|
try:
|
|
parts = stored_hash.upper().split(":")
|
|
if len(parts) != 2 or parts[0] != "16":
|
|
return False
|
|
|
|
salt_hex_plus_hash_hex = parts[1]
|
|
separator_index = salt_hex_plus_hash_hex.find("60")
|
|
if separator_index != 16:
|
|
return False
|
|
|
|
salt_hex = salt_hex_plus_hash_hex[:separator_index]
|
|
expected_hash_hex = salt_hex_plus_hash_hex[separator_index + 2 :]
|
|
|
|
salt = bytes.fromhex(salt_hex)
|
|
except (ValueError, IndexError):
|
|
return False
|
|
|
|
EXPBIAS = 6
|
|
c = 96
|
|
count = (16 + (c & 15)) << ((c >> 4) + EXPBIAS)
|
|
|
|
hashbytes = salt + provided_password.encode("utf-8")
|
|
len_hashbytes = len(hashbytes)
|
|
h = hashlib.sha1()
|
|
|
|
while count > 0:
|
|
if count >= len_hashbytes:
|
|
h.update(hashbytes)
|
|
count -= len_hashbytes
|
|
continue
|
|
h.update(hashbytes[:count])
|
|
break
|
|
|
|
calculated_hash_hex = h.hexdigest().upper()
|
|
return secrets.compare_digest(calculated_hash_hex, expected_hash_hex)
|