ForcePilot/backend/package/yuxi/channel/gateway/device_identity.py

69 lines
2.1 KiB
Python
Raw Normal View History

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]