1

I'm trying to encrypt user-input which is first validated and sanitized by PHP. The data then gets encrypted using openssl and is stored in a MYSQL-backend. Over time we want to download that table and decrypt it with Python. So I'm looking at a cross-language requirement.

I have the following string as user input:

someone.somebody@blabla.com

this is sanitized using PHP's builtin method:

filter_var($verifiedMail, FILTER_SANITIZE_EMAIL)

I then pass it to the encryption logic of the PHP script:

$lock = "82YoH8jE7TJyEX2hLzFe35wTPaaDgVP1";     // use a 256 bit key!!!!!
/*Ideally you'd define the $lock as defined constant and store it elsewhere!!!*/
$method = 'aes-256-cfb';

$ivlen = openssl_cipher_iv_length($method);
$isSecure = false;
do{
    $iv = openssl_random_pseudo_bytes($ivlen,$isSecure);
}while(!isSecure);

$mailStorage = openssl_encrypt($mail, $method, $lock, 0, $iv);

unset($method, $lock, $ivlen);

In the SQL database I store two variables: $iv and $mailStorage For this iteration these values are:

$iv = '1abcd16944cc06c09e1d1635108a9077';
$mailStorage = 'gRDzkzHiysRqfjNXAYY+TWJ+88LtUcWfPFkn';

My $iv variable gets stored as a hexadecimal value by calling bin2hex($iv) when inserting the value in the database. So far so good; (I guess?)

The values are in the database, and the PHP-script works without throwing errors. So I continued to work on the Python decryption part. The relevant block for this is:

import binascii
from Crypto.Cipher import AES
from base64 import b64decode

securedoutput = "gRDzkzHiysRqfjNXAYY+TWJ+88LtUcWfPFkn"  #the result of the encryption done by php
vector = "1abcd16944cc06c09e1d1635108a9077"# the IV generated by PHP and stored as hex in the database
password = "82YoH8jE7TJyEX2hLzFe35wTPaaDgVP1"##the key used for encryption

iv = binascii.unhexlify(vector)
password = password.encode()
cipher = AES.new(password, AES.MODE_CFB, iv)
securedoutput = b64decode(securedoutput)
data = cipher.decrypt(securedoutput)
print(data)
print(data.decode())

I start by converting the hexadecimal initialization vector back to binary; and I encode the password which seems to be needed by the python module.

When I print out the data-variable I get:

b'sBD\xec@\x90\xbe\xe3\xe6\xccdv\x13\xb8\xb06@\x1a\xe6r\x82\xdfwe\xb0\xf4\x80'

and when applying data.decode() I get: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xec in position 3: invalid continuation byte

My main question is: Why is my python output in now way matching the PHP-input; what am I doing wrong when encrypting and/or decrypting the string?

Another question - more related to defensive programming principles relates to this PHP-block:

$isSecure = false;
do{
    $iv = openssl_random_pseudo_bytes($ivlen,$isSecure);
}while(!isSecure);

should I add a limit to this while-loop; or can I assume that openssl isn't going to take 100 iterations to find a secure value for $iv? What are some considerations I should make for this?

Finally; cryptography is a completely new field for me and I've spent the past few days reading up on initialization vectors, encryption modes etc... To the best of my limited knowledge, this should be secure; or am I blatantly wrong? I did not apply padding as I was led to believe that the CFB mode doesn't require that. Assuming my PHP-script doesn't leak; what's the worst that could happen if the database leaks (so the iv and encrypted output) are out in the open?

I've been looking at other questions which seem to use outdated modules such as:

mcrypt: Decrypting strings in Python that were encrypted with MCRYPT_RIJNDAEL_256 in PHP

They helped me in understanding the problem, but apparently not enough.

Clueless_captain
  • 420
  • 2
  • 13
  • This might be stupid, but have you tried enconding the email as utf8 on the php side? before encrypting – Claudio Oct 22 '19 at 12:45
  • @Claudio; thanks for thinking along - nothing stupid about that. I changed the relevant part of the code to: `$mailStorage = openssl_encrypt(utf8_encode($mail), $method, $lock, 0, $iv);` but the Python output unfortunately remains gibberish. – Clueless_captain Oct 22 '19 at 12:59
  • 2
    If you're using Pycryptodome, the default segment-size is 8 bits ([ref](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html#cfb-mode)), while it is 128 bits for your PHP code. You should be able to decrypt correctly, if you change the size to 128. even if the ciphertext and iv are leaked, the data is secure as long as the key is not known. – t.m.adam Oct 22 '19 at 14:54
  • @t.m.adam: Thanks! this was the way to go. I updated the Pythoncode to: `cipher = AES.new(password, AES.MODE_CFB, iv, segment_size=128)` Why do I need to set the segment size to 128 if the php code uses AES-256-CFB (or is this unrelated)? – Clueless_captain Oct 23 '19 at 08:03
  • 1
    Unrelated. The number 256 in AES-256-CFB specifies the key size (in Pycryptododome it is determined by the size of the supplied key). The segment size specifies how much of the key stream and input is used in any operation (I think). If you were to use an 8 bit segment size in PHP, you would use AES-256-CFB**8**, but it's best to use the full block size (128 bits). – t.m.adam Oct 23 '19 at 08:26

0 Answers0