69 lines
2.1 KiB
Python
69 lines
2.1 KiB
Python
|
|
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]
|