add salt parameter to hash generation for sha256 plugin

This commit is contained in:
Matthieu Bourgain 2024-04-19 09:43:10 +02:00
commit 805c3ea248
No known key found for this signature in database
GPG key ID: 33BA95C808890C39
4 changed files with 194 additions and 6 deletions

View file

@ -0,0 +1,105 @@
"""Generate MySQL caching_sha2_password hash for a given password and salt."""
import hashlib
def to64(v: int, n: int) -> str:
"""Convert a 32-bit integer to a base-64 string"""
i64 = (
[".", "/"]
+ [chr(x) for x in range(48, 58)]
+ [chr(x) for x in range(65, 91)]
+ [chr(x) for x in range(97, 123)]
)
result: str = ""
while n > 0:
n -= 1
result += i64[v & 0x3F]
v >>= 6
return result
def hashlib_sha256(data: bytes) -> bytes:
"""Return SHA-256 digest from hashlib ."""
return hashlib.sha256(data).digest()
def sha256_digest(key: str, salt: str, loops: int) -> str:
"""Return a SHA-256 digest of the concatenation of the key, the salt, and the key, repeated as necessary."""
# https://www.akkadia.org/drepper/SHA-crypt.txt
num_bytes: bytes = 32
bytes_key: bytes = key.encode()
bytes_salt: bytes = salt.encode()
digest_b = hashlib_sha256(bytes_key + bytes_salt + bytes_key)
tmp = bytes_key + bytes_salt
for i in range(len(bytes_key), 0, -num_bytes):
tmp += digest_b if i > num_bytes else digest_b[:i]
i = len(bytes_key)
while i > 0:
tmp += digest_b if (i & 1) != 0 else bytes_key
i >>= 1
digest_a = hashlib_sha256(tmp)
tmp = b""
for i in range(len(bytes_key)):
tmp += bytes_key
digest_dp = hashlib_sha256(tmp)
byte_sequence_p = b""
for i in range(len(bytes_key), 0, -num_bytes):
byte_sequence_p += digest_dp if i > num_bytes else digest_dp[:i]
tmp = b""
til = 16 + digest_a[0]
for i in range(til):
tmp += bytes_salt
digest_ds = hashlib_sha256(tmp)
byte_sequence_s = b""
for i in range(len(bytes_salt), 0, -num_bytes):
byte_sequence_s += digest_ds if i > num_bytes else digest_ds[:i]
digest_c = digest_a
for i in range(loops):
tmp = byte_sequence_p if (i & 1) else digest_c
if i % 3:
tmp += byte_sequence_s
if i % 7:
tmp += byte_sequence_p
tmp += digest_c if (i & 1) else byte_sequence_p
digest_c = hashlib_sha256(tmp)
inc1, inc2, mod, end = (10, 21, 30, 0)
i = 0
tmp = ""
while True:
tmp += to64(
(digest_c[i] << 16) | (digest_c[(i + inc1) % mod] << 8) | digest_c[(i + inc1 * 2) % mod], 4
)
i = (i + inc2) % mod
if i == end:
break
tmp += to64((digest_c[31] << 8) | digest_c[30], 3)
return tmp
def mysql_sha256_password_hash_hex(password: str, salt: str) -> str:
"""Return a MySQL compatible caching_sha2_password hash in hex format."""
assert len(salt) == 20, "Salt must be 20 characters long."
count = 5
iteration = 1000 * count
digest = sha256_digest(password, salt, iteration)
return f"$A${count:>03}${salt}{digest}".encode().hex().upper()