I only have some very rudimentary theoretical knowledge about RSA.
While reading different sources about how to use it in practice, it seemed that PKCS#1 OAEP would be a good thing.
For a test implementation, I use Python with PyCrypto. E.g. this is an example using PKCS#1 OAEP.
Encrypting using the public key and then decrypting using the private key works fine. E.g. the public can send some data to person X with the private key.
From my basic understanding of how RSA works, I thought that I can just interchange the public/private key, i.e. I can use the public key for encrypting and the private key for decrypting. E.g. person X can encrypt some data with its own private key and the public can decrypt it using the public key. If the decryption works fine, this gives some sort of proof that the data is coming from person X.
PyCrypto complains when I try to decrypt using the public key.
From reading the PyCrypto source code, in the _RSAKey._decrypt
function (here), it seems that the key object itself knows if it is the private or public key and differs between them (to my surprise, again based on my very basic RSA understanding).
From there, it looks like I could hack the decrypt function so that it uses the public key. Or somewhat differently: I could just interchange the public exponent e
and the private exponent d
in the key objects.
But all this seems like it is not intended to be used/hacked this way. So I wanted to ask here about my misunderstandings.
Also, just out of curiosity, I generated some keys (RSA.generate(2048)
) and looked at n
, e
and d
. In all cases, n
and d
was very huge while e
was in all cases constant (65537) (I wouldn't have expected that).
I guess from all this that I really shouldn't just interchange e
and d
.
So I guess I should use some other method for the signature like PKCS1_PSS.
Some code for the encrypting/decrypting, if anyone is interested:
def randomString(l):
import random
return ''.join(chr(random.randint(0, 0xFF)) for i in range(l))
def genkeypair():
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
pubkey = key.publickey().exportKey("DER")
privkey = key.exportKey("DER")
return (pubkey,privkey)
def encrypt(v, rsapubkey):
from Crypto.PublicKey import RSA
rsakey = RSA.importKey(rsapubkey)
from Crypto.Cipher import PKCS1_OAEP
rsa = PKCS1_OAEP.new(rsakey)
import binstruct
from array import array
aeskey = randomString(32)
iv = randomString(16)
from Crypto.Cipher import AES
aes = AES.new(aeskey, AES.MODE_CBC, iv)
data = binstruct.varEncode(v)
data += array("B", (0,) * (-len(data) % 16))
out = binstruct.strEncode(rsa.encrypt(aeskey + iv))
out += array("B", aes.encrypt(data))
return out
def decrypt(stream, rsaprivkey):
from array import array
from StringIO import StringIO
if isinstance(stream, array): stream = stream.tostring()
if isinstance(stream, str): stream = StringIO(stream)
from Crypto.PublicKey import RSA
rsakey = RSA.importKey(rsaprivkey)
from Crypto.Cipher import PKCS1_OAEP
rsa = PKCS1_OAEP.new(rsakey)
import binstruct
aesdata = binstruct.strDecode(stream)
aesdata = rsa.decrypt(aesdata)
aeskey = aesdata[0:32]
iv = aesdata[32:]
from Crypto.Cipher import AES
aes = AES.new(aeskey, AES.MODE_CBC, iv)
class Stream:
buffer = []
def read1(self):
if len(self.buffer) == 0:
nextIn = stream.read(16)
self.buffer += list(aes.decrypt(nextIn))
return self.buffer.pop(0)
def read(self, n):
return "".join([self.read1() for i in range(n)])
v = binstruct.varDecode(Stream())
return v
(binstruct
is a small module which can encode/decode tree data structures - similar to JSON/BSON.)
That is where I thought I could just also use encrypt
with the private key and and decrypt
with the public key.
The final implementation with (hopefully) correct signing/authentication can be found here in binstruct.