1

I have 3 different scripts that should be basically identical logic wise, yet when run the RSA sig generated by python does not agree with the RSA sigs of the other two implementations. What am I doing differently in 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())

For reference my test-key.pem (it was generated specifically for this question and thus is a throw away key).

-----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-----

and here is the results I am getting:

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
tewFD3qIoo1aWzAMvqqBXr3AHhXlh1gqq9P/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==
3
  • 1
    I don't believe the first two are hashing the data whereas the python version clearly is. Therefore the first two do not contain a proper PKCS1 DigestInfo structure whereas the python one does. Commented May 18, 2023 at 16:41
  • @PresidentJamesK.Polk how would get the python to not hash the data? (I know its wrong but I am trying to get the python to talk to a ruby api ... i cant change the ruby) Commented May 18, 2023 at 16:55
  • 2
    PyCryptodome does not support this. But it can easily be implemented, see e.g. here, function customizedSign(). With this, the Ruby/Bash signature can be generated, s. online replit.com/@3hK8cL8H24hwiS7/…. Commented May 18, 2023 at 18:21

1 Answer 1

1

Shout out to @Topaco for pointing me to this:

from Crypto.Util import number
from Crypto.PublicKey import RSA
import base64

def customizedSign(key, msg):
    modBits = number.size(key.n)
    k = number.ceil_div(modBits, 8)
    ps = b'\xFF' * (k - len(msg) - 3)
    em = b'\x00\x01' + ps + b'\x00' + msg
    em_int = number.bytes_to_long(em)
    m_int = key._decrypt(em_int)
    signature = number.long_to_bytes(m_int, k)
    return signature

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"""

with open('./test-key.pem', 'rb') as key_file:
    private_key = RSA.import_key(key_file.read())
    
signature = customizedSign(private_key, string_to_sign.encode())
print(base64.b64encode(signature).decode())

gives the following sig:

python signme.py && ./signme.sh
jxQHbrJlRBw5cfbpdyWvNVhfWOZtV1t0wdYPBKqGuIil5tXtzmOaXr1l21CmP+SdSY9+yc0eeuC1ylIoFaDf3+W61+zImW9b6zS9GnssNqogPpC1ERhXfNkv5t2vRcg5Df3EQG5XfQb/X/7VMkoshLOGjul4If2X3jbNK4ZaIehi8A1Ie7XmLSllRBgOYxHDoztsGNlZz7mQQjNPCcO/nh7BPLG/2HcLZCsFOTBn8LD1EYyNtewFD3qIoo1aWzAMvqqBXr3AHhXlh1gqq9P/adQcWYf/uGZvGs6W2juRys9y39I4hitDk2EUW10lx3+6SZq4oqt4G5TEZP9XQYs1kw==

It should prob be noted:

This gives the same output as the openssl rsautl -sign command without hashing the input data. Please remember that this is not a recommended use of the cryptography library and could lead to security vulnerabilities in a real-world application.

The correct solution is most likely to fix the API that I am attempting to connect to (*cough* chef *cough*) but I will fully admit to the fact that I really don't know enough about this domain to be making a pull request there.

Hope this helps any people who might follow me down this rabbit hole in the future.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.