Skip to content

Instantly share code, notes, and snippets.

@shatil
Created July 27, 2016 17:37
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save shatil/d1be21c8f3eaa16ea27983bc62056ed8 to your computer and use it in GitHub Desktop.
Save shatil/d1be21c8f3eaa16ea27983bc62056ed8 to your computer and use it in GitHub Desktop.
Python OpenSSL libraries' private key signing vs. OpenSSL's rsautl
#!/usr/bin/env python
from __future__ import print_function
import base64
import sys
PEM = ("""-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAgK1Q6Ydi8UUheJLvnTYJE65NOZtAtjDdDSxS+6b4x9EakjIylljSzs5uLEJn
kCe4f/vvQKrAKQiQdcjTM8Ea/wiS/Mj+xSGD3Sfk0BuRuYat9gpwCFY9mzyruesp6WAE5hXmsDNQ
I1sY3b2xJePnP4YWV+2oUaXn11vpOOTk7XVwnP7VfQu8PIIRHVJbZfV2C6rkCIhJrzBfa3jIe6F/
KY+dTwu5CZHiopuPsjbbSKfqhLf4f3zUBcdgUsdZCB21LoBNWvGpPZlQJ1VvXWHkwYNo1r5L35H0
7KarS8Y6D3gL29YKvtkY/UHvyuNDcwM02IUewdrHDaGiOEiUsarmyQIDAQABAoH/VPIVeBToPF5m
ujJF/IKve06VrHHRRnT7eLbKEVco5MAlyl1ZB+ebQusD0DQGKiQOqG6ogwg10uDUfO0WgBP6vmHq
pvYJOhnl8xli8/8/NDq0nLhHPTmxccmblCCqimXY1gufPrKhNLXutHOFfn31KvpZxbIea8gaRRFn
5Sc1+YueD4Bq6P/XRnQjAvAGmMTuNMbvHwX8K6JKNh6JNpPkJQ5+dXpd16ahAwYMsWaCLjTikoOb
NEhviVz22rJMw/KWCQYyIWHDaQd7QMhXdP2BUwbans7U1d+Igw6/dXf9BufM5d2IB02idTB/jFRE
6QtCb3kmBVBZdEwmPCUkqHRlAoGBALrmZGMxcY94rd922/H94hPmUlJPG1fxG+lnMAALdC7tpC73
dmlEYDdsEQW5Y9C9wSraIawxd0xtxOG90ZbZ/yMqpFZ9+5FVr3zpGrMxngDV92KLrJ2AElD/CeLB
NatNJoO+mqKuW6vX3XEZA2KqX3oTm0zhhS5nazZixmt1dAYbAoGBALBAROTLulzSh9SLFWiY3A1a
zoK7ATusesUFLeY6/K6SV4dQDT6fTDiZh2qXMir88UDCOBxCEjGGIw+sMh3iAUHBzHdi5xtAFesN
WyjfhddTQwtfRgO93P+/RgvssrhsItWqnTfAdNkO7+woSjC0eQ7fEdWsIYhWdqSW9LZowKTrAoGB
AIbUGettCc1Ec7pXlofWbTeJ2i1CoCkq6MXSCNCfcqtACEdRgfyitP6GWSlV+mnl2eo9/jioXrWm
InfvZbl7fhEye+dhbxADTlvAFeDblG5p7NMMi/P7JjuEIO+SDlOLjpNP92IQglVPnpIuR0DwQ3xf
lJM7xcYaGT/cteNjkdWtAoGBAKk5S+yRXyH4UcpUr/15pu57nYQPoSN2e3nneyZuxGWoxLl6tvzF
Xh2J62cAPH7h1ZFj6RPYrDc4nzlRD915PdOxC2wlXdfgNCs266vW0V7o5ppoo4S8KxCyycJxRTel
O90Cr2j0NDykBuAr3u/cl88bhrgtSRTqT4fAGL163lx5AoGAWiWgczL19PAIzv2WxnRmlwIexPBG
6A+KURg4/APfZOxa5rA91lKU2cQoGqgHErWhSdfZrdyNSqppAFtdB8Sgy1irkKGLBBiWOq4hfX2V
pCGks4zzBYQJ27/GnY2gbv6U6bBx2JW2zDvZZSU6ckv6eoJ2YEVMn63uzdpdwXlsTzE=
-----END RSA PRIVATE KEY-----""")
def via_cryptography(message):
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
private_key = serialization.load_pem_private_key(
PEM, password=None, backend=default_backend())
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
def via_openssl(message):
import subprocess
import tempfile
with open("my-pem", "wb+") as f:
f.write(PEM)
f.flush()
p = subprocess.Popen(
"openssl rsautl -sign -inkey " + f.name,
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate(input=message)
if stderr or p.returncode != 0:
print(stderr)
return stdout
def via_pycrypto(message):
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA
from Crypto.Signature import PKCS1_PSS
rsakey = RSA.importKey(PEM)
return rsakey.encrypt(message, None)[0]
def via_pyopenssl(message, digest="sha1"):
import OpenSSL
key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, PEM)
return OpenSSL.crypto.sign(key, message, digest)
def via_rsa(message):
import rsa
privkey = rsa.PrivateKey.load_pkcs1(PEM)
return rsa.sign(message, privkey, "SHA-1")
def via_ruby(message):
import subprocess
syntax = (
"require 'openssl'; key = OpenSSL::PKey::RSA.new('%s'); "
"print key.private_encrypt('asdf')"
) % PEM
return subprocess.check_output('ruby -e "%s"' % syntax, shell=True)
if __name__ == "__main__":
message = "asdf"
for each in (via_cryptography, via_openssl, via_pycrypto, via_pyopenssl,
via_rsa, via_ruby):
print("Using %s:" % each.__name__)
print(base64.b64encode(each(message)))
print()
@shatil
Copy link
Author

shatil commented Jul 27, 2016

You'll want to create a virtualenv and:

source /path/to/virtualenv/bin/activate
pip install cryptography pycrypto pyOpenSSL rsa

I'm using the Ruby and OpenSSL that shipped with OS X El Capitan:

$ ruby -v
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15]

$ openssl version
OpenSSL 0.9.8zh 14 Jan 2016

This is what I see:

Using via_cryptography:
GrdVUAd97PSGvouGLkG+DVba+czOFwz2zueGq7QzIe5O+5jV6zk+bjMBi6XZl6jx1Qy9m1e0yaJ+teYVPYZ7feylJKNV4M1Fp9CQrAv3SRPTowg8JfkCnTOND5zvhTiAJ2YDYcdss8MD7odUrZGmGmtvU0JdlY9+JBkB2HJ+menGgJhqMjDc3x7G5pXap1IInITvBC92kSz7nAGWvM6qakU3Mx8US+UNdKqhkRRZLT6WLZKN4rLzOFlXRkmGssEbjh/QPeSJW8Yog+DoONUn8eLV7fsCd1cDo3Qp1LwQLJs4qv7e5x3cxmq4DZom7gzbhULfAg9Jxs8CvdlRdI5Log==

Using via_openssl:
P7+x6Wzk29dioiiGKFjMn6EkjfOKuqDpPb0Fn1v64Hqvfa0CYj07YvM8/F2ho5Q88XuDa610C9pC2shEWBgABkgFAH+uA7JZBlWz7Tibw3PKZpUkCWtpVMevx/l7C99o1PHoOre5mBec+tDDL+swH4E7To+chSYbyU9C7l4Xwu4abGEuJst1QfE/Fh40CntRE1Pqh+yCaHjOlkODkneH9pHAd9EFyPefq6D9/+RWbVSkgX3fGDMHUdNecDWT6cs1rebjpYiE7X7yFn0MiHsfChdmOMo/FFqQPKxs4Bq0q7WKs1UBcu9pwEGpOFib04kOGHoNIYgFWv72oLqoIv7C6A==

Using via_pycrypto:
EpLb9OsRtKNIDMaHq3OV1cuGUkmSrjpwZsSxlAk119RdZkw53tmWc8eS1GdqpTHrNi/SZEJ7uKHe/5ch8ekSyJryeML33sSIDMrr0dmYMDN3I0ddt8u8mXIDlYjgkyzFDtRGNZSrxjsXomckgYyFp8rb5eDlME6Xms0cs3WlN8xRh8FpxQjlUUtYNlxx6e+BIdeUrJU1oyTEsPqw0rnrtMRP6l1vxBRgMB2yzDUHJjGqAonpKYnWRXi5kSkpDBmlw2F8Yox5MgPhzmCeiEYssw0rdYeMGiv+47OB675JAK+zOlzsk5wHtWI17ucO+WdEi0CoSlGrthozSVZn5yrg6A==

Using via_pyopenssl:
GrdVUAd97PSGvouGLkG+DVba+czOFwz2zueGq7QzIe5O+5jV6zk+bjMBi6XZl6jx1Qy9m1e0yaJ+teYVPYZ7feylJKNV4M1Fp9CQrAv3SRPTowg8JfkCnTOND5zvhTiAJ2YDYcdss8MD7odUrZGmGmtvU0JdlY9+JBkB2HJ+menGgJhqMjDc3x7G5pXap1IInITvBC92kSz7nAGWvM6qakU3Mx8US+UNdKqhkRRZLT6WLZKN4rLzOFlXRkmGssEbjh/QPeSJW8Yog+DoONUn8eLV7fsCd1cDo3Qp1LwQLJs4qv7e5x3cxmq4DZom7gzbhULfAg9Jxs8CvdlRdI5Log==

Using via_rsa:
GrdVUAd97PSGvouGLkG+DVba+czOFwz2zueGq7QzIe5O+5jV6zk+bjMBi6XZl6jx1Qy9m1e0yaJ+teYVPYZ7feylJKNV4M1Fp9CQrAv3SRPTowg8JfkCnTOND5zvhTiAJ2YDYcdss8MD7odUrZGmGmtvU0JdlY9+JBkB2HJ+menGgJhqMjDc3x7G5pXap1IInITvBC92kSz7nAGWvM6qakU3Mx8US+UNdKqhkRRZLT6WLZKN4rLzOFlXRkmGssEbjh/QPeSJW8Yog+DoONUn8eLV7fsCd1cDo3Qp1LwQLJs4qv7e5x3cxmq4DZom7gzbhULfAg9Jxs8CvdlRdI5Log==

Using via_ruby:
P7+x6Wzk29dioiiGKFjMn6EkjfOKuqDpPb0Fn1v64Hqvfa0CYj07YvM8/F2ho5Q88XuDa610C9pC2shEWBgABkgFAH+uA7JZBlWz7Tibw3PKZpUkCWtpVMevx/l7C99o1PHoOre5mBec+tDDL+swH4E7To+chSYbyU9C7l4Xwu4abGEuJst1QfE/Fh40CntRE1Pqh+yCaHjOlkODkneH9pHAd9EFyPefq6D9/+RWbVSkgX3fGDMHUdNecDWT6cs1rebjpYiE7X7yFn0MiHsfChdmOMo/FFqQPKxs4Bq0q7WKs1UBcu9pwEGpOFib04kOGHoNIYgFWv72oLqoIv7C6A==

@logic
Copy link

logic commented Jul 27, 2016

It looks like you're mixing up signing and encrypting pretty liberally through the different implementations?

The pycrypto example stood out for me, since it's the odd one out. Being more explicit about using PKCS1_v1_5 gives you results consistent with the other hashing versions:

def via_pycrypto(message):
    from Crypto.PublicKey import RSA
    from Crypto.Hash import SHA
    from Crypto.Signature import PKCS1_v1_5
    rsakey = RSA.importKey(PEM)
    h = SHA.new(message)
    signer = PKCS1_v1_5.new(rsakey)
    return signer.sign(h)

This leaves openssl rsautl and ruby as the odd ones out. But what if we change ruby's implementation to signing rather than encrypting?

#!/usr/bin/env ruby

require 'openssl'
require 'base64'

PEM = '<snip private key>'

key = OpenSSL::PKey::RSA.new(PEM);
result = key.sign(OpenSSL::Digest::SHA1.new, 'asdf')
print Base64.strict_encode64(result)

Now everyone agrees, because everyone is hashing the key. Well, everyone except openssl rsautl. ;) Now that you have consistent output between all implementations, you can probably converge on a solution, presumably by switching all of your examples to encrypt (like the original ruby version).

@shatil
Copy link
Author

shatil commented Jul 27, 2016

You're right about pycrypto. I tried using private key encryption rather than signing, and didn't revert the function back. The end result for me must authenticate against Chef, which I've managed using none of the Python libraries (my lack of any expertise is probably why): https://docs.chef.io/api_chef_server.html

Where I tried encrypt among the Python libraries, the resulting signature appears to be different each time around (e.g., rsa.encrypt(message, rsa.PrivateKey.load_pkcs1(PEM))).

I used Ruby's private_encrypt rather than sign (stolen from https://gist.github.com/gmcmillan/3184964) because it generates the same signature as openssl rsautl, which is what Chef's curl example uses: https://docs.chef.io/auth.html

@sybrenstuvel
Copy link

Where I tried encrypt among the Python libraries, the resulting signature appears to be different each time around (e.g., rsa.encrypt(message, rsa.PrivateKey.load_pkcs1(PEM))).

That's because it uses random padding :)

@ellepdesk
Copy link

To get consistent results change the openssl command to:
openssl dgst -sha1 -sign

However it seems you are looking for a way to encrypt using the python modules available

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment