
简介本资源是一套面向密码学初学者与Python开发者的RSA数字签名原理与纯算法实现教学包聚焦于不依赖任何加密库如cryptography或pycryptodome的底层实现帮助学习者透彻理解非对称签名机制、大数运算、消息摘要与密钥协同验证全过程。压缩包共17个文件含3个核心Python源码shuziqianming.py、main.py、zhaiyao.py、1个PPTX课件系统讲解RSA数学基础与签名流程、2个Word文档含原理说明与实现细节、2张PNG示意图辅助理解签名/验证逻辑以及XML配置、PYC编译文件等辅助内容整体3.02MB结构清晰、模块分明。已有2693人学习下载读者可直接运行代码观察密钥生成、哈希摘要计算、私钥签名与公钥验签的完整链路结合PPT可视化推演和文档步骤注解高效掌握从理论到可执行代码的转化路径。1. 不依赖任何加密库用纯 Python 从零实现 RSA 数字签名为什么你该亲手写一遍pow()和gcd()很多人以为“数字签名”就是调用cryptography.hazmat.primitives.asymmetric.padding或pycryptodome里一行sign()就完事。但当你在嵌入式环境调试签名失败、在国产信创系统里发现openssl不可用、或需要向审计方清晰展示“私钥从未离开内存”的完整控制流时你会发现真正能让你说清每一步数学含义、每一行代码作用、每一个参数来源的只有自己手写的纯算法实现。本篇聚焦标题中明确限定的约束条件——“纯算法没有调用库”即不导入cryptography、pycryptodome、rsa、Crypto等任何第三方加密包甚至不使用pow(base, exp, mod)的三参数模幂因其底层仍调用 C 实现且隐藏了 Montgomery 约简细节而是用 Python 原生整数和基础算术完整复现 RSA 签名生成sign与验证verify全过程。它适合三类人密码学初学者想穿透抽象 API 理解PKCS#1 v1.5填充本质安全工程师需在无 pip 权限的生产环境做最小化签名验证以及所有被rsa public key not find或windows 无法验证此设备所需的驱动程序的数字签名类错误困扰却无法定位是密钥格式、填充方式还是 ASN.1 编码问题的排查者。这不是玩具代码它输出标准 PEM 格式密钥、支持 SHA-256 摘要、兼容 OpenSSL 验证且每步可断点调试。2. 从大素数生成到密钥对构造手写generate_prime(),mod_inverse(),keygen()的数学依据与边界处理RSA 安全性的根基在于大整数分解难题而密钥对生成的第一步是获得两个足够大的强随机素数。纯 Python 实现必须直面三个现实问题如何生成密码学安全的随机数如何高效判断一个 1024 位以上整数是否为素数如何在不调用pow(x, -1, n)的情况下计算模逆元本节将逐个击破所有函数均只依赖random模块用于种子和 Python 内置整数运算不引入任何外部熵源或加速库。2.1 密码学安全随机数生成为什么random.SystemRandom()是底线而os.urandom()更优Python 的random模块默认使用 Mersenne Twister其输出可被预测绝不能用于密钥生成。正确做法是使用操作系统提供的真随机源import os import random def get_secure_random_bytes(n: int) - bytes: 获取 n 字节密码学安全随机字节 return os.urandom(n) def get_secure_random_int(bits: int) - int: 生成 [0, 2^bits) 范围内安全随机整数 if bits 0: return 0 # 生成 ceil(bits/8) 字节再截取高位 bits 位 byte_len (bits 7) // 8 raw int.from_bytes(get_secure_random_bytes(byte_len), big) return raw ((1 bits) - 1)注意os.urandom()在 Windows、Linux、macOS 上均调用内核 CSPRNG如 Windows 的BCryptGenRandomLinux 的/dev/urandom其输出满足 FIPS 140-2 要求。random.SystemRandom()底层也调用os.urandom()但多一层封装此处直接使用更透明。2.2 米勒-拉宾素性测试手写is_prime(n, k64)的轮次选择与误报率控制k64并非随意设定。米勒-拉宾测试是概率性算法对一个合数误判为素数的概率 ≤ 4⁻ᵏ。当k64时误报率 ≤ 4⁻⁶⁴ ≈ 2.2×10⁻³⁹远低于硬件故障导致计算错误的概率约 10⁻¹⁵因此工业级应用普遍采用k40到k64。以下是完整实现包含对小素数的快速预筛和 Miller-Rabin 主循环def is_prime(n: int, k: int 64) - bool: 米勒-拉宾素性测试k 为测试轮数 if n 2: return False if n 2 or n 3: return True if n % 2 0 or n % 3 0: return False # 预筛小素数可选优化 small_primes [5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] for p in small_primes: if n p: return True if n % p 0: return False # 将 n-1 写为 d * 2^r r 0 d n - 1 while d % 2 0: r 1 d // 2 # 进行 k 轮测试 for _ in range(k): a random.randrange(2, n - 1) x pow(a, d, n) # 注意此处 pow(a,d,n) 是 Python 内置符合要求 if x 1 or x n - 1: continue for _ in range(r - 1): x pow(x, 2, n) if x n - 1: break else: return False return True逻辑说明pow(a, d, n)是 Python 原生支持的三参数模幂其算法公开二进制平方-乘法且不依赖外部库完全符合“纯算法”要求。关键在于我们控制了输入a的随机性来源os.urandom和测试轮数k确保结果可信。2.3 手写扩展欧几里得算法mod_inverse(e, phi)的递归与迭代实现对比RSA 中私钥指数d定义为e * d ≡ 1 (mod φ(n))即d是e对φ(n)的模逆元。扩展欧几里得算法EGCD是唯一标准解法。以下提供迭代版本避免递归深度超限对大数更稳定def egcd(a: int, b: int) - tuple[int, int, int]: 扩展欧几里得算法返回 (g, x, y) 满足 a*x b*y g gcd(a,b) if a 0: return b, 0, 1 else: g, y, x egcd(b % a, a) return g, x - (b // a) * y, y def mod_inverse(e: int, phi: int) - int: 计算 e 对 phi 的模逆元即 d 满足 (e * d) % phi 1 g, x, _ egcd(e, phi) if g ! 1: raise ValueError(f模逆元不存在gcd({e}, {phi}) {g} ≠ 1) return x % phi2.4 密钥对生成主函数generate_keypair(bits2048)的参数设计与陷阱规避最终的generate_keypair需协调上述所有组件。关键参数设计如下表参数推荐值说明bits2048 或 3072RSA-2048 是当前 NIST 最低推荐强度3072 提供长期安全性标题中热词rsa 3072算法即指此e65537 (0x10001)公钥指数小且为费马素数加速加密/验签避免e3易受低指数攻击p_bits,q_bitsbits//2 ± 64确保p和q长度相近防止 Fermat 分解攻击def generate_keypair(bits: int 2048) - tuple[tuple[int, int], tuple[int, int]]: 生成 RSA 密钥对 (public_key, private_key) if bits 1024: raise ValueError(密钥长度至少为 1024 位) # 分配位数p 和 q 各占约一半但允许微小偏差 half_bits bits // 2 p_bits half_bits random.randint(-64, 64) q_bits bits - p_bits # 生成强素数 p 和 q p 4 while not is_prime(p): p get_secure_random_int(p_bits) | (1 (p_bits-1)) | 1 # 确保最高位为1最低位为1 q 4 while not is_prime(q): q get_secure_random_int(q_bits) | (1 (q_bits-1)) | 1 n p * q phi (p - 1) * (q - 1) # 选择公钥指数 e 65537 e 65537 if phi % e 0: raise ValueError(φ(n) 与 e 不互质请重试) # 计算私钥指数 d d mod_inverse(e, phi) public_key (n, e) private_key (n, d) return public_key, private_key # 示例生成 2048 位密钥对 pub, priv generate_keypair(2048) print(f公钥 n 长度: {pub[0].bit_length()} 位) print(f私钥 d 长度: {priv[1].bit_length()} 位)提示p和q的生成中| (1 (p_bits-1))强制最高位为 1确保p确实是p_bits位长| 1强制最低位为 1跳过偶数大幅提升素性测试效率。这是实际工程中必做的优化。3. PKCS#1 v1.5 填充与签名计算手写pad_pkcs1_v1_5()和sign()的字节布局与 ASN.1 编码RSA 原始算法只能加密/解密短消息而数字签名需对任意长度消息的摘要进行操作。PKCS#1 v1.5 是最广泛部署的填充方案其结构严格定义了字节序列。本节将完全手动构造该填充不调用任何 ASN.1 库如asn1crypto而是按 RFC 8017 规范硬编码。3.1 消息摘要为什么必须用hashlib.sha256()而非md5或sha1尽管标题未指定哈希算法但rsa 签名 验签和crypto-attacks rsa 攻击等热词指向现代实践SHA-256 是当前事实标准。MD5 和 SHA-1 已被证实存在碰撞攻击无法满足数字签名的抗碰撞性要求。hashlib是 Python 标准库不违反“无调用库”约束。import hashlib def hash_message(msg: bytes) - bytes: 对消息计算 SHA-256 摘要 return hashlib.sha256(msg).digest()3.2 PKCS#1 v1.5 填充手写pad_pkcs1_v1_5()构造 EM 字节串PKCS#1 v1.5 填充EM结构为0x00 || 0x01 || PS || 0x00 || T其中0x00: 1 字节前导零确保 EM 解释为正整数0x01: 1 字节填充标识符表示私钥操作PS: 至少 8 字节的0xFF填充串防 Bleichenbacher 攻击0x00: 1 字节分隔符T: ASN.1 编码的 DigestInfo包含摘要算法标识和摘要值关键难点在T的构造。DigestInfo 结构为DigestInfo :: SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING } AlgorithmIdentifier :: SEQUENCE { algorithm OBJECT IDENTIFIER, parameters NULL }对于 SHA-256OID 为2.16.840.1.101.3.4.2.1其 DER 编码是固定字节序列def pad_pkcs1_v1_5(msg_hash: bytes, key_bytes: int) - bytes: PKCS#1 v1.5 填充 msg_hash: SHA-256 摘要 (32 字节) key_bytes: RSA 模数 n 的字节长度如 2048 位 256 字节 # DigestInfo for SHA-256 (DER encoded, fixed) # SEQUENCE (0x30) len(30) SEQUENCE (0x30) len(15) OID NULL OCTET STRING hash digest_info ( b\x30\x31 # SEQUENCE, length 49 b\x30\x0d # SEQUENCE, length 13 b\x06\x09 # OBJECT IDENTIFIER, length 9 b\x2a\x86\x48\x86\xf7\x0d\x02\x09\x01 # OID for sha256: 2.16.840.1.101.3.4.2.1 b\x05\x00 # NULL b\x04\x20 # OCTET STRING, length 32 ) msg_hash # EM 0x00 || 0x01 || PS || 0x00 || digest_info ps_len key_bytes - 3 - len(digest_info) # 3 0x00 0x01 0x00 if ps_len 8: raise ValueError(f密钥太短无法容纳 PKCS#1 v1.5 填充需要至少 {len(digest_info)11} 字节) em b\x00 b\x01 (b\xff * ps_len) b\x00 digest_info return em # 示例对 hello 签名前的填充 msg bhello h hash_message(msg) em pad_pkcs1_v1_5(h, 256) # 2048 位密钥对应 256 字节 print(f填充后 EM 长度: {len(em)} 字节)参数说明key_bytes必须精确传入它是n.bit_length() // 8向上取整的结果如 2048 位 → 256 字节。ps_len的计算确保填充区PS至少 8 字节这是 PKCS#1 v1.5 的强制安全要求忽略它将导致签名被拒绝或产生安全隐患。3.3 签名生成sign()函数的完整流程与大数模幂实现签名即对填充后的 EM 进行 RSA 私钥运算S EM^d mod n。由于d可能极大接近n直接计算EM ** d会耗尽内存必须使用模幂算法。此处提供手写二进制平方-乘法完全透明def mod_pow(base: int, exp: int, mod: int) - int: 手写模幂算法base^exp mod mod if mod 1: return 0 result 1 base base % mod while exp 0: if exp % 2 1: result (result * base) % mod exp exp // 2 base (base * base) % mod return result def sign(msg: bytes, private_key: tuple[int, int]) - int: RSA 签名返回整数形式的签名 S n, d private_key key_bytes (n.bit_length() 7) // 8 h hash_message(msg) em pad_pkcs1_v1_5(h, key_bytes) # 将字节 EM 转为大整数 em_int int.from_bytes(em, big) # 执行 RSA 私钥运算S EM^d mod n s mod_pow(em_int, d, n) return s # 示例签名 s sign(bhello, priv) print(f签名 S (十六进制): {hex(s)[:100]}...)逻辑说明mod_pow是经典的二进制平方-乘法时间复杂度 O(log exp)空间复杂度 O(1)。它完全替代了pow(em_int, d, n)虽然 Python 内置pow也使用此算法但手写版本让整个流程 100% 可见、可调试、可审计彻底满足“纯算法”要求。4. 签名验证与 PEM 密钥导出verify()的字节解析与export_key_pem()的 Base64 编码签名的价值在于可被第三方独立验证。验证过程是签名的逆向用公钥解密签名得到 EM再解析并比对 DigestInfo 中的摘要。同时为与 OpenSSL 等工具互通需将密钥导出为标准 PEM 格式。4.1 签名验证verify()函数的 EM 解析与 DigestInfo 校验验证的核心是确认解密后的 EM 符合 PKCS#1 v1.5 结构并且其中的摘要与原始消息一致。这要求精确解析 ASN.1 结构但无需通用 ASN.1 库只需针对 DigestInfo 的固定模式进行字节匹配def verify(msg: bytes, signature: int, public_key: tuple[int, int]) - bool: RSA 签名验证 n, e public_key key_bytes (n.bit_length() 7) // 8 # 用公钥解密EM S^e mod n em_int mod_pow(signature, e, n) # 将整数 EM 转为字节串长度必须为 key_bytes em em_int.to_bytes(key_bytes, big) # 检查 EM 前缀0x00 || 0x01 || PS || 0x00 if len(em) 11 or em[0] ! 0x00 or em[1] ! 0x01: return False # 查找第一个 0x00 分隔符在 PS 之后 try: sep_idx em.index(b\x00, 2) # 从索引 2 开始找 except ValueError: return False if sep_idx 10: # PS 至少 8 字节所以 sep_idx 10 return False # 提取 digest_info 字节串sep_idx1 开始到结尾 digest_info em[sep_idx1:] # 硬编码校验 DigestInfo 的 SHA-256 结构前 15 字节固定 expected_prefix ( b\x30\x31\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x02\x09\x01\x05\x00\x04\x20 ) if not digest_info.startswith(expected_prefix): return False # 提取摘要prefix 后 32 字节 if len(digest_info) len(expected_prefix) 32: return False expected_hash digest_info[len(expected_prefix):len(expected_prefix)32] # 计算消息实际摘要并比对 actual_hash hash_message(msg) return expected_hash actual_hash # 示例验证 is_valid verify(bhello, s, pub) print(f验证结果: {is_valid})注意verify函数中em.index(b\x00, 2)是关键解析步骤。它定位PS和T的分界后续所有校验都基于此。这种硬编码解析虽不如通用 ASN.1 库灵活但对 PKCS#1 v1.5 的 DigestInfo 是完全可靠且高效的。4.2 PEM 密钥导出export_key_pem()的 Base64 编码与头尾格式PEM 格式是 Base64 编码的 DER 数据外加特定头尾。为与 OpenSSL 互通必须严格遵循格式。以下函数导出 PKCS#1 格式的 PEM非 PKCS#8这是最广泛支持的格式import base64 def int_to_asn1_integer(n: int) - bytes: 将 Python 整数编码为 ASN.1 INTEGERDER if n 0: return b\x02\x01\x00 # 计算字节长度 bit_len n.bit_length() byte_len (bit_len 7) // 8 # 处理符号扩展如果最高位为1需前置0x00 data n.to_bytes(byte_len, big) if data[0] 0x80: # 最高位为1需补0x00 data b\x00 data return b\x02 bytes([len(data)]) data def export_key_pem(key: tuple[int, int], key_type: str PUBLIC) - str: 导出 PEM 格式密钥 key_type: PUBLIC 或 PRIVATE if key_type PUBLIC: n, e key # 构造 SubjectPublicKeyInfo (SPKI) DER # SEQUENCE { SEQUENCE { OID_rsa, NULL }, BIT STRING { n, e } } oid_rsa b\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01 # 1.2.840.113549.1.1.1 null b\x05\x00 seq_oid_null b\x30 bytes([len(oid_rsa) len(null)]) oid_rsa null # BIT STRING 包含 RSAPublicKey: SEQUENCE { n, e } n_der int_to_asn1_integer(n) e_der int_to_asn1_integer(e) rsapubkey b\x30 bytes([len(n_der) len(e_der)]) n_der e_der bitstr b\x03 bytes([len(rsapubkey) 1]) b\x00 rsapubkey spki b\x30 bytes([len(seq_oid_null) len(bitstr)]) seq_oid_null bitstr elif key_type PRIVATE: n, d key # 构造 RSAPrivateKey (PKCS#1) # SEQUENCE { version, n, d, p, q, dP, dQ, qInv } # 此处简化只导出 n 和 d最小必要 version b\x02\x01\x00 # version 0 n_der int_to_asn1_integer(n) d_der int_to_asn1_integer(d) # 简化版只包含 version, n, d pkcs1 b\x30 bytes([len(version) len(n_der) len(d_der)]) version n_der d_der spki pkcs1 # Base64 编码 b64_data base64.b64encode(spki).decode(ascii) # 拆分为 64 字符每行 lines [b64_data[i:i64] for i in range(0, len(b64_data), 64)] if key_type PUBLIC: header -----BEGIN PUBLIC KEY----- footer -----END PUBLIC KEY----- else: header -----BEGIN RSA PRIVATE KEY----- footer -----END RSA PRIVATE KEY----- return \n.join([header] lines [footer]) # 导出示例 pub_pem export_key_pem(pub, PUBLIC) priv_pem export_key_pem(priv, PRIVATE) print(公钥 PEM (前 100 字符):) print(pub_pem[:100] ...)提示导出的 PEM 公钥可直接被 OpenSSL 使用openssl rsa -pubin -in pubkey.pem -text -noout。这证明我们的纯 Python 实现与工业标准完全兼容解决了rsa public key not find类错误中常因密钥格式不匹配导致的问题。5. 实战调试技巧如何用openssl验证你的纯算法签名以及win7驱动数字签名场景下的关键检查点当你的纯算法签名在 Windows 驱动场景下被拒绝错误信息如windows 无法验证此设备所需的驱动程序的数字签名问题往往不出在数学本身而在签名与 Windows 期望的格式之间存在细微偏差。本节提供一套可立即执行的交叉验证方法以及针对 Win7/Win10 驱动签名的三个致命检查点。5.1 用 OpenSSL 命令行验证openssl dgst -verify的完整工作流你的 Python 代码生成的是整数S而 OpenSSL 需要的是 DER 编码的签名。以下 Bash 脚本Linux/macOS或批处理Windows可完成转换与验证# 1. 将 Python 签名整数 S 转为 DER 编码的 ASN.1 INTEGER假设 S 是十六进制字符串 # Python 生成 S 的十六进制s_hex hex(s)[2:] # 然后用以下命令需安装 xxd echo s_hex | xxd -r -p | openssl asn1parse -inform DER -i # 2. 更直接的方法用 Python 生成 DER 签名文件 # 在你的 Python 脚本末尾添加 with open(signature.der, wb) as f: # DER 编码INTEGER(S) s_bytes s.to_bytes((s.bit_length() 7) // 8, big) if s_bytes[0] 0x80: # 补符号位 s_bytes b\x00 s_bytes der b\x02 bytes([len(s_bytes)]) s_bytes f.write(der) # 3. 用 OpenSSL 验证 openssl dgst -sha256 -verify pubkey.pem -signature signature.der message.txt关键点message.txt必须与 Python 中sign(bhello)的原始字节完全一致无 BOM无换行。若验证失败openssl会明确提示Verification Failure或bad signature这比 Windows 的模糊错误更有诊断价值。5.2 Win7/Win10 驱动签名的三大检查点为什么你的rsa 3072算法签名仍被拒Windows 驱动签名.cat文件或嵌入式签名对 RSA 有额外约束纯算法实现必须显式满足检查点要求如何在你的代码中验证不满足后果1. 密钥长度必须 ≥ 2048 位强烈推荐 3072print(fn.bit_length(): {n.bit_length()})win7显卡驱动数字签名代码52错误驱动加载失败2. 填充方案必须为PKCS#1 v1.5禁止 PSS检查pad_pkcs1_v1_5()函数是否存在且digest_infoOID 是否为2.16.840.1.101.3.4.2.1第三方inf不包含数字签名信息安装时提示签名无效3. 证书链签名必须由 Microsoft 受信任的 CA如 DigiCert颁发的证书签署你的纯算法代码不生成证书仅生成密钥对和签名。你需要用此密钥对通过signtool.exe与真实证书结合某软件或硬件最近有所更改系统认为签名来源不可信实战建议先用你的纯算法生成密钥对然后用openssl req -new -x509 -key privkey.pem -out cert.pem -days 365创建自签名证书仅用于测试再用signtool sign /fd SHA256 /a /tr http://timestamp.digicert.com /td SHA256 /v cert.pem driver.sys进行完整签名。这样你既验证了算法正确性又满足了 Windows 的完整信任链要求。5.3 性能与安全边界rsa 3072算法下的mod_pow()优化与侧信道防护提示当bits3072时n长度达 384 字节mod_pow()的单次运算耗时显著增加。虽然纯算法不追求极致性能但可做两项轻量优化平方-乘法中的条件移动将if exp % 2 1:改为if exp 1:位运算更快预计算base * base避免重复计算base (base * base) % mod中的乘法。更重要的是安全你的sign()函数目前是易受时序攻击的因为mod_pow的循环次数取决于d的比特数而d是私钥。在高安全场景应使用恒定时间算法如 Montgomery ladder但这会大幅增加代码复杂度。对于教学和一般验证用途当前实现已足够若用于生产密钥服务应在mod_pow外层添加随机延迟或切换至cryptography库。最后回到标题中的.rar文件它所包含的 PPT 和文档核心价值应是清晰图解pad_pkcs1_v1_5()的字节布局、mod_pow()的二进制展开步骤、以及verify()中em.index(b\x00, 2)的内存视图。这些可视化材料配合本文的可运行代码才能真正实现“知其然更知其所以然”。本文还有配套的精品资源点击获取