- 盘面图 XSS 防护:装配层全面转义用户可控字段 + 模板严格 CSP(default-src 'none',无 script-src)+ 6 项注入向量回归测试 - 自更新签名信任锚:--apply 双源 manifest 交叉验证 → ed25519 验签(纯 Python 实现,缺 sig 即拒)→ 逐文件 SHA256,全 fail-closed - sync 工具同步后自动重建 manifest + 签名 + 独立验签,私钥缺失 fail-closed - 测试 170 项全绿
129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
"""纯 Python ed25519(RFC 8032)签名与验签,零依赖,仅用标准库。
|
||
|
||
self_update.py --apply 用其中的 verify() 校验 manifest.json 签名;
|
||
tools/sign_manifest.py 用 sign() 生成签名。验签性能不在关键路径上
|
||
(每次更新只验一次),实现取教科书式的扩展坐标点运算,求正确不求快。
|
||
|
||
密钥编码:私钥 = 32 字节种子(hex/base64 存储);公钥 = 32 字节 RFC 8032
|
||
标准压缩编码;签名 = 64 字节 R‖S。
|
||
"""
|
||
import hashlib
|
||
|
||
# 域参数:GF(2^255 - 19),曲线 -x² + y² = 1 + d·x²·y²
|
||
_P = 2 ** 255 - 19
|
||
_Q = 2 ** 252 + 27742317777372353535851937790883648493 # 群的阶
|
||
_D = (-121665 * pow(121666, _P - 2, _P)) % _P
|
||
_I = pow(2, (_P - 1) // 4, _P) # sqrt(-1)
|
||
|
||
|
||
def _xrecover(y: int) -> int:
|
||
xx = (y * y - 1) * pow(_D * y * y + 1, _P - 2, _P) % _P
|
||
x = pow(xx, (_P + 3) // 8, _P)
|
||
if (x * x - xx) % _P != 0:
|
||
x = x * _I % _P
|
||
if x & 1:
|
||
x = _P - x
|
||
return x
|
||
|
||
|
||
_BY = (4 * pow(5, _P - 2, _P)) % _P
|
||
_BX = _xrecover(_BY)
|
||
# 基点(扩展坐标 x, y, z, t,x=X/Z, y=Y/Z, t=XY/Z)
|
||
_B = (_BX, _BY, 1, _BX * _BY % _P)
|
||
_IDENTITY = (0, 1, 1, 0)
|
||
|
||
|
||
def _point_add(P, Q):
|
||
x1, y1, z1, t1 = P
|
||
x2, y2, z2, t2 = Q
|
||
a = (y1 - x1) * (y2 - x2) % _P
|
||
b = (y1 + x1) * (y2 + x2) % _P
|
||
c = 2 * _D * t1 * t2 % _P
|
||
d = 2 * z1 * z2 % _P
|
||
e, f, g, h = b - a, d - c, d + c, b + a
|
||
return (e * f % _P, g * h % _P, f * g % _P, e * h % _P)
|
||
|
||
|
||
def _scalarmult(P, e: int):
|
||
Q = _IDENTITY
|
||
while e > 0:
|
||
if e & 1:
|
||
Q = _point_add(Q, P)
|
||
P = _point_add(P, P)
|
||
e >>= 1
|
||
return Q
|
||
|
||
|
||
def _encode_point(P) -> bytes:
|
||
x, y, z, _t = P
|
||
zi = pow(z, _P - 2, _P)
|
||
x, y = x * zi % _P, y * zi % _P
|
||
return (y | ((x & 1) << 255)).to_bytes(32, "little")
|
||
|
||
|
||
def _decode_point(s: bytes):
|
||
if len(s) != 32:
|
||
raise ValueError("点编码长度须为 32 字节")
|
||
y = int.from_bytes(s, "little") & ((1 << 255) - 1)
|
||
if y >= _P:
|
||
raise ValueError("点编码 y 坐标越界")
|
||
x = _xrecover(y)
|
||
if (x & 1) != (s[31] >> 7):
|
||
x = _P - x
|
||
if (-x * x + y * y - 1 - _D * x * x * y * y) % _P != 0:
|
||
raise ValueError("点不在曲线上")
|
||
return (x, y, 1, x * y % _P)
|
||
|
||
|
||
def _hint(m: bytes) -> int:
|
||
return int.from_bytes(hashlib.sha512(m).digest(), "little")
|
||
|
||
|
||
def _secret_expand(seed: bytes):
|
||
if len(seed) != 32:
|
||
raise ValueError("私钥种子须为 32 字节")
|
||
h = hashlib.sha512(seed).digest()
|
||
a = int.from_bytes(h[:32], "little")
|
||
a &= (1 << 254) - 8
|
||
a |= 1 << 254
|
||
return a, h[32:]
|
||
|
||
|
||
def publickey(seed: bytes) -> bytes:
|
||
"""32 字节种子 → 32 字节公钥。"""
|
||
a, _prefix = _secret_expand(seed)
|
||
return _encode_point(_scalarmult(_B, a))
|
||
|
||
|
||
def sign(seed: bytes, msg: bytes) -> bytes:
|
||
"""RFC 8032 确定性签名:64 字节 R‖S。仅发版工具(tools/)使用。"""
|
||
a, prefix = _secret_expand(seed)
|
||
A = publickey(seed)
|
||
r = _hint(prefix + msg) % _Q
|
||
R = _encode_point(_scalarmult(_B, r))
|
||
S = (r + _hint(R + A + msg) * a) % _Q
|
||
return R + S.to_bytes(32, "little")
|
||
|
||
|
||
def verify(pk: bytes, msg: bytes, sig: bytes) -> bool:
|
||
"""验签:通过返回 True;任何不符(含畸形输入)返回 False,不抛异常。"""
|
||
if len(pk) != 32 or len(sig) != 64:
|
||
return False
|
||
R, s = sig[:32], int.from_bytes(sig[32:], "little")
|
||
if s >= _Q:
|
||
return False
|
||
try:
|
||
A = _decode_point(pk)
|
||
Rpt = _decode_point(R)
|
||
except ValueError:
|
||
return False
|
||
h = _hint(R + pk + msg) % _Q
|
||
# [S]B == R + [h]A
|
||
return _encode_point(_scalarmult(_B, s)) == _encode_point(
|
||
_point_add(Rpt, _scalarmult(A, h)))
|
||
|
||
|
||
def fingerprint(pk: bytes) -> str:
|
||
"""公钥指纹:SHA256(公钥) 十六进制,用于注释与审计对照。"""
|
||
return hashlib.sha256(pk).hexdigest()
|