83 lines
3.0 KiB
Python
83 lines
3.0 KiB
Python
"""魂籍(portable_identity_record):可验证、版本化的身份与状态包。
|
|
|
|
完整性模型:
|
|
- payload 规范化序列化(排序键、紧凑分隔符)后取 SHA-256 摘要;
|
|
- 签名 = HMAC-SHA256(密钥, "{character_id}|{version}|{digest}"),绑定人物与版本;
|
|
- 校验:重算摘要与签名比对。篡改 payload 破坏摘要;把记录复制到别的人物/版本,
|
|
签名不匹配——文件相同或人物 ID 相同不足以冒充原主体(data-model.md §1)。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
|
|
from app.core.config import settings
|
|
from app.models.jingsheng import IdentityRecord
|
|
|
|
|
|
def _canonical(payload: dict) -> str:
|
|
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
|
|
|
|
|
def _digest(payload: dict) -> str:
|
|
return hashlib.sha256(_canonical(payload).encode("utf-8")).hexdigest()
|
|
|
|
|
|
def _signature(character_id: str, version: int, digest: str) -> str:
|
|
secret = settings.identity_record_secret
|
|
message = f"{character_id}|{version}|{digest}".encode("utf-8")
|
|
return hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
|
|
|
|
|
|
def build_identity_record(
|
|
*,
|
|
character_id: str,
|
|
version: int,
|
|
identity: dict,
|
|
runtime_state: dict,
|
|
data_state: dict | None = None,
|
|
) -> IdentityRecord:
|
|
"""构建一条魂籍版本记录(调用方负责落库)。"""
|
|
payload = {
|
|
"spec": "hunjing.portable_identity_record.v1",
|
|
"character_id": character_id,
|
|
"version": version,
|
|
"identity": identity,
|
|
"data_state": data_state or {"note": "数据状态由各域自报(M1 占位)"},
|
|
"runtime_state": runtime_state,
|
|
}
|
|
digest = _digest(payload)
|
|
return IdentityRecord(
|
|
character_id=character_id,
|
|
version=version,
|
|
payload_json=json.dumps(payload, ensure_ascii=False),
|
|
digest=digest,
|
|
signature=_signature(character_id, version, digest),
|
|
)
|
|
|
|
|
|
class IdentityVerificationError(Exception):
|
|
"""校验失败:摘要或签名不匹配。"""
|
|
|
|
def __init__(self, reason: str) -> None:
|
|
super().__init__(reason)
|
|
self.reason = reason
|
|
|
|
|
|
def verify_identity_record(record: IdentityRecord) -> dict:
|
|
"""校验魂籍记录,返回规范化 payload;失败抛 IdentityVerificationError。"""
|
|
try:
|
|
payload = json.loads(record.payload_json)
|
|
except json.JSONDecodeError as exc:
|
|
raise IdentityVerificationError("payload 无法解析") from exc
|
|
if _digest(payload) != record.digest:
|
|
raise IdentityVerificationError("摘要不匹配(payload 已被篡改)")
|
|
expected = _signature(record.character_id, record.version, record.digest)
|
|
if not hmac.compare_digest(expected, record.signature):
|
|
raise IdentityVerificationError("签名不匹配(非本平台签发或被移植冒用)")
|
|
if payload.get("character_id") != record.character_id or payload.get("version") != record.version:
|
|
raise IdentityVerificationError("payload 声明的身份与记录不符")
|
|
return payload
|