当前位置:首页>python>python调用java国密签名的服务

python调用java国密签名的服务

  • 2026-10-11 07:31:50
python调用java国密签名的服务

文章目录

  • 背景
  • 网络接口给出的文档中,有java示例代码
  • python代码
  • 来时路

背景

使用python 调用 一个java开发的网络接口, 需要签名, python 的依赖对签名的计算算法与java有些差别,导致频繁的验签不通过, 在此记录下解决验签问题

网络接口给出的文档中,有java示例代码

// 公钥 public_keyaccess_key = ""// 私钥 private_key, secret_key = "MIGTAgE.........xxxxxxxx"String result = sj.toString();//生成签名 SM2 sm2 = SmUtil.sm2(secret_key, null);String signKey2 = sm2.signHex(HexUtil.encodeHexStr(result));

python代码

from gmssl import sm2import base64from asn1crypto.keys import PrivateKeyInfo, PublicKeyInfodef _decode_key_material(key: str) -> bytes:    """    解码密钥材料字符串为字节序列。    自动检测输入是 Base64 编码还是十六进制编码,并进行相应解码。    对于十六进制字符串,支持可选的 '0x' 前缀。    Args:        key (str): 密钥材料字符串,可以是 Base64 编码或十六进制编码。    Returns:        bytes: 解码后的原始字节数据。    Raises:        ValueError: 如果十六进制字符串长度不是偶数。    """    key = key.strip()    if _looks_like_base64(key):        return base64.b64decode(key)    # 处理十六进制格式:去除可选的 '0x' 前缀并验证长度    raw = key.lower()    if raw.startswith("0x"):        raw = raw[2:]    if len(raw) % 2:        raise ValueError("十六进制密钥长度必须为偶数")    return bytes.fromhex(raw)def _looks_like_base64(s: str) -> bool:    """    判断字符串是否看起来像 Base64 编码。    通过检查常见的前缀特征、特殊字符 ('+', '/') 或填充符 ('=') 来启发式判断。    Args:        s (str): 待检查的字符串。    Returns:        bool: 如果字符串疑似 Base64 编码则返回 True,否则返回 False。    """    return (        s.startswith("MFkw")        or s.startswith("MIG")        or "+" in s        or "/" in s        or (len(s) % 4 == 0 and s.endswith("="))    )def parse_private_key_hex(private_key: str) -> str:    """    解析私钥并返回标准化的 64 位十六进制 d 值。    支持多种输入格式:    1. 裸 d 值:64 位十六进制字符串(可选 '0x' 前缀)。    2. PKCS#8 格式:Base64 编码或 DER 十六进制编码的结构化私钥。    Args:        private_key (str): 私钥字符串,支持 PKCS#8 Base64/DER 或裸 d 值十六进制。    Returns:        str: 64 位小写十六进制字符串,表示私钥的 d 值(不含 '0x' 前缀)。    Raises:        ValueError: 如果输入格式无法识别或解析失败。    """    key = private_key.strip()    raw = key.lower()    if raw.startswith("0x"):        raw = raw[2:]    # 情况1:如果是标准的 64 位十六进制 d 值,直接返回    if len(raw) == 64 and all(c in "0123456789abcdef" for c in raw):        return raw    # 情况2:尝试作为结构化密钥(PKCS#8)解析    der = _decode_key_material(key)    try:        pk = PrivateKeyInfo.load(der)        d = pk["private_key"].parsed["private_key"].native        return format(d, "x").zfill(64)    except Exception as exc:        raise ValueError(            "无法解析 SM2 私钥,请使用 PKCS#8 Base64 或 64 位十六进制 d 值"        ) from excdef parse_public_key_hex(public_key: str) -> str:    """    从公钥字符串中解析出适用于 gmssl 的坐标数据(x||y)。    返回 128 位十六进制字符串,不包含 '04' 前缀。    支持以下格式:    1. X509 SubjectPublicKeyInfo:Base64 编码。    2. 未压缩点十六进制:'04'||X||Y (130字符) 或 X||Y (128字符)。    Args:        public_key (str): 公钥字符串,支持 Base64 编码的 SPKI 或十六进制编码的点坐标。    Returns:        str: 128 位小写十六进制字符串,表示公钥的 x 和 y 坐标拼接结果。    Raises:        ValueError: 如果公钥格式不支持或解析失败。    """    key = public_key.strip()    raw = key.lower()    if raw.startswith("0x"):        raw = raw[2:]    # 情况1:Base64 编码的公钥(通常是 SPKI 结构)    if _looks_like_base64(key):        der = base64.b64decode(key)        # 尝试直接从 DER 字节末尾提取未压缩点数据 (优化路径)        if len(der) >= 65 and der[-65] == 0x04:            return der[-64:].hex()        # 使用 asn1crypto 库解析 SPKI 结构获取公钥点        point = PublicKeyInfo.load(der)["public_key"].native        if len(point) == 65 and point[0] == 0x04:            return point[1:].hex()        raise ValueError("无法从 Base64 公钥中解析 SM2 坐标")    # 情况2:十六进制编码的公钥点    # 如果包含 '04' 前缀且总长度为 130 (04 + 64 + 64),则去除前缀    if raw.startswith("04") and len(raw) == 130:        raw = raw[2:]    # 验证最终长度是否为 128 (64 + 64)    if len(raw) != 128:        raise ValueError(f"不支持的公钥格式,期望 128 位十六进制,实际长度 {len(raw)}")    return rawif __name__ == "__main__":		sj = "this a test word"		secret_key= "MFkwEwYHKoZIzj0C.......................................000O000AAp=="		access_key = "MIGTAgEAMBMGByqG.......................2k"# 这里有个非常重要的步骤, 就是观察AK, SK, 看是什么编码的. 我的KEY, 都是base64编码的, 所以需要先base64.decode(). 		priv_hex = parse_private_key_hex(secret_key)  # 解析私钥		pub_hex = parse_public_key_hex(access_key)  # 解析公钥		crypt = sm2.CryptSM2(private_key=priv_hex, public_key=pub_hex, asn1=True)		signature = crypt.sign_with_sm3(sj.encode("utf-8"))# 6. 验签,非必须的,可以移除。		crypt = sm2.CryptSM2(private_key="", public_key=pub_hex, asn1=True)		verify_result = bool(crypt.verify_with_sm3(signature, sj.encode("utf-8")))print("验签结果:", verify_result)

来时路

  1. 使用 java 编写了生成签名和验签的工具, python 可以使用命令行调用这个java工具.  这个方式也跑通过. 这是个保底策略.

最新文章

随机文章