In [ ]:
from Crypto.PublicKey import RSA
import base64

from github_settings import SSH_KEY_PASSWORD

my_public_key = RSA.importKey(
    open('/Users/raymondyee/.ssh/id_rsa.pub', 'r').read())

my_private_key =  RSA.importKey(open('/Users/raymondyee/.ssh/id_rsa','r').read(),  
              passphrase=SSH_KEY_PASSWORD)


message = "abcdefgh"

converting between ssh and pem

verify that my id_rsa.pem is actually equivalent to my id_rsa.pub


In [ ]:
print (my_public_key.exportKey(format='PEM'))

In [ ]:
print (open("/Users/raymondyee/.ssh/id_rsa.pem").read())

How to match the

Base64.encode64

from travis.rb/repository.rb at dcc9f20535c811068c4ff9788ae9bd026a116351 · travis-ci/travis.rb This docs: Module: Base64 (Ruby 2_2_0):

Returns the Base64-encoded version of bin. This method complies with RFC 2045. Line feeds are added to every 60 encoded characters.

pycrypto + my own id_rsa

padding

Class: OpenSSL::PKey::RSA (Ruby 2_2_4):

Encrypt string with the public key. padding defaults to PKCS1_PADDING. The encrypted string output can be decrypted using private_decrypt.

Also in the doc:

RSA is an asymmetric public key algorithm that has been formalized in RFC 3447.

Look for how to do so in Python.

possible values for padding (see source: Ruby MRI/test/openssl/test_pkey_rsa.rb):

  • OpenSSL::PKey::RSA::NO_PADDING
  • OpenSSL::PKey::RSA::PKCS1_PADDING

Looks like there is no standard library support in Ruby libs for

Also: don't know whether PKCS1_PADDING means:

pycrypto: Module PKCS1_v1_5

Crypto.Cipher.PKCS1_v1_5

pycrypto: Module PKCS1_OAEP

Crypto.Cipher.PKCS1_OAEP


In [ ]:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Cipher import PKCS1_OAEP

from Crypto.Hash import SHA
from Crypto import Random

import base64

def nopadding_encrypt(message, key):
    ciphertext = key.encrypt(message, 0)[0]
    return base64.b64encode(ciphertext)

def nopadding_decrypt(ciphertextb64, key):
    ciphertext = base64.b64decode(ciphertextb64)
    return key.decrypt(ciphertext)

def pkcs1v15_encrypt(message, key):

    h = SHA.new(message)

    cipher = PKCS1_v1_5.new(key)
    ciphertext = cipher.encrypt(message+h.digest())

    return base64.b64encode(ciphertext)

def pkcs1v15_decrypt (ciphertextb64, key):

    dsize = SHA.digest_size
    sentinel = Random.new().read(15+dsize)      # Let's assume that average data length is 15

    cipher = PKCS1_v1_5.new(key)

    ciphertext = base64.b64decode(ciphertextb64)
    message = cipher.decrypt(ciphertext, sentinel)

    digest = SHA.new(message[:-dsize]).digest()
    
    print ("len(message): {} sentinel: {} len(digest):{} dsize: {}".format(len(message), sentinel, 
                                                                           len(digest), dsize))
    if digest==message[-dsize:]:                # Note how we DO NOT look for the sentinel
        return message[:-dsize]
    else:
        raise Exception ('encryption was done incorrectly:{}'.format(message))
        

def pkcs1oaep_encrypt(message, key):
    cipher = PKCS1_OAEP.new(key)
    ciphertext = cipher.encrypt(message)
    
    return base64.b64encode(ciphertext)

def pkcs1oaep_decrypt(ciphertextb64, key):
    
    cipher = PKCS1_OAEP.new(key)
    ciphertext = base64.b64decode(ciphertextb64)
    
    return cipher.decrypt(ciphertext)

In [ ]:
enc_data = nopadding_encrypt(message, my_public_key)
print (enc_data, 
       nopadding_decrypt (enc_data, my_private_key)
      )

In [ ]:
enc_data = pkcs1v15_encrypt(message, my_public_key)

print (enc_data, pkcs1v15_decrypt(enc_data,
    my_private_key
))

In [ ]:
enc_data = pkcs1oaep_encrypt(message, my_public_key)

print (enc_data, 
pkcs1oaep_decrypt( enc_data,
    my_private_key
))

In [ ]:
### try decrypting output from Ruby with pkcs1v15

In [ ]:
ruby_output = """
Upw4QQcNptfvd6t00mVLZaLMd965DqiiNOYmRStkcr1eX/v3ETkTNIqkc8WG
ajrTYM20rYw3wfcMIjbCKXBSouTYqrJ4H4Uom3BbOI11Ykmf3Lf20QhB5r9K
YwDLol3bKSqbTTNXhPm2ALSjsX5tha4jkc4VooGAA6grMMcTmS9cGgCC0Gm5
oILJzzLb5WEEN2CiUk0JVvSvadYylDyuFou8iP6GVPpOrILDNHHZKb70irXb
E846PrDg8x83fL3+OoYAtfup3fR2ZH2qVXvs4JAQqRH9ECQtUkinJ4sukKYU
R/pULVPeWI/xgX0cQ3xxXg3V8m4IcqF1nTe8TkZ1RA==
""".strip()

assert base64.b64decode(ruby_output) == base64.b64decode(ruby_output.replace("\n",""))

pkcs1v15_decrypt(ruby_output, my_private_key)

In [ ]:
pkcs1oaep_decrypt(ruby_output, my_private_key)

In [ ]:
%%bash

echo -n 'abcdefgh' \
 | openssl rsautl \
     -encrypt \
     -pubin -inkey ~/.ssh/id_rsa.pem \
     > /Users/raymondyee/Downloads/cipher.txt

In [ ]:
pkcs1v15_decrypt(base64.b64encode(open("/Users/raymondyee/Downloads/cipher.txt", "rb").read()), my_private_key)

In [ ]:
%%bash

cat /Users/raymondyee/Downloads/test_message.txt \
  | base64 -D \
  | openssl rsautl \
      -decrypt \
      -inkey ~/.ssh/id_rsa

In [ ]:
pkcs1oaep_decrypt(base64.b64encode(open("/Users/raymondyee/Downloads/cipher.txt", "rb").read()), my_private_key)

In [ ]:
%%bash
# using openssl

echo -n 'abcdefgh' | openssl rsautl -encrypt -pubin -inkey /Users/raymondyee/.ssh/id_rsa.pem | base64

In [ ]:
openssl_output = """
yHkXsyDCj6eZJ7Ixf8vdXwOT7iCp9DHjVNcVmyMYR/fAsLzgLDeuNeS01hsMAVXtDiEJaMjxVaAqziRgeYB8Q36ZDGm9OUBkWahjQbvouXjS/YG5wLpW+PxnhYOIWS8La74dc50Kwqa5r6iqDJufBxJfD9g0eAngBTeIxIg1jq/r/ThNYcpb3qLVa4+h9sd4BocXxwvAwSjd0Wr1B4rogSUdxf11KU6K2tlQTjb/GHfOY7HjXaQH6jz8gRWJlNdDVaGSc+DCKiZfGrB62Ifuf94RBNjq0Y9T18PS+vVatcI2FJ8rSpV90cHYB3gTSLmBBwytW1SUt2rYR13Oi7aCUA==
""".strip()

pkcs1v15_decrypt(openssl_output, my_private_key)

python cryptography library

Background for the library:

The state of crypto in Python [LWN.net]

RSA — Cryptography 1.3.dev1 documentation


In [ ]:
import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes

digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(b"abc")
digest.update(b"123")
digest.finalize()

In [ ]:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa

private_key = rsa.generate_private_key(
     public_exponent=65537,
     key_size=2048,
     backend=default_backend()
 )

private_key

In [ ]:
from github_settings import SSH_KEY_PASSWORD
from cryptography.hazmat.primitives import serialization

with open("/Users/raymondyee/.ssh/id_rsa", "rb") as key_file:
     private_key = serialization.load_pem_private_key(
         key_file.read(),
         password=SSH_KEY_PASSWORD,
         backend=default_backend()
     )

In [ ]:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding


public_key = private_key.public_key()
pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
 )
pem.splitlines()

In [ ]:
message = b"abcdefgh"

#OAEP

ciphertext = public_key.encrypt(
     message,
     padding.OAEP(
         mgf=padding.MGF1(algorithm=hashes.SHA1()),
         algorithm=hashes.SHA1(),
         label=None
     )
)

ciphertext

In [ ]:
message = b"abcdefgh"

#PKCS1v15

ciphertext = public_key.encrypt(
     message,
     padding.PKCS1v15()
)

ciphertext

In [ ]:
plaintext = private_key.decrypt(
     ciphertext,
     padding.PKCS1v15()
)

plaintext == message

In [ ]:
private_key.decrypt(
    base64.b64decode(openssl_output),
    padding.PKCS1v15()
)

In [ ]:
private_key.decrypt(
    base64.b64decode(ruby_output),
    padding.PKCS1v15()
)

Conclusion: use the cryptography lib