mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
64 lines
2.3 KiB
Python
64 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import secrets
|
|
import uuid
|
|
|
|
import bcrypt
|
|
from gmssl import func, sm2 # type: ignore[import-untyped]
|
|
|
|
|
|
def generate_database_token(value: str | None = None) -> str:
|
|
source = value if value is not None else str(uuid.uuid4())
|
|
return hashlib.md5(source.encode("utf-8"), usedforsecurity=False).hexdigest()
|
|
|
|
|
|
def bcrypt_hash(password: str, rounds: int = 10) -> str:
|
|
encoded = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt(rounds=rounds))
|
|
# The bundled Java BCryptPasswordEncoder only accepts $2a$ hashes.
|
|
return encoded.decode("ascii").replace("$2b$", "$2a$", 1)
|
|
|
|
|
|
def bcrypt_matches(password: str, encoded: str | None) -> bool:
|
|
if not encoded or not encoded.startswith(("$2a$", "$2$")):
|
|
return False
|
|
normalized = encoded.replace("$2$", "$2a$", 1)
|
|
try:
|
|
return bcrypt.checkpw(password.encode("utf-8"), normalized.encode("ascii"))
|
|
except (ValueError, UnicodeEncodeError):
|
|
return False
|
|
|
|
|
|
def sm2_generate_keypair() -> tuple[str, str]:
|
|
private_key = func.random_hex(64)
|
|
helper = sm2.CryptSM2(private_key=private_key, public_key="", mode=1)
|
|
public_point = str(helper._kg(int(private_key, 16), sm2.default_ecc_table["g"])) # noqa: SLF001
|
|
public_key = "04" + public_point
|
|
return public_key, private_key
|
|
|
|
|
|
def sm2_encrypt_c1c3c2(public_key: str, plaintext: str) -> str:
|
|
helper = sm2.CryptSM2(private_key="", public_key=public_key, mode=1)
|
|
encrypted = helper.encrypt(plaintext.encode("utf-8"))
|
|
if encrypted is None:
|
|
raise ValueError("SM2 KDF returned an all-zero key")
|
|
# BouncyCastle's SM2Engine emits the uncompressed-point marker.
|
|
return "04" + bytes(encrypted).hex()
|
|
|
|
|
|
def sm2_decrypt_c1c3c2(private_key: str, ciphertext: str) -> str:
|
|
normalized = ciphertext.strip().lower()
|
|
if normalized.startswith("04"):
|
|
normalized = normalized[2:]
|
|
if len(normalized) < 128 + 64 or len(normalized) % 2:
|
|
raise ValueError("invalid SM2 C1C3C2 ciphertext")
|
|
helper = sm2.CryptSM2(private_key=private_key, public_key="", mode=1)
|
|
decrypted = helper.decrypt(bytes.fromhex(normalized))
|
|
if decrypted is None:
|
|
raise ValueError("SM2 decryption failed")
|
|
return bytes(decrypted).decode("utf-8")
|
|
|
|
|
|
def random_hex(length: int) -> str:
|
|
return secrets.token_hex((length + 1) // 2)[:length]
|