You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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填充逻辑不同:

  1. Ruby的private_encrypt和Bash的openssl rsautl -sign默认行为是:直接对string_to_sign的原始字节进行RSA私钥加密,使用PKCS#1 v1.5填充,但不做哈希值的ASN.1编码。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:41:33