Python Crypto, RSA Public/Private key, with large file

22,221

Solution 1

RSA is quite slow, so it's not really suitable for encrypting / decrypting large blocks of data. It's normally used to encrypt / decrypt the keys of a faster symmetrical cypher, eg AES.

At a glance, your encrypted data looks a bit small to correspond to your input data, but I'm not that familiar with Crypto (I only installed it myself a few days ago), so I can't explain what it's done to your data.

But this code works for me:

#!/usr/bin/env python

from Crypto.PublicKey import RSA
from Crypto import Random

src_data = 'To be, or not to be - that is the question.'
print `src_data`

random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
print 'Key generated'

pub_key = key.publickey()
print 'Public key', pub_key

enc_data = pub_key.encrypt(src_data, 32)[0]
print `enc_data`

dec_data = key.decrypt(enc_data)
print `dec_data`

typical output

'To be, or not to be - that is the question.'
Key generated
Public key <_RSAobj @0xb7114dcc n(1024),e>
',\x17\xb1\x8a\x98\xb0-z\x8c\xb8r\x17\xa2\xfe[\x10I\x97\x93\x9d[\x93\x19&\\\x16V\xc2\xa3\x99\x80\xa5\x08\xafT\xb5iA|\x89\xeeJ\x90%\xceXv\x9f\x9f\xcb\\P"i\x00D\xd4\x16\xee\xa9\xe49\x18[\xa5\x0f\xd3\xfb\x91\xd5\x98\x1bP\xbf\xa4\xa5Dz\x8b7\x13\x9dqk+\xf7A\xd3\x12\x1c\x06\xcep\xf2\xba\xc6\xee\xf8\xa2\xb4\x04v\xfb\xb7>\xb3U\x17\xban\xf7\xc0oM+Tq\xce\xe3D\x83\xb9\xa4\x90\xe6c,\x18'
'To be, or not to be - that is the question.'

FWIW, here's a slightly modified version of the above which runs on both Python 2 and Python 3, although there will be minor differences in the output of the two versions.

In Python 3 we cannot pass strings to the encryption or decryption functions, we must pass bytes. Also, Python 3 doesn't support the backticks syntax that gets the repr of an object in Python 2.

This code call the string .encode() and bytes .decode() methods to perform the conversions. We could specify an encoding codec, eg

src_data.encode('utf-8')

but that's not necessary, since UTF-8 is the default codec.

from __future__ import print_function

from Crypto.PublicKey import RSA
from Crypto import Random

src_data = 'To be, or not to be - that is the question.'
print(repr(src_data))

random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
pub_key = key.publickey()
print('Key generated')

print(key.exportKey().decode())
print(pub_key.exportKey().decode())

enc_data = pub_key.encrypt(src_data.encode(), 32)[0]
print('Encoded\n', repr(enc_data))

dec_data = key.decrypt(enc_data).decode()
print('Decoded\n', repr(dec_data))    

Typical Python 3 output

'To be, or not to be - that is the question.'
Key generated
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDL/TzI4yHmlcC8qP3xWNieujmXR7CnEaZJrDH1Hyr/tGNa0aEE
jDIz+RlMntBbhOuiQMkMtCSB5X28h7HetiD4XkWTXmlIiKZQLZ074cO5mxF+HhF7
WIG30VONpX+Q4t/beqtaqbzyeIWvDdcCjUwOSQLrUKU5PX9LFzX+FnN1UwIDAQAB
AoGASRVZib+Wjb5pZy5EjQt/0J53s7ODnte78/k1jNS12xcN4aPpRG/WLLi6T7E2
hROCOIdtgJep3MAT5E/ZciledflaDwwmLo6+NsrhMppsNhpIHsvxWxmwxnH+bC2H
lpyeUmxku4xzqwYW4kuF3iaR45K2eUpXQyWTE9+pgvepgoECQQDT6Waiavstvs4W
btW2P4J+7O//NmTcvnyLTnhFTXklxTxnSun54HYOce8+TttsXWESTbzf91saN5SW
0vPyKK25AkEA9m3gbwFppiA65464jnDCP1AnrR15n3tbsLpaadYdRm07b+4BB0R2
M820cgber14JiGndOfv1uPl1Ooy0IH4hawJBAJKRC/uqIrAxGDlLz2SN6KQBHL1X
0csbtOhlDaphOzl0gaKvncTGCuFSzDY8NGdu7oTKX6hIXSp05sCqhy8mE4ECQE49
xKx5/llIkmtC3UYcdxAzGuXUHfGM8SfDg0FnQhRCSkTXhGwSSJVaEpjBpaJ4cP5m
3l6yqOn6CkZ0thw679ECQCWNC5hVEtsAb0TcjGdTpw+xTFusiZciNyHTQ64Zq2cc
ehQrxTRDIEBA4wIgUwrTwdVXk10OtpcVZvLIVjqdC84=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL/TzI4yHmlcC8qP3xWNieujmX
R7CnEaZJrDH1Hyr/tGNa0aEEjDIz+RlMntBbhOuiQMkMtCSB5X28h7HetiD4XkWT
XmlIiKZQLZ074cO5mxF+HhF7WIG30VONpX+Q4t/beqtaqbzyeIWvDdcCjUwOSQLr
UKU5PX9LFzX+FnN1UwIDAQAB
-----END PUBLIC KEY-----
Encoded
 b'\x843\x9aJ\xe6\x91p\xd2\x9c\xd0r{37\xa2G\x13Q\xc7~\xbd5\xce\x9f\xd4\x16\xda\x11\x02.\xb7\xf1\xf3Q\x8c|\xb0R2B\x1b)e\xeaD\x8e\x11\x1b\xb0J:\xbal\xac\x86\xdcb}_\x16IX\xccd\x0c\xb5E?Im<\x04ORT\xc9\xc6K|;\xf3\xbcK\xfd\x89\x96ZF(\x0b\x82v\x19`\xc3\xa1N\x934*\x9c\xfcT\xf4i\x02g\x1fl\xec\xc1\x19z\x9f7\xa6}\xe2\xe3}\xaa|\x1e\x13z\xd9$\xea'
Decoded
 'To be, or not to be - that is the question.'

We don't really need to use UTF-8 encoding here. Since src_data is a pure 7-bit ASCII string, and we've embedded it into the script as a literal, we could have supplied it as a literal bytes string instead:

src_data = b'To be, or not to be - that is the question.' 

Solution 2

RSA can only encrypt a limited amout of input. How much that is depends on the key size of RSA (1024-bit in your case) and the used padding. Everything bigger than that (128 byte when no padding is used and less if padding is used) and you cannot recover it anymore.

The solution is to use hybrid encryption.

  1. Generate a random byte string 16, 24 or 32 bytes to be used as the AES key,
  2. Encrypt the actual data with AES using the previously generated key and
  3. Encrypt the AES key with RSA.

AES encryption:

from Crypto.Cipher import AES
from Crypto import Random

aeskey = Random.new().read(32)
iv = Random.new().read(AES.block_size)
cipher = AES.new(aeskey, AES.MODE_CFB, iv)
msg = iv + cipher.encrypt(b'Attack at dawn')

RSA encryption (use a proper padding like OAEP, because textbook RSA is horribly broken):

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

message = aeskey
random_generator = Random.new().read
rsakey = RSA.generate(1024, random_generator)
cipher = PKCS1_OAEP.new(rsakey.publickey())
ciphertext = cipher.encrypt(message)

And only send msg and ciphertext. The decryption is similar, but backwards, because you first have to recover the AES key from the RSA ciphertext. Don't forget to slice off the IV when decryption with AES.

Share:
22,221
Jack Feng
Author by

Jack Feng

Updated on May 11, 2020

Comments

  • Jack Feng
    Jack Feng about 4 years

    I now know that RSA Public/Private key can only encrypt very short input at once, but can anyone provide a way to encrypt any type of file(.txt, .phf, .exe, etc) with only the public/private key? I do not want to have additional AES key.

    Here is my code, I am not getting the original content back after my encryption and decryption with the pair of public & private key. I do not care about how safe is my encryption or decryption, I just want the simple encrypt decrypt working on any input that it might take, no matter how long or large it is.

    from Crypto.PublicKey import RSA
    from Crypto import Random
    
    
    random_generator = Random.new().read
    key = RSA.generate(1024, random_generator)
    public_key = key.publickey()
    
    f = open('C:\Users\Administrator\Desktop\jack.txt','r').read()
    
    print 'original content: '+ f
    
    enc_data = public_key.encrypt(f, 32)
    print 'encrypted data: '
    print enc_data
    
    dec_data = key.decrypt(enc_data)
    print 'decrypted data: '+ dec_data
    

    Here is the output:

    original content: Python Cryptography Toolkit
    
    A collection of cryptographic modules implementing various algorithms and protocols.
    
    Subpackages:
    
    Crypto.Cipher
    Secret-key (AES, DES, ARC4) and public-key encryption (RSA PKCS#1) algorithms
    Crypto.Hash
    Hashing algorithms (MD5, SHA, HMAC)
    Crypto.Protocol
    Cryptographic protocols (Chaffing, all-or-nothing transform, key derivation functions). This package does not contain any network protocols.
    Crypto.PublicKey
    Public-key encryption and signature algorithms (RSA, DSA)
    Crypto.Signature
    Public-key signature algorithms (RSA PKCS#1)
    Crypto.Util
    Various useful modules and functions (long-to-string conversion, random number generation, number theoretic functions)
    encrypted data: 
    ('\x08\xe3\x9d\x03\x1e\xe9(\xe2\xc7\xc6e\x0b5\x02\xc0\xd8G\x1f\xf5\xb8\x9cMC\x93Z\x982\xa5\x97\xec\xab4\x18\xc2\xc8\xd9\xd3\x99aX\xd96b\x19\x96\xdc\x1d|F\xe0\xa9\xa9\xea\x03\x10>0g\x83\xdb\xeb\xdb\x13\x91\xc6\xd8\xf6\x95\xedE@A\x0bc\xae\xbe\xbe\xf0\xde\xcc\xcexk\x10\xb3\x86\xd3\xdd\xd0\xca@T2\x9a\x8a6ut\xb1\xaf\x07\x1f\xa2M\r\xf0D\xa2`h\xc3\x89\x18\x0e\xd4\xca\xee\xf5\xfc\x01\xed\x95}X\x1f\x13 1',)
    decrypted data: ���J�rPX �����ju�a,�xm�'�]��ٟ�?y;�)��tĹ�,�D4^�ba�8����9q
    +�i��l �q]Kd�Y���u��S�B���Ϲ�^�A3
    .7��j��m�
    �6�dl� qU
    
  • Jack Feng
    Jack Feng about 9 years
    is there anyway I can do it without an additional AES Key? Cause I am really looking for just encrypt/decrypting and sign/verifying the file with only public/private key.
  • Artjom B.
    Artjom B. about 9 years
    Is I said, if the file is too large, then you cannot encryption/decrypt it with plain or padded RSA. You need hybrid encryption or RSA-KEM. Signing and verifying would work, because an RSA signature is the encryption (with the private key) of the hash of the file contents and is therefore size agnostic.
  • Jack Feng
    Jack Feng about 9 years
    what my professor wanted was encrypt/sign and decrypt/verify with only the key pair, so when you send someone something, all they need is the encrypted file and just one of the key. The signature you were talking about is sending 3 things, file, signature, public key. He wanted the signature included inside of the file while signing, so all you need is public key to verify.
  • Artjom B.
    Artjom B. about 9 years
    One still needs only the public key to verify and one still needs only the private key to decrypt. It is just a cascade. You can see msg+ciphertext of the encryption as a single ciphertext. But a signature does not include the data just as much as your handwritten signature does not include your flesh and blood.
  • ScottMcC
    ScottMcC about 7 years
    To make this work in python 3, you need to encode src_data with .encode('UTF-8')
  • PM 2Ring
    PM 2Ring about 7 years
    @ScottMcC Thanks for the heads-up. I've updated my answer to include Python 2 / 3 compatible code. I suppose this answer doesn't really answer the OP's question, but I guess it's still a helpful demo for those who want to use RSA on a small amount of text.
  • ScottMcC
    ScottMcC about 7 years
    @PM 2Ring The additional content that you've added is certainly helpful, thanks for the update. Unfortunately the error output from pycrypto isn't very helpful when you forget to encode the string correctly in Python 3: TypeError: argument 1 must be int, not str . Took me a bit of time to realise that it was an encoding issue...
  • PM 2Ring
    PM 2Ring about 7 years
    @ScottMcC Crypto by name, crypto by nature. ;) It's good that Python 3 makes a clear distinction between text strings and bytes strings, and while I understand the rationale of having the items in a bytes (or bytearray) object being integers, many Python coders agree that it can be a bit bewildering when that sort of error happens. Even some of the core developers have wondered if they made the right decision there.