#I am trying to make a compiler with dummy data via a JSON. I have to encrypt it as a .bin and #then hash it and export it as a .blob file. Is there a certain order of operations when it comes #to all of theses cryptography nodes/algorithms? Anyways I am able to encrypt the json and then #turn it into a .bin. All I have to do now is hash it & export it as a .blob. Is there a certain #order I need to do this process? Thank you and have a good day.
#! /usr/bin/env python3
import os
import json
import hashlib #justin import
# import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.asymmetric import ec
from base64 import b64encode, b64decode
key = os.urandom(16)
iv = os.urandom(16)
output_list = []
def encrypt_input(elements, num, type):
for i in range(1, num + 1):
if str(i) in elements:
if type == 'uint32_t':
value = int(elements[str(i)])
plaintext = value.to_bytes(4, 'big', signed=False)
elif type == 'hex':
value = (elements[str(i)])
plaintext = bytes.fromhex(value)
else:
value = 0
if type == 'uint32_t':
plaintext = value.to_bytes(4, 'big', signed=False)
elif type == 'hex':
plaintext = value.to_bytes(32, 'big', signed=False)
cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
encryptor = cipher.encryptor()
output_list.append(encryptor.update(plaintext) + encryptor.finalize())
with open(filename, mode='rb') as input_file:
file_contents = input_file.read()
cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
encryptor = cipher.encryptor()
file_length = len(file_contents).to_bytes(4, 'big', signed=False)
output_list.append(encryptor.update(file_length) + encryptor.finalize)
encryptor = cipher.encryptor()
ciphertext - encryptor.update(file_contents) + encryptor.finalize()
output_list.append(ciphertext)
with open('input.json') as json_file:
data = json.load(json_file)
encrypt_input(data['parameter'], 4 , 'uint_32_t')
encrypt_input(data['user_data'], 128 , 'hex')
encrypt_input(data['internal_data'], 32 , 'hex')
encrypt_file_input(data['user_binary']['file'])
data = b''.join(output_list)
private_key = ec.generate_private_key(ec.SECP256R1())
signature = private_key.sign(data, ec.ECDSA(hashes.SHA256))
print(signature)
with open('sip.bin','w') as output:
output.write(signature+data)
# justin's code
# this example is taking our file and turning it into BLOB format
# after its encrypted, you are using hash function,
#using the cryptography library located here https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#elliptic-curve-signature-algorithms
# https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/hashes.py#L136-L139
# from cryptography.hazmat.primitives.asymmetric import utils
# chosen_hash = hashes.SHA256()
# hasher = hashes.Hash(chosen_hash)
# hasher.update(b"data & ")
# hasher.update(b"more data")
# digest = hasher.finalize()
# sig = private_key.sign(
# digest,
# ec.ECDSA(utils.Prehashed(chosen_hash))
# )
# #SHA256 Secure Hash Algorithm
Related
I'm trying to encrypt and decrypt a file with PyCryptodome but without success. I can encrypt strings and data just fine but when trying to encrypt files it fails. I have 2 problems, first is that I can't encrypt larger strings. Witch i tried to solve by reading the file with a buffer. Second is that when I try to encrypt it as smaller buffers it just gives me an error "raise ValueError("Ciphertext with incorrect length.")"
My code looks like this:
from Crypto.Cipher import PKCS1_OAEP
import binascii
import ast
file_to_encrypt = "file_example_MP3_700KB.mp3"
buffer_size = 65536 # 64kb
input_file = open(file_to_encrypt, "rb")
output_file = open(file_to_encrypt + ".encrypted", "wb")
# Import keys
pub = open("publickey.txt", "rb")
pubKey = RSA.importKey(pub.read())
pub.close()
priv = open("privatekey.txt", "rb")
keyPair = RSA.importKey(priv.read())
priv.close()
# --------------------------------------------------------------
# Encrypt
encryptor = PKCS1_OAEP.new(pubKey)
buffer = input_file.read(buffer_size)
while len(buffer) > 0:
encrypted = encryptor.encrypt(buffer)
output_file.write(encrypted)
buffer = input_file.read(buffer_size)
input_file.close()
output_file.close()
# --------------------------------------------------------------
input_file = open(file_to_encrypt + ".encrypted", "rb")
output_file = open(file_to_encrypt + ".decrypted", "wb")
# Decrypt
decryptor = PKCS1_OAEP.new(keyPair)
buffer = input_file.read(buffer_size)
while len(buffer) > 0:
decrypted = decryptor.decrypt(ast.literal_eval(str(buffer)))
output_file.write(decrypted)
buffer = input_file.read(buffer_size)
input_file.close()
output_file.close()
# --------------------------------------------------------------
And generating the keys looks like this:
from Crypto.Cipher import PKCS1_OAEP
import binascii
import ast
# key generation
keyPair = RSA.generate(3072*2)
pubKey = keyPair.publickey()
# --------------------------------------------------------------
# Export keys
pub = open("publickey.txt", "wb")
pub.write(pubKey.exportKey('PEM'))
pub.close()
priv = open("privatekey.txt", "wb")
priv.write(keyPair.exportKey('PEM'))
priv.close()
# --------------------------------------------------------------
# Import keys
pub = open("publickey.txt", "rb")
pubKey = RSA.importKey(pub.read())
pub.close()
priv = open("privatekey.txt", "rb")
keyPair = RSA.importKey(priv.read())
priv.close()
# --------------------------------------------------------------
# encryption
msg = '550011'
encryptor = PKCS1_OAEP.new(pubKey)
encrypted = encryptor.encrypt(msg.encode())
# --------------------------------------------------------------
# decryption
decryptor = PKCS1_OAEP.new(keyPair)
decrypted = str(decryptor.decrypt(ast.literal_eval(str(encrypted))))[2:-1]
# --------------------------------------------------------------
print("Encrypted:", binascii.hexlify(encrypted))
print("Decrypted:", decrypted)
if msg == decrypted:
print("PASSED!")
else:
print("FAILED!")
Changing buffer_size fixes the first problem (that the data I'm trying to encrypt is too large.)
But I still can't decrypt my file after encrypting it.
Generating and importing keys works just fine. And encrypting and decrypting with them works just fine as well. As long as I'm only encrypting small strings and not files.
I am playing a CTF challenge . I have been given encrypted file and Code used for encryption where I've to find the time used for seed variable for decryption . But I'm unable to decrypt the output from bytes to string .
Here is the code used in the CTF challenge -
#! /usr/local/bin/python
from Crypto.Cipher import AES
from datetime import datetime
import hashlib
from time import strftime
seed = str(datetime.now())
key = hashlib.sha256(seed.encode('utf-8')).digest()
IV = 16 * '\x00'
mode = AES.MODE_CBC
encryptor = AES.new(key, mode, IV=IV)
text = open('flag.txt', 'r', encoding='utf-8').read()
text = text + "0" * (16-len(text)%16)
ciphertext = encryptor.encrypt(text)
target = open('flag.enc.txt', 'wb')
target.write(ciphertext)
target.close()
And here is the code I've used for decryption -
#! /usr/local/bin/python
from Crypto.Cipher import AES
from datetime import datetime
import hashlib
import base64
from time import strftime
seedOrg = '2021-07-06 03:16:51.'
IV = 16 * '\x00'
mode = AES.MODE_CBC
with open('/home/teja_mxx/Desktop/CapStone/encDec/flag.enc.txt', 'rb') as f:
encText = f.read()
print(encText)
for x in range(1):
if len(str(x)) < 9:
llen = len(str(x))
milli = ("0" * (9 - llen))+str(x)
seed = seedOrg+milli
key = hashlib.sha256(seed.encode('utf-8')).digest()
decryptor = AES.new(key, mode, IV=IV)
decryptedText = decryptor.decrypt(encText)
print(decryptedText.decode())
And I'm getting this error . How do I resolve it ?
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe1 in position 4: invalid continuation byte
Overview of what I'm trying to do. I have encrypted versions of files that I need to read into pandas. For a couple of reasons it is much better to decrypt into a stream rather than a file, so that's my interest below although I also attempt to decrypt to a file just as an intermediate step (but this also isn't working).
I'm able to get this working for a csv, but not for either hdf or stata (I'd accept an answer that works for either hdf or stata, though the answer might be the same for both, which is why I'm combining in one question).
The code for encrypting/decrypting files is taken from another stackoverflow question (which I can't find at the moment).
import pandas as pd
import io
from Crypto import Random
from Crypto.Cipher import AES
def pad(s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(message, key, key_size=256):
message = pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def decrypt(ciphertext, key):
iv = ciphertext[:AES.block_size]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return plaintext.rstrip(b"\0")
def encrypt_file(file_name, key):
with open(file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
with open(file_name + ".enc", 'wb') as fo:
fo.write(enc)
def decrypt_file(file_name, key):
with open(file_name, 'rb') as fo:
ciphertext = fo.read()
dec = decrypt(ciphertext, key)
with open(file_name[:-4], 'wb') as fo:
fo.write(dec)
And here's my attempt to extend the code to decrypt to a stream rather than a file.
def decrypt_stream(file_name, key):
with open(file_name, 'rb') as fo:
ciphertext = fo.read()
dec = decrypt(ciphertext, key)
cipherbyte = io.BytesIO()
cipherbyte.write(dec)
cipherbyte.seek(0)
return cipherbyte
Finally, here's the sample program with sample data attempting to make this work:
key = 'this is an example key'[:16]
df = pd.DataFrame({ 'x':[1,2], 'y':[3,4] })
df.to_csv('test.csv',index=False)
df.to_hdf('test.h5','test',mode='w')
df.to_stata('test.dta')
encrypt_file('test.csv',key)
encrypt_file('test.h5',key)
encrypt_file('test.dta',key)
decrypt_file('test.csv.enc',key)
decrypt_file('test.h5.enc',key)
decrypt_file('test.dta.enc',key)
# csv works here but hdf and stata don't
# I'm less interested in this part but include it for completeness
df_from_file = pd.read_csv('test.csv')
df_from_file = pd.read_hdf('test.h5','test')
df_from_file = pd.read_stata('test.dta')
# csv works here but hdf and stata don't
# the hdf and stata lines below are what I really need to get working
df_from_stream = pd.read_csv( decrypt_stream('test.csv.enc',key) )
df_from_stream = pd.read_hdf( decrypt_stream('test.h5.enc',key), 'test' )
df_from_stream = pd.read_stata( decrypt_stream('test.dta.enc',key) )
Unfortunately I don't think I can shrink this code anymore and still have a complete example.
Again, my hope would be to have all 4 non-working lines above working (file and stream for hdf and stata) but I'm happy to accept an answer that works for either the hdf stream alone or the stata stream alone.
Also, I'm open to other encryption alternatives, I just used some existing pycrypto-based code that I found here on SO. My work explicitly requires 256-bit AES but beyond that I'm open so this solution needn't be based specifically on the pycrypto library or the specific code example above.
Info on my setup:
python: 3.4.3
pandas: 0.17.0 (anaconda 2.3.0 distribution)
mac os: 10.11.3
The biggest issue is the padding/unpadding method. It assumes that the null character can't be part of the actual content. Since stata/hdf files are binary, it's safer to pad using the number of extra bytes we use, encoded as a character. This number will be used during unpadding.
Also for this time being, read_hdf doesn't support reading from a file like object, even if the API documentation claims so. If we restrict ourselves to the stata format, the following code will perform what you need:
import pandas as pd
import io
from Crypto import Random
from Crypto.Cipher import AES
def pad(s):
n = AES.block_size - len(s) % AES.block_size
return s + n * chr(n)
def unpad(s):
return s[:-ord(s[-1])]
def encrypt(message, key, key_size=256):
message = pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def decrypt(ciphertext, key):
iv = ciphertext[:AES.block_size]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext[AES.block_size:])
return unpad(plaintext)
def encrypt_file(file_name, key):
with open(file_name, 'rb') as fo:
plaintext = fo.read()
enc = encrypt(plaintext, key)
with open(file_name + ".enc", 'wb') as fo:
fo.write(enc)
def decrypt_stream(file_name, key):
with open(file_name, 'rb') as fo:
ciphertext = fo.read()
dec = decrypt(ciphertext, key)
cipherbyte = io.BytesIO()
cipherbyte.write(dec)
cipherbyte.seek(0)
return cipherbyte
key = 'this is an example key'[:16]
df = pd.DataFrame({
'x': [1,2],
'y': [3,4]
})
df.to_stata('test.dta')
encrypt_file('test.dta', key)
print pd.read_stata(decrypt_stream('test.dta.enc', key))
Output:
index x y
0 0 1 3
1 1 2 4
In python 3 you can use the following pad, unpad versions:
def pad(s):
n = AES.block_size - len(s) % AES.block_size
return s + bytearray([n] * n)
def unpad(s):
return s[:-s[-1]]
What worked for me in the case of .h5 format and the cryptography library was:
from cryptography.fernet import Fernet
def read_h5_file(new_file:str, decrypted: bytes, verbose=False):
with open(new_file, 'wb') as f:
f.write(decrypted)
print(f'Created {new_file}') if verbose else ''
df = pd.read_hdf(new_file)
os.remove(new_file)
print(f'Deleted {new_file}') if verbose else ''
return df
with open(path_to_file, 'rb') as f:
data = f.read()
fernet = Fernet(key)
decrypted = fernet.decrypt(data)
new_file = './example_path/example.h5'
df = read_h5_file(new_file, decrypted, verbose=verbose)
So I created a .h5 file. Read its content. Return it with the function. Delete the decrypted file again.
Maybe this approach helps, as I didn't find any other or similar solution on this online.
I need help using RSA encryption and decryption in Python.
I am creating a private/public key pair, encrypting a message with keys and writing message to a file. Then I am reading ciphertext from file and decrypting text using key.
I am having trouble with the decryption portion. As you can see in my code below, when I put in decrypted = key.decrypt(message) that the program works, yet the decrypted message is encrypted again. It seems like it is not reading the ciphertext from the file.
Can anyone help me write this code so decryption reads ciphertext from file and then uses key to decrypt ciphertext?
import Crypto
from Crypto.PublicKey import RSA
from Crypto import Random
random_generator = Random.new().read
key = RSA.generate(1024, random_generator) #generate public and private keys
publickey = key.publickey # pub key export for exchange
encrypted = publickey.encrypt('encrypt this message', 32)
#message to encrypt is in the above line 'encrypt this message'
print 'encrypted message:', encrypted #ciphertext
f = open ('encryption.txt', 'w'w)
f.write(str(encrypted)) #write ciphertext to file
f.close()
#decrypted code below
f = open ('encryption.txt', 'r')
message = f.read()
decrypted = key.decrypt(message)
print 'decrypted', decrypted
f = open ('encryption.txt', 'w')
f.write(str(message))
f.write(str(decrypted))
f.close()
In order to make it work you need to convert key from str to tuple before decryption(ast.literal_eval function). Here is fixed code:
import Crypto
from Crypto.PublicKey import RSA
from Crypto import Random
import ast
random_generator = Random.new().read
key = RSA.generate(1024, random_generator) #generate pub and priv key
publickey = key.publickey() # pub key export for exchange
encrypted = publickey.encrypt('encrypt this message', 32)
#message to encrypt is in the above line 'encrypt this message'
print('encrypted message:', encrypted) #ciphertext
f = open ('encryption.txt', 'w')
f.write(str(encrypted)) #write ciphertext to file
f.close()
#decrypted code below
f = open('encryption.txt', 'r')
message = f.read()
decrypted = key.decrypt(ast.literal_eval(str(encrypted)))
print('decrypted', decrypted)
f = open ('encryption.txt', 'w')
f.write(str(message))
f.write(str(decrypted))
f.close()
PKCS#1 OAEP is an asymmetric cipher based on RSA and the OAEP padding
from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Cipher import PKCS1_OAEP
def rsa_encrypt_decrypt():
key = RSA.generate(2048)
private_key = key.export_key('PEM')
public_key = key.publickey().exportKey('PEM')
message = input('plain text for RSA encryption and decryption:')
message = str.encode(message)
rsa_public_key = RSA.importKey(public_key)
rsa_public_key = PKCS1_OAEP.new(rsa_public_key)
encrypted_text = rsa_public_key.encrypt(message)
#encrypted_text = b64encode(encrypted_text)
print('your encrypted_text is : {}'.format(encrypted_text))
rsa_private_key = RSA.importKey(private_key)
rsa_private_key = PKCS1_OAEP.new(rsa_private_key)
decrypted_text = rsa_private_key.decrypt(encrypted_text)
print('your decrypted_text is : {}'.format(decrypted_text))
# coding: utf-8
from __future__ import unicode_literals
import base64
import os
import six
from Crypto import Random
from Crypto.PublicKey import RSA
class PublicKeyFileExists(Exception): pass
class RSAEncryption(object):
PRIVATE_KEY_FILE_PATH = None
PUBLIC_KEY_FILE_PATH = None
def encrypt(self, message):
public_key = self._get_public_key()
public_key_object = RSA.importKey(public_key)
random_phrase = 'M'
encrypted_message = public_key_object.encrypt(self._to_format_for_encrypt(message), random_phrase)[0]
# use base64 for save encrypted_message in database without problems with encoding
return base64.b64encode(encrypted_message)
def decrypt(self, encoded_encrypted_message):
encrypted_message = base64.b64decode(encoded_encrypted_message)
private_key = self._get_private_key()
private_key_object = RSA.importKey(private_key)
decrypted_message = private_key_object.decrypt(encrypted_message)
return six.text_type(decrypted_message, encoding='utf8')
def generate_keys(self):
"""Be careful rewrite your keys"""
random_generator = Random.new().read
key = RSA.generate(1024, random_generator)
private, public = key.exportKey(), key.publickey().exportKey()
if os.path.isfile(self.PUBLIC_KEY_FILE_PATH):
raise PublicKeyFileExists('Файл с публичным ключом существует. Удалите ключ')
self.create_directories()
with open(self.PRIVATE_KEY_FILE_PATH, 'w') as private_file:
private_file.write(private)
with open(self.PUBLIC_KEY_FILE_PATH, 'w') as public_file:
public_file.write(public)
return private, public
def create_directories(self, for_private_key=True):
public_key_path = self.PUBLIC_KEY_FILE_PATH.rsplit('/', 1)
if not os.path.exists(public_key_path):
os.makedirs(public_key_path)
if for_private_key:
private_key_path = self.PRIVATE_KEY_FILE_PATH.rsplit('/', 1)
if not os.path.exists(private_key_path):
os.makedirs(private_key_path)
def _get_public_key(self):
"""run generate_keys() before get keys """
with open(self.PUBLIC_KEY_FILE_PATH, 'r') as _file:
return _file.read()
def _get_private_key(self):
"""run generate_keys() before get keys """
with open(self.PRIVATE_KEY_FILE_PATH, 'r') as _file:
return _file.read()
def _to_format_for_encrypt(self, value):
if isinstance(value, int):
return six.binary_type(value)
for str_type in six.string_types:
if isinstance(value, str_type):
return value.encode('utf8')
if isinstance(value, six.binary_type):
return value
And use
KEYS_DIRECTORY = settings.SURVEY_DIR_WITH_ENCRYPTED_KEYS
class TestingEncryption(RSAEncryption):
PRIVATE_KEY_FILE_PATH = KEYS_DIRECTORY + 'private.key'
PUBLIC_KEY_FILE_PATH = KEYS_DIRECTORY + 'public.key'
# django/flask
from django.core.files import File
class ProductionEncryption(RSAEncryption):
PUBLIC_KEY_FILE_PATH = settings.SURVEY_DIR_WITH_ENCRYPTED_KEYS + 'public.key'
def _get_private_key(self):
"""run generate_keys() before get keys """
from corportal.utils import global_elements
private_key = global_elements.request.FILES.get('private_key')
if private_key:
private_key_file = File(private_key)
return private_key_file.read()
message = 'Hello мой friend'
encrypted_mes = ProductionEncryption().encrypt(message)
decrypted_mes = ProductionEncryption().decrypt(message)
Here is my implementation for python 3 and pycrypto
from Crypto.PublicKey import RSA
key = RSA.generate(4096)
f = open('/home/john/Desktop/my_rsa_public.pem', 'wb')
f.write(key.publickey().exportKey('PEM'))
f.close()
f = open('/home/john/Desktop/my_rsa_private.pem', 'wb')
f.write(key.exportKey('PEM'))
f.close()
f = open('/home/john/Desktop/my_rsa_public.pem', 'rb')
f1 = open('/home/john/Desktop/my_rsa_private.pem', 'rb')
key = RSA.importKey(f.read())
key1 = RSA.importKey(f1.read())
x = key.encrypt(b"dddddd",32)
print(x)
z = key1.decrypt(x)
print(z)
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
secret_message = b'ATTACK AT DAWN'
### First, make a key and save it
key = RSA.generate(2048)
with open( 'mykey.pem', 'wb' ) as f:
f.write( key.exportKey( 'PEM' ))
### Then use key to encrypt and save our message
public_crypter = PKCS1_OAEP.new( key )
enc_data = public_crypter.encrypt( secret_message )
with open( 'encrypted.txt', 'wb' ) as f:
f.write( enc_data )
### And later on load and decode
with open( 'mykey.pem', 'r' ) as f:
key = RSA.importKey( f.read() )
with open( 'encrypted.txt', 'rb' ) as f:
encrypted_data = f.read()
public_crypter = PKCS1_OAEP.new( key )
decrypted_data = public_crypter.decrypt( encrypted_data )
You can use simple way for genarate RSA . Use rsa library
pip install rsa
Watch out using Crypto!!!
It is a wonderful library but it has an issue in python3.8 'cause from the library time was removed the attribute clock(). To fix it just modify the source in /usr/lib/python3.8/site-packages/Crypto/Random/_UserFriendlyRNG.pyline 77 changing t = time.clock() int t = time.perf_counter()
I just found pycrypto today, and I've been working on my AES encryption class. Unfortunately it only half-works. self.h.md5 outputs md5 hash in hex format, and is 32byte.
This is the output. It seems to decrypt the message, but it puts random characters after decryption, in this case \n\n\n... I think I have a problem with block size of self.data, anyone know how to fix this?
Jans-MacBook-Pro:test2 jan$ ../../bin/python3 data.py
b'RLfGmn5jf5WTJphnmW0hXG7IaIYcCRpjaTTqwXR6yiJCUytnDib+GQYlFORm+jIctest
1 2 3 4 5 endtest\n\n\n\n\n\n\n\n\n\n'
from Crypto.Cipher import AES
from base64 import b64encode, b64decode
from os import urandom
class Encryption():
def __init__(self):
self.h = Hash()
def values(self, data, key):
self.data = data
self.key = key
self.mode = AES.MODE_CBC
self.iv = urandom(16)
if not self.key:
self.key = Cfg_Encrypt_Key
self.key = self.h.md5(self.key, True)
def encrypt(self, data, key):
self.values(data, key)
return b64encode(self.iv + AES.new(self.key, self.mode, self.iv).encrypt(self.data))
def decrypt(self, data, key):
self.values(data, key)
self.iv = b64decode(self.data)[:16]
return AES.new(self.key, self.mode, self.iv).decrypt(b64decode(self.data)[16:])
To be honest, the characters "\n\n\n\n\n\n\n\n\n\n" don't look that random to me. ;-)
You are using AES in CBC mode. That requires length of plaintext and ciphertext to be always a multiple of 16 bytes. With the code you show, you should actually see an exception being raised when data passed to encrypt() does not fulfill such condition. It looks like you added enough new line characters ('\n' to whatever the input is until the plaintext happened to be aligned.
Apart from that, there are two common ways to solve the alignment issue:
Switch from CBC (AES.MODE_CBC) to CFB (AES.MODE_CFB). With the default segment_size used by PyCrypto, you will not have any restriction on plaintext and ciphertext lengths.
Keep CBC and use a padding scheme like PKCS#7, that is:
before encrypting a plaintext of X bytes, append to the back as many bytes you need to to reach the next 16 byte boundary. All padding bytes have the same value: the number of bytes that you are adding:
length = 16 - (len(data) % 16)
data += bytes([length])*length
That's Python 3 style. In Python 2, you would have:
length = 16 - (len(data) % 16)
data += chr(length)*length
after decrypting, remove from the back of the plaintext as many bytes as indicated by padding:
data = data[:-data[-1]]
Even though I understand in your case it is just a class exercise, I would like to point out that it is insecure to send data without any form of authentication (e.g. a MAC).
You can use a fix character as long as you remember the length of your initial payload, so you don't "throw" useful end bytes away.
Try this:
import base64
from Crypto.Cipher import AES
def encrypt(payload, salt, key):
return AES.new(key, AES.MODE_CBC, salt).encrypt(r_pad(payload))
def decrypt(payload, salt, key, length):
return AES.new(key, AES.MODE_CBC, salt).decrypt(payload)[:length]
def r_pad(payload, block_size=16):
length = block_size - (len(payload) % block_size)
return payload + chr(length) * length
print(decrypt(encrypt("some cyphertext", "b" * 16, "b" * 16), "b" * 16, "b" * 16, len("some cyphertext")))
from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
import base64
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = ''
while len(d) < key_length + iv_length:
d_i = md5(d_i + password + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def encrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
salt = Random.new().read(bs - len('Salted__'))
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
#print in_file
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
out_file.write('Salted__' + salt)
finished = False
while not finished:
chunk = in_file.read(1024 * bs)
if len(chunk) == 0 or len(chunk) % bs != 0:
padding_length = bs - (len(chunk) % bs)
chunk += padding_length * chr(padding_length)
finished = True
out_file.write(cipher.encrypt(chunk))
in_file.close()
out_file.close()
def decrypt(in_file, out_file, password, key_length=32):
bs = AES.block_size
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
salt = in_file.read(bs)[len('Salted__'):]
key, iv = derive_key_and_iv(password, salt, key_length, bs)
cipher = AES.new(key, AES.MODE_CBC, iv)
next_chunk = ''
finished = False
while not finished:
chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
if len(next_chunk) == 0:
padding_length = ord(chunk[-1])
if padding_length < 1 or padding_length > bs:
raise ValueError("bad decrypt pad (%d)" % padding_length)
# all the pad-bytes must be the same
if chunk[-padding_length:] != (padding_length * chr(padding_length)):
# this is similar to the bad decrypt:evp_enc.c from openssl program
raise ValueError("bad decrypt")
chunk = chunk[:-padding_length]
finished = True
out_file.write(chunk)
in_file.close()
out_file.close()
def encode(in_file, out_file):
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
data = in_file.read()
out_file.write(base64.b64encode(data))
in_file.close()
out_file.close()
def decode(in_file, out_file):
in_file = file(in_file, 'rb')
out_file = file(out_file, 'wb')
data = in_file.read()
out_file.write(base64.b64decode(data))
in_file.close()
out_file.close()
AES.new().encrypt() and .decrypt() take as both input and output strings whose length is a multiple of 16. You have to fix it in one way or another. For example you can store the real length at the start, and use that to truncate the decrypted string.
Note also that while it's the only restriction for AES, other modules (notably in Crypto.PublicKey) have additional restrictions that comes from their mathematical implementation and that shouldn't (in my opinion) be visible to the end user, but are. For example Crypto.PublicKey.ElGamal will encrypt any short string, but if it starts with null characters, they are lost upon decryption.