Hello everyone.
I was wondering if it's possible to do a double RSA/PKCS#1 encryption with PyCrypto.
I have a server that has its own RSA key (generated with the openssl
command when said server is installed) and a client which can request the public part of the server's key. Also, that client can ask the server to generate another RSA key (or key-pair) for it. In that case, the server also keeps the private (or the "whole" RSA key) and sends the client the public part of its key.
I've been playing around with RSA/PKCS and AES encription. I have created a test Python file that works fine encrypting with only one RSA key. What it does is encrypting the data with the symmetric AES system (which uses a random key generated "on-the-fly"), cyphers the password used for AES using the RSA/PKCS#1 system and puts it in the result to be sent:
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Interesting links:
# 1> http://stackoverflow.com/a/9039039/289011
# 2> http://eli.thegreenplace.net/2010/06/25/aes-encryption-of-files-in-python-with-pycrypto/
from Crypto.PublicKey import RSA
import base64
import os
from Crypto.Cipher import AES
import Crypto.Util.number
import random
import struct
import cStringIO
from Crypto.Cipher import PKCS1_OAEP
def encrypt(string):
#Begin RSA Part to get a cypher that uses the server's public key
externKeyFilename="/home/borrajax/rsaKeys/server-key.pub"
externKeyFile = open(externKeyFilename, "r")
rsaKey= RSA.importKey(externKeyFile, passphrase="F00bAr")
pkcs1Encryptor=PKCS1_OAEP.new(rsaKey)
#End RSA Part
#Begin AES Part
iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
thisMessagePassword = os.urandom(16)
aesEncryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
chunksize=64*1024
#End AES Part
#Begin RSA Encription of the AES Key
rsaEncryptedPassword = pkcs1Encryptor.encrypt(thisMessagePassword)
retvalTmp = cStringIO.StringIO()
retvalTmp.write(struct.pack('<Q', len(string)))
retvalTmp.write(struct.pack('<Q', len(rsaEncryptedPassword)))
retvalTmp.write(rsaEncryptedPassword)
retvalTmp.write(iv)
while len(string) > 0:
chunk = string[0:chunksize]
string = string[chunksize:]
if len(chunk) % 16 != 0:
chunk += ' ' * (16 - len(chunk) % 16)
retvalTmp.write(aesEncryptor.encrypt(chunk))
return retvalTmp.getvalue()
def decrypt(string):
stringAsBuffer = cStringIO.StringIO(string)
retval = str()
chunksize=64*1024
externKeyFilename="/home/borrajax/rsaKeys/server-key.pem"
externKey = open(externKeyFilename, "r")
rsaKey = RSA.importKey(externKey, passphrase="F00bAr")
pkcs1Decryptor=PKCS1_OAEP.new(rsaKey)
origsize = struct.unpack('<Q', stringAsBuffer.read(struct.calcsize('Q')))[0]
rsaEncryptedPasswordLength = long(struct.unpack('<Q', stringAsBuffer.read(struct.calcsize('Q')))[0])
rsaEncryptedPassword = stringAsBuffer.read(rsaEncryptedPasswordLength)
thisMessagePassword = pkcs1Decryptor.decrypt(rsaEncryptedPassword)
iv = stringAsBuffer.read(16)
decryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
while True:
chunk = stringAsBuffer.read(chunksize)
if len(chunk) == 0:
break
retval += decryptor.decrypt(chunk)
return retval
if __name__ == "__main__":
encryptedThingy=encrypt(base64.b64encode("Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉Toñóooooañjfl凯兰;kañañfjaafafs凱蘭pingüiñoo你好to金玉"))
print "Decrypted thingy: %s" % base64.b64decode(decrypt(encryptedThingy))
As you can see, the AES password is encrypted with the server's RSA key. Now, I'd like to be extra paranoid, and encrypt that already encrypted password with the Client's public key, so the "encrypt" method would be something like:
def encrypt(string):
#Begin RSA Part to get a cypher that uses the server's public key
externServerKeyFilename="/home/borrajax/rsaKeys/server-key.pub"
externServerKeyFile = open(externServerKeyFilename, "r")
rsaServerKey= RSA.importKey(externServerKeyFile, passphrase="F00bAr")
pkcs1ServerEncryptor=PKCS1_OAEP.new(rsaServerKey)
#End RSA Part
#Begin RSA Part to get a cypher that uses the client's public key
externClientKeyFilename="/home/borrajax/rsaKeys/client-key.pub"
externClientKeyFile = open(externClientKeyFilename, "r")
rsaClientKey= RSA.importKey(externClientKeyFile, passphrase="F00bAr")
pkcs1ClientEncryptor=PKCS1_OAEP.new(rsaClientKey)
#End RSA Part
#Begin AES Part
iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16))
thisMessagePassword = os.urandom(16)
aesEncryptor = AES.new(thisMessagePassword, AES.MODE_CBC, iv)
chunksize=64*1024
#End AES Part
#Begin RSA Encription of the AES Key
rsaEncryptedPasswordWithServer = pkcs1ServerEncryptor.encrypt(thisMessagePassword)
rsaEncryptedPasswordWithServerAndClient = pkcs1ClientEncryptor.encrypt(rsaEncryptedPasswordWithServer) #Katacrasssshh here!!
retvalTmp = cStringIO.StringIO()
retvalTmp.write(struct.pack('<Q', len(string)))
retvalTmp.write(struct.pack('<Q', len(rsaEncryptedPasswordWithServerAndClient)))
#...Probably some yadda yadda here with key lengths and stuff so it would help re-build the keys in the server's side...
retvalTmp.write(rsaEncryptedPasswordWithServerAndClient)
retvalTmp.write(iv)
while len(string) > 0:
chunk = string[0:chunksize]
string = string[chunksize:]
if len(chunk) % 16 != 0:
chunk += ' ' * (16 - len(chunk) % 16)
retvalTmp.write(aesEncryptor.encrypt(chunk))
return retvalTmp.getvalue()
But when I try to re-encrypt the key, I get a ValueError("Plaintext too large")
exception. Which makes sense (at least makes sense to someone who barely knows anything about encryption) because PKCS adds a padding so when I encrypt the "thisMessagePassword
" with the server's public key, I get a 256 bytes string, which is too long for the second PKCS encryptor (I've been doing some "manual testing" and the limit seems to be 214 bytes... I mean... that is the last value that doesn't throw an exception).
I am aware that is probably a weird construct and that it would probably make more sense use the server's public key for encryption and signing with the client's key, but I'm just trying to play a bit with encryption things and try to understand how they work and why. That's why any hint will be appreciated.
Thank you in advance!