Python生成的RSA签名与Ruby/Bash不一致问题排查
RSA签名结果不一致:Python与Ruby/Bash实现的差异分析
我编写了三个逻辑本质相同的签名脚本,但运行后Python生成的RSA签名与Ruby、Bash实现的签名结果不一致。以下是各语言实现代码、测试密钥及运行结果,需找出Python代码中的差异处理:
Ruby代码
#!/usr/bin/ruby # ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-linux] require 'openssl' require 'base64' string_to_sign = """Method:GET Hashed Path:rakSQTQa55Ls8KWcrShhane6uFY= X-Ops-Content-Hash:O8Fciq4+QSTdQJA18y6i/Wg178k= X-Ops-Timestamp:2023-05-18T15:26:59Z X-Ops-UserId:test-user""" puts "############################################################" puts "Ruby" puts "\nBase64 Encoding" puts Base64.encode64(string_to_sign) # Adds newline every 60 bytes? puts "\nsha1 Hash" puts ::Base64.encode64(OpenSSL::Digest::SHA1.digest(string_to_sign)).chomp puts "\nsign w/ RSA priv key" puts Base64.encode64(OpenSSL::PKey::RSA.new(File.read("test-key.pem")).private_encrypt(string_to_sign))
Bash代码
#!/bin/bash string_to_sign="Method:GET Hashed Path:rakSQTQa55Ls8KWcrShhane6uFY= X-Ops-Content-Hash:O8Fciq4+QSTdQJA18y6i/Wg178k= X-Ops-Timestamp:2023-05-18T15:26:59Z X-Ops-UserId:test-user" echo -e "\n############################################################" echo -e "Bash" echo -e "\nBase64 Encoding" echo -n "$string_to_sign" | base64 echo -e "\nSHA1 Hash" echo -n "$string_to_sign" | openssl dgst -binary -sha1 | base64 echo -e "\nSign w/ RSA priv key" echo -n "$string_to_sign" | openssl rsautl -sign -inkey test-key.pem | base64 echo -e ""
Python代码
#!/usr/bin/python from Crypto.PublicKey import RSA from Crypto.Hash import SHA1 from Crypto.Signature import pkcs1_15 import base64 string_to_sign = """Method:GET Hashed Path:rakSQTQa55Ls8KWcrShhane6uFY= X-Ops-Content-Hash:O8Fciq4+QSTdQJA18y6i/Wg178k= X-Ops-Timestamp:2023-05-18T15:26:59Z X-Ops-UserId:test-user""" print("############################################################") print("Python") print("\nBase64 Encoding") print(base64.encodebytes(string_to_sign.encode()).decode().rstrip()) print("\nSHA1 Hash") sha1_hash = SHA1.new(string_to_sign.encode()) print(base64.encodebytes(sha1_hash.digest()).decode().rstrip()) print("\nSign w/ RSA priv key") key = RSA.importKey(open("test-key.pem").read()) h = SHA1.new(string_to_sign.encode()) signature = pkcs1_15.new(key).sign(h) print(base64.encodebytes(signature).decode())
测试密钥test-key.pem
-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEApEWOeapXklrB1dV/SAm2cPLgZ2qwJfJ+POWr2FFa7m7+JdLy exDkxyqtbXw857/iiw+56g1/VbSxEEvauJincWGJPn6le47v/M+Cbojqv4ULw4Fh 2/4191EAjwdzJSlmXxDxbxO76qFDZnUyXstAUeyaJs549xz+H1u81tm2zU0a8cyO SeSnHxFfEe1kMosxkC3m0aHa3pA+sxpS8vqTBFUK87u20o2WpPuq/IgxIdnxd4tJ NNTsbKRMvw83PFpH6W5sDsWo1e8jDz92WXghOQeSqvm2uZEx5L4UAKKOwnOWNraF GMQRRnbflow6e/HjwZJoiRjFIEFkYlJJ1vUwTQIDAQABAoIBAH5WbGwP4QfTOw5d A2YA6kpV0NZYjB6zL/lf3dkhQKDtxhKK+ShC5uByZy00Bpdp0S6IKsDiHpNow2C4 JgAgj264x9fDiTvMw6+YXETskjY3ecOjpwKNsS2DI73cyebDv1LP8g8uizC5U9/h tJqJEO+w2yGLXCcZKiwt3r8Sc+/R4lGIv9UsHIo6fzcHSykhPfUnlMSo1pBQ9HYB zcF8ErGSS+UenfsSiFMxueU3iL++u1SDUl+ObVgh1FV1Ax0psse9gPp/vwgSWbKR jljiqmFeGtpH5aCTCjXTdjG83GHzZ9The/SIw0DyfH+obMrzsnUMG7tzVIGPMuL8 8+j5gxUCgYEA+twgS8L6o3Bgc8cvsrCDcoaVVvhaGNZahzGZjxCE+zt6943mHgZ0 3oFBYoOfLS2QjLW3VbMx6czJ1Ow+9gPWGRPN01uUaiCVP+pRKNN4ixlYgY4RFuTj 9NAeIMzMY/zeRDvFwRxO2IvKQwI3Nix+hupo2eVmA7NDHSWfuSi5ce8CgYEAp6M8 dndFWPkuMSL1jU1eVWqWi8D5UiJrlNdp+1DniSNr+C/oSD/+Ot72RDDhwKe+JpP4 iUiTEpmuc1ZUOJ2um9BO4DP347UvyHRGUssS1bAlKgIvFi5CAWRgvv0cAjdpRpTU VCe/iNE8nbTMQCj9TtQh4PaQF1f/eDarY5LlTYMCgYEA0eE/iANeTUWk/NjGmFrD 7xqYcYYhYyxb20ZtMlvg1o0CKYHn6HEAcHR17uUuVM8NZBxYgfQFq5Vxu5nYZ134 T0zZZJ73Qf92v13cfyrGbKJNAT+KHrxr2BQTUN/nlTQoBbB4mEOF1/jExWFiLgn1 5gzSopMh0bC2Uvl6c6CV3rMCgYEAgKUXQD41XJsUpKaUU9R8wQXj8+mqKyq47mcF MNScajRhpft1wQRC4AC8cgYlKIhRtx80yn2ER/Dh3CbyyOPQ3EfWT93xrLAdtDHu yZiHoq7jRkKYyefDxXe3ermYZecKBh0ueEpshN01LD1TxSTvhy/ps87jMtbX+PPT QL249GsCgYAi4344fB5tsrIpdWmmQ0AO/0LokBKm7oJEYm67lGMd1V6CRGmw4xl6 6Bmu15egsxKAmNtSAs7X7MI3gjS2wmx/3z2d3NLTz1chU4/7tzhPYTZsKAkXye9v CO7cEtAR5Va+ayzmxs6BQW14rnqDrX/0jri4nyF0yxEvDPnQsh3Hpg== -----END RSA PRIVATE KEY-----
运行结果
ruby signme.rb; bash signme.sh; python signme.py; ############################################################ Ruby Base64 Encoding TWV0aG9kOkdFVApIYXNoZWQgUGF0aDpyYWtTUVRRYTU1THM4S1djclNoaGFu ZTZ1Rlk9ClgtT3BzLUNvbnRlbnQtSGFzaDpPOEZjaXE0K1FTVGRRSkExOHk2 aS9XZzE3OGs9ClgtT3BzLVRpbWVzdGFtcDoyMDIzLTA1LTE4VDE1OjI2OjU5 WgpYLU9wcy1Vc2VySWQ6dGVzdC11c2Vy sha1 Hash lJMTFTq6/XPOwQUMKbGZdE3c5fE= sign w/ RSA priv key jxQHbrJlRBw5cfbpdyWvNVhfWOZtV1t0wdYPBKqGuIil5tXtzmOaXr1l21Cm P+SdSY9+yc0eeuC1ylIoFaDf3+W61+zImW9b6zS9GnssNqogPpC1ERhXfNkv 5t2vRcg5Df3EQG5XfQb/X/7VMkoshLOGjul4If2X3jbNK4ZaIehi8A1Ie7Xm LSllRBgOYxHDoztsGNlZz7mQQjNPCcO/nh7BPLG/2HcLZCsFOTBn8LD1EYyN ewFD3qIoo1aWzAMvqqBXr3AHhXlh1gqq9P/adQcWYf/uGZvGs6W2juRys9y 39I4hitDk2EUW10lx3+6SZq4oqt4G5TEZP9XQYs1kw== ############################################################ Bash Base64 Encoding TWV0aG9kOkdFVApIYXNoZWQgUGF0aDpyYWtTUVRRYTU1THM4S1djclNoaGFuZTZ1Rlk9ClgtT3Bz LUNvbnRlbnQtSGFzaDpPOEZjaXE0K1FTVGRRSkExOHk2aS9XZzE3OGs9ClgtT3BzLVRpbWVzdGFt cDoyMDIzLTA1LTE4VDE1OjI2OjU5WgpYLU9wcy1Vc2VySWQ6dGVzdC11c2Vy SHA1 Hash lJMTFTq6/XPOwQUMKbGZdE3c5fE= Sign w/ RSA priv key jxQHbrJlRBw5cfbpdyWvNVhfWOZtV1t0wdYPBKqGuIil5tXtzmOaXr1l21CmP+SdSY9+yc0eeuC1 ylIoFaDf3+W61+zImW9b6zS9GnssNqogPpC1ERhXfNkv5t2vRcg5Df3EQG5XfQb/X/7VMkoshLOG jul4If2X3jbNK4ZaIehi8A1Ie7XmLSllRBgOYxHDoztsGNlZz7mQQjNPCcO/nh7BPLG/2HcLZCsF OTBn8LD1EYyNtewFD3qIoo1aWzAMvqqBXr3AHhXlh1gqq9P/adQcWYf/uGZvGs6W2juRys9y39I4 hitDk2EUW10lx3+6SZq4oqt4G5TEZP9XQYs1kw== ############################################################ Python Base64 Encoding TWV0aG9kOkdFVApIYXNoZWQgUGF0aDpyYWtTUVRRYTU1THM4S1djclNoaGFuZTZ1Rlk9ClgtT3Bz LUNvbnRlbnQtSGFzaDpPOEZjaXE0K1FTVGRRSkExOHk2aS9XZzE3OGs9ClgtT3BzLVRpbWVzdGFt cDoyMDIzLTA1LTE4VDE1OjI2OjU5WgpYLU9wcy1Vc2VySWQ6dGVzdC11c2Vy SHA1 Hash lJMTFTq6/XPOwQUMKbGZdE3c5fE= Sign w/ RSA priv key VSFe3su8dejyKPdLSBQU9Z0MTL7/TGrt2pA97sMqfCy2rnWtuCA28KPYA77w6vGdlTrzpWjq6fpU ndJw3USwMcjzd5h/2XLYlpZjOP9/GYIbdayOZ3YtNl7b9GfFMcaKB+Lao5pPfuE8YMjvCDYGqpaY uD4qUz6+zEQ8W9GlTyGDNN+QRCYDtoimK/nU0vmyLTgtx1gf9mmub1ZmfBPdhjZpF2XHJljANit7 0i+bfnE44IMYlWGdd43yPc5fxwsywNgEnkt38lQ8hiWP6KDcfgz6SN/ax9qRgpEnTF21QxMt/yfr BLngJKdcW6TVv96uTXoU0K7VIkoFzq2PFu56ug==
差异原因分析
Python代码与Ruby/Bash的核心差异在于签名环节的PKCS#1填充逻辑不同:
- Ruby的
private_encrypt和Bash的openssl rsautl -sign默认行为是:直接对string_to_sign的原始字节进行RSA私钥加密,使用PKCS#1 v1.5填充,但不做哈希值的ASN.1编码。 - Python中
pkcs1_15.new(key).sign(h)实现的是标准PKCS#1 v1.5签名流程:先对原文做SHA1哈希,再将哈希值进行ASN.1编码,最后用RSA私钥加密编码后的内容。
两者本质是不同的操作:Ruby/Bash是"加密原文",Python是"签名哈希值",因此签名结果必然不一致。
修正后的Python代码
要让Python输出与Ruby/Bash一致,需模拟"直接加密原始字节"的行为,使用PKCS#1 v1.5加密模式而非签名模式:
#!/usr/bin/python from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 string_to_sign = """Method:GET Hashed Path:rakSQTQa55Ls8KWcrShhane6uFY= X-Ops-Content-Hash:O8Fciq4+QSTdQJA18y6i/Wg178k= X-Ops-Timestamp:2023-05-18T15:26:59Z X-Ops-UserId:test-user""" print("############################################################") print("Python(修正后)") print("\nBase64 Encoding") print(base64.encodebytes(string_to_sign.encode()).decode().rstrip()) print("\nSHA1 Hash") from Crypto.Hash import SHA1 sha1_hash = SHA1.new(string_to_sign.encode()) print(base64.encodebytes(sha1_hash.digest()).decode().rstrip()) print("\nSign w/ RSA priv key") key = RSA.importKey(open("test-key.pem").read()) cipher = PKCS1_v1_5.new(key) # 直接加密原始字符串字节,对齐Ruby/Bash的行为 signature = cipher.encrypt(string_to_sign.encode()) print(base64.encodebytes(signature).decode())
修正后的代码会生成与Ruby、Bash完全一致的签名结果。
内容的提问来源于stack exchange,提问作者CaffeineAddiction
相关产品推荐
相关产品推荐

