import hashlib import logging from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey logger = logging.getLogger(__name__) DEVICE_ID_HEX_LENGTH = 16 def generate_device_identity() -> tuple[str, str, str]: """生成 Ed25519 设备身份。 Returns: (device_id, public_key_pem, private_key_pem) """ private_key = Ed25519PrivateKey.generate() public_key = private_key.public_key() public_key_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("utf-8") private_key_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.BestAvailableEncryption(b"yuxi-device-key"), ).decode("utf-8") device_id = _derive_device_id_from_key(public_key) logger.info("Generated device identity: device_id=%s", device_id) return device_id, public_key_pem, private_key_pem def derive_device_id(public_key_pem: str) -> str: public_key = serialization.load_pem_public_key(public_key_pem.encode("utf-8")) return _derive_device_id_from_key(public_key) _PRIVATE_KEY_PASSWORD = b"yuxi-device-key" def sign_challenge(private_key_pem: str, challenge: bytes) -> bytes: private_key = serialization.load_pem_private_key( private_key_pem.encode("utf-8"), password=_PRIVATE_KEY_PASSWORD, ) return private_key.sign(challenge) def verify_signature(public_key_pem: str, challenge: bytes, signature: bytes) -> bool: try: public_key = serialization.load_pem_public_key(public_key_pem.encode("utf-8")) public_key.verify(signature, challenge) return True except Exception: return False def _derive_device_id_from_key(public_key) -> str: public_key_raw = public_key.public_bytes( encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw, ) return hashlib.sha256(public_key_raw).hexdigest()[:DEVICE_ID_HEX_LENGTH]