RSA:签名/验证示例
下面实际演示 RSA 签名/验证算法。我们将使用 Python 的 pycryptodome 软件包生成 RSA 密钥。生成密钥后,通过简单的模幂运算(加密和解密消息哈希值)计算 RSA 数字签名并验证签名。
pip install pycryptodome
接下来,生成一个 1024 位 RSA 密钥对:
from Crypto.PublicKey import RSA
keyPair = RSA.generate(bits=1024)
print(f"Public key: (n={hex(keyPair.n)}, e={hex(keyPair.e)})")
print(f"Private key: (n={hex(keyPair.n)}, d={hex(keyPair.d)})")
运行上述代码示例:https://repl.it/@nakov/RSA-key-in-Python。
上述代码的输出可能如下(由于存在随机性,每次执行的结果都会不同):
Public key: (n=0xf51518d30754430e4b89f828fd4f1a8e8f44dd10e0635c0e93b7c01802729a37e1dfc8848d7fbbdf2599830268d544c1ecab4f2b19b6164a4ac29c8b1a4ec6930047397d0bb93aa77ed0c2f5d5c90ff3d458755b2367b46cc5c0d83f8f8673ec85b0575b9d1cea2c35a0b881a6d007d95c1cc94892bec61c2e9ed1599c1e605f, e=0x10001)
Private key: (n=0xf51518d30754430e4b89f828fd4f1a8e8f44dd10e0635c0e93b7c01802729a37e1dfc8848d7fbbdf2599830268d544c1ecab4f2b19b6164a4ac29c8b1a4ec6930047397d0bb93aa77ed0c2f5d5c90ff3d458755b2367b46cc5c0d83f8f8673ec85b0575b9d1cea2c35a0b881a6d007d95c1cc94892bec61c2e9ed1599c1e605f, d=0x165ecc9b4689fc6ceb9c3658977686f8083fc2e5ed75644bb8540766a9a2884d1d82edac9bb5d312353e63e4ee68b913f264589f98833459a7a547e0b2900a33e71023c4dedb42875b2dfdf412881199a990dfb77c097ce71b9c8b8811480f1637b85900137231ab47a7e0cbecc0b011c2c341b6de2b2e9c24d455ccd1fc0c21)
现在使用 RSA 私钥 {n, d} 签署消息。计算消息的哈希值,并对哈希值求以 n 为模、指数为 d 的幂(使用私钥加密哈希值)。这里使用 SHA-512 哈希,它可以容纳在当前 RSA 密钥长度(1024 位)内。在 Python 中,内置函数 pow(x, y, n) 可以执行模幂运算:
# RSA sign the message
msg = b'A message for signing'
from hashlib import sha512
hash = int.from_bytes(sha512(msg).digest(), byteorder='big')
signature = pow(hash, keyPair.d, keyPair.n)
print("Signature:", hex(signature))
运行上述代码示例:https://repl.it/@nakov/RSA-sign-in-Python。
得到的数字签名是 RSA 密钥范围 [0...n) 内的整数。对于上述私钥和消息,得到的签名如下:
Signature: 0x650c9f2e6701e3fe73d3054904a9a4bbdb96733f1c4c743ef573ad6ac14c5a3bf8a4731f6e6276faea5247303677fb8dbdf24ff78e53c25052cdca87eecfee85476bcb8a05cb9a1efef7cb87dd68223e117ce800ac46177172544757a487be32f5ab8fe0879fa8add78be465ea8f8d5acf977e9f1ae36d4d47816ea6ed41372b
该签名是一个 1024 位整数(128 字节,256 个十六进制数字)。这个签名大小与 RSA 密钥长度相对应。
现在来验证签名:使用公钥解密签名(对 signature 求以 n 为模、指数为 e 的幂),再将从签名得到的哈希值与原始已签名消息的哈希值进行比较:
# RSA verify signature
msg = b'A message for signing'
hash = int.from_bytes(sha512(msg).digest(), byteorder='big')
hashFromSignature = pow(signature, keyPair.e, keyPair.n)
print("Signature valid:", hash == hashFromSignature)
运行上述代码示例:https://repl.it/@nakov/RSA-sign-verify-in-Python。
由于签名有效,输出将显示 True:
Signature valid: True
现在尝试篡改消息并再次验证签名:
# RSA verify signature (tampered msg)
msg = b'A message for signing (tampered)'
hash = int.from_bytes(sha512(msg).digest(), byteorder='big')
hashFromSignature = pow(signature, keyPair.e, keyPair.n)
print("Signature valid (tampered):", hash == hashFromSignature)
运行上述代码示例:https://repl.it/@nakov/RSA-verify-tampered-message-in-Python。
此时签名将无效,上述代码的输出为:
Signature valid (tampered): False
请尽情尝试上述 RSA 签名/验证示例。可以修改代码,例如使用 4096 位密钥,或在签名验证步骤中尝试篡改公钥或签名。
RSA 签名标准 PKCS#1
上面演示了 RSA 签名的简单用法,但业界通常遵循密码学标准。RSA 签名采用最广泛的标准是 "PKCS#1",它有多个版本(1.5、2.0、2.1、2.2),最新版本见 RFC 8017。PKCS#1 标准定义了 RSA 签名算法(RSASP1)和 RSA 签名验证算法(RSAVP1),它们与上一节实现的算法几乎相同。
为演示 PKCS#1 RSA 数字签名,我们将使用以下基于 Python 库 pycryptodome 的代码;该库按照 PKCS#1 v1.5 规范实现了 RSA 签名/验证:
from Crypto.PublicKey import RSA
from Crypto.Signature.pkcs1_15 import PKCS115_SigScheme
from Crypto.Hash import SHA256
import binascii
# Generate 1024-bit RSA key pair (private + public key)
keyPair = RSA.generate(bits=1024)
pubKey = keyPair.publickey()
# Sign the message using the PKCS#1 v1.5 signature scheme (RSASP1)
msg = b'Message for RSA signing'
hash = SHA256.new(msg)
signer = PKCS115_SigScheme(keyPair)
signature = signer.sign(hash)
print("Signature:", binascii.hexlify(signature))
# Verify valid PKCS#1 v1.5 signature (RSAVP1)
msg = b'Message for RSA signing'
hash = SHA256.new(msg)
verifier = PKCS115_SigScheme(pubKey)
try:
verifier.verify(hash, signature)
print("Signature is valid.")
except:
print("Signature is invalid.")
# Verify invalid PKCS#1 v1.5 signature (RSAVP1)
msg = b'A tampered message'
hash = SHA256.new(msg)
verifier = PKCS115_SigScheme(pubKey)
try:
verifier.verify(hash, signature)
print("Signature is valid.")
except:
print("Signature is invalid.")
运行上述代码示例:https://repl.it/@nakov/PKCShash1-in-Python。
上述代码的输出表明,使用 1024 位 RSA 私钥进行 PKCS#1 RSA 签名会产生 1024 位数字签名,随后可用相应的公钥成功验证该签名。如果消息、签名或公钥遭到篡改,签名验证就会失败。上述示例的输出如下:
Signature: b'243b9ed6561ab3bddead98508af0ac34b4567b1358011ace24db71ce2bc7f1a2e942b6231aa84cb07bae85b668d7c7cd0bc40cdda6f8162de57f0ee842e589c58f94aa4f96d51355f8aa395d7db950ebb9d375fca3124b6222699a645e93287bc6f5eb5b750fc0b470588f949a887dff75ed42cf01d9642a5d497f609b8cd043'
Signature is valid.
Signature is invalid.
请注意,在实际应用中,RSA 密钥长度应至少为 3072 位,才能提供足够安全的签名。