I am working on a project where PHP is used for decrypt AES-256-CBC messages
<?php
class CryptService{
private static $encryptMethod = 'AES-256-CBC';
private $key;
private $iv;
public function __construct(){
$this->key = hash('sha256', 'c7b35827805788e77e41c50df44441491098be42');
$this->iv = substr(hash('sha256', 'c09f6a9e157d253d0b2f0bcd81d338298950f246'), 0, 16);
}
public function decrypt($string){
$string = base64_decode($string);
return openssl_decrypt($string, self::$encryptMethod, $this->key, 0, $this->iv);
}
public function encrypt($string){
$output = openssl_encrypt($string, self::$encryptMethod, $this->key, 0, $this->iv);
$output = base64_encode($output);
return $output;
}
}
$a = new CryptService;
echo $a->encrypt('secret');
echo "\n";
echo $a->decrypt('S1NaeUFaUHdqc20rQWM1L2ZVMDJudz09');
echo "\n";
ouutput
>>> S1NaeUFaUHdqc20rQWM1L2ZVMDJudz09
>>> secret
Now I have to write Python 3 code for encrypting data.
I've tried use PyCrypto but without success. My code:
import base64
import hashlib
from Crypto.Cipher import AES
class AESCipher:
def __init__(self, key, iv):
self.key = hashlib.sha256(key.encode('utf-8')).digest()
self.iv = hashlib.sha256(iv.encode('utf-8')).digest()[:16]
__pad = lambda self,s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
__unpad = lambda self,s: s[0:-ord(s[-1])]
def encrypt( self, raw ):
raw = self.__pad(raw)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return base64.b64encode(cipher.encrypt(raw))
def decrypt( self, enc ):
enc = base64.b64decode(enc)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv )
return self.__unpad(cipher.decrypt(enc).decode("utf-8"))
cipher = AESCipher('c7b35827805788e77e41c50df44441491098be42', 'c09f6a9e157d253d0b2f0bcd81d338298950f246')
enc_str = cipher.encrypt("secret")
print(enc_str)
output
>>> b'tnF87LsVAkzkvs+gwpCRMg=='
But I need output S1NaeUFaUHdqc20rQWM1L2ZVMDJudz09 which will PHP decrypt to secret. How to modify Python code to get expected output?
PHP's hash outputs a Hex-encoded string by default, but Python's .digest() returns bytes. You probably wanted to use .hexdigest():
def __init__(self, key, iv):
self.key = hashlib.sha256(key.encode('utf-8')).hexdigest()[:32].encode("utf-8")
self.iv = hashlib.sha256(iv.encode('utf-8')).hexdigest()[:16].encode("utf-8")
The idea of the initialization vector (IV) is to provide randomization for the encryption with the same key. If you use the same IV, an attacker may be able to deduce that you send the same message twice. This can be considered as a broken protocol.
The IV is not supposed to be secret, so you can simply send it along with the ciphertext. It is common to prepend it to the ciphertext during encryption and slice it off before decryption.
Related
I'm trying to write logic with Elixir and PHP to encrypt and decrypt text. My goal is to decrypt data which is encrypted in Elixir and vice versa. Both algorithms works fine, but the only problem I'm encountering is separating Initialization Vector from cipher text and conversion of binary to string in PHP and also decrypted text in Elixir.
Elixir Implementation:
defmodule Crypto.AES do
#block_size 16
#secret_key "something secret here"
def encrypt(text) do
secret_key_hash = make_hash(#secret_key, 32)
IO.inspect secret_key_hash
# create random Initialisation Vector
iv = :crypto.strong_rand_bytes(#block_size)
text = pad_pkcs7(text, #block_size)
encrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, text, true )
encrypted_text = ( iv <> <<"::">> <> encrypted_text )
Base.encode64(encrypted_text)
end
def decrypt(ciphertext) do
secret_key_hash = make_hash(#secret_key, 32)
{:ok, ciphertext} = Base.decode64(ciphertext)
<<iv::binary-16, rp::binary-2, ciphertext::binary>> = ciphertext
decrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, ciphertext, false)
unpad_pkcs7(decrypted_text)
end
#doc """
Pad the `message` by extending it to the nearest `blocksize` boundary,
appending the number of bytes of padding to the end of the block.
If the original `message` is a multiple of `blocksize`, an additional block
of bytes with value `blocksize` is added.
"""
def pad_pkcs7(message, blocksize) do
pad = blocksize - rem(byte_size(message), blocksize)
message <> to_string(List.duplicate(pad, pad))
end
#doc """
Remove the PKCS#7 padding from the end of `data`.
"""
def unpad_pkcs7(data) do
<<pad>> = binary_part(data, byte_size(data), -1)
binary_part(data, 0, byte_size(data) - pad)
end
def make_hash(text, length) do
:crypto.hash(:sha512, text)
|> Base.encode16
|> String.downcase
|> String.slice(0, length)
end
end
PHP Implementation:
<?php
$ENCRYPTION_KEY = 'something secret here';
$ENCRYPTION_ALGORITHM = 'AES-256-CBC';
function encrypt($plain_text) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
// create random Initialisation Vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ENCRYPTION_ALGORITHM));
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
0,
$iv
);
return base64_encode($encrypted_text . '::' . $iv);
}
function decrypt($ciphertext) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
$split = explode('::', base64_decode($ciphertext), 2);
list($iv, $encrypted_text) = array_pad($split, 2, null);
$plain_text = openssl_decrypt(
$encrypted_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
0,
$iv
);
return $plain_text;
}
function make_hash($text, $length) {
$hash_key = hash("sha512", $text, false);
return substr($hash_key,0,$length);
}
$ct = encrypt("hello");
// echo $ct."\n";
echo decrypt("Sr4nMnMdDHhUQcnW6RwZ2Do6rhBh/ytW1W/x7Xx2/Xrv3A==")."\n";
?>
Please suggest some possible solutions. Thanks
The two codes are incompatible for two reasons:
The PHP code returns the ciphertext Base64 encoded by default during encryption. This must be disabled because the Base64 encoding is performed explicitly after the concatenation of IV and ciphertext. Disabling is possible by passing OPENSSL_RAW_DATA as 4th parameter in openssl_encrypt(). The same applies for decryption:
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA, // Fix: Don't Base64 encode the ciphertext
$iv
);
...
$plain_text = openssl_decrypt(
$encrted_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA, // Fix: Don't Base64 decode the ciphertext
$iv
);
As already noted in the comment, both codes assume a different order of IV and ciphertext when separating during decryption. Menwhile you have adapted the separation during decryption in the PHP code to that of the Elixir code.
But also for encryption, both codes use a different order of IV and ciphertext. The modification of the PHP code concerning the encryption is still missing:
...
return base64_encode($iv . '::' . $encrypted_text); // Reverse order
With these changes, the two codes are functionally identical and a ciphertext generated with the PHP code can be decrypted with the Elixir code and vice versa.
A note regarding concatenation: Both the IV and ciphertext can contain the separator :: with a certain probability, which can cause problems. It would be better to concatenate the data without a separator and use the known length of the IV for the separation.
Also, using a hash function for key derivation is insecure, better apply a reliable key derivation function like PBKDF2. Furthermore, taking the hex encoded key instead of the binary data reduces security.
With suggestions (except PBKDF2, will add later) from #Topaco, here's complete solution.
Elixir - git-link
defmodule Crypto.AES do
#block_size 16
#secret_key "put something secret here"
def encrypt(plain_text) do
secret_key_hash = make_hash(#secret_key, 32)
# create Initialisation Vector
iv = :crypto.strong_rand_bytes(#block_size)
padded_text = pad_pkcs7(plain_text, #block_size)
encrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, padded_text, true )
# concatenate IV for decryption
encrypted_text = ( iv <> encrypted_text )
Base.encode64(encrypted_text)
end
def decrypt(ciphertext) do
secret_key_hash = make_hash(#secret_key, 32)
{:ok, ciphertext} = Base.decode64(ciphertext)
<<iv::binary-16, ciphertext::binary>> = ciphertext
decrypted_text = :crypto.crypto_one_time(:aes_256_cbc, secret_key_hash, iv, ciphertext, false)
unpad_pkcs7(decrypted_text)
end
defp pad_pkcs7(message, blocksize) do
pad = blocksize - rem(byte_size(message), blocksize)
message <> to_string(List.duplicate(pad, pad))
end
defp unpad_pkcs7(data) do
<<pad>> = binary_part(data, byte_size(data), -1)
binary_part(data, 0, byte_size(data) - pad)
end
defp make_hash(text, length) do
:crypto.hash(:sha512, text)
|> Base.encode16
|> String.downcase
|> String.slice(0, length)
end
end
PHP - gist-link
<?php
$ENCRYPTION_KEY = "put something secret here";
$ENCRYPTION_ALGORITHM = 'AES-256-CBC';
function encrypt($plain_text) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
// create random Initialization Vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ENCRYPTION_ALGORITHM));
$encrypted_text = openssl_encrypt(
$plain_text,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA,
$iv
);
# concatenate the IV for decryption
return base64_encode($iv . $encrypted_text);
}
function decrypt($ciphertext) {
global $ENCRYPTION_KEY;
global $ENCRYPTION_ALGORITHM;
$EncryptionKey = make_hash($ENCRYPTION_KEY, 32);
$ciphertext = base64_decode($ciphertext);
// get Initialization Vector part (16 bytes long)
$iv = substr($ciphertext, 0, 16);
// rest is actual cipher text
$ciphertext = substr($ciphertext, 16);
$decrypted_text = openssl_decrypt(
$ciphertext,
$ENCRYPTION_ALGORITHM,
$EncryptionKey,
OPENSSL_RAW_DATA,
$iv
);
return $decrypted_text;
}
function make_hash($text, $length) {
$hash_key = hash("sha512", $text, false);
return substr($hash_key,0,$length);
}
// $ct = encrypt("code");
// $dt = decrypt($ct);
// echo $ct."\n";
// echo $dt."\n";
?>
I'm migrating my existing code to Python3, unfortunately the decryption shows error that IV must be 16 bytes long.
I have tried decrypting the key with sha1 then tried to decrypt.
My php5.6 code is
<?php
define('ENCR_ALGO', MCRYPT_RIJNDAEL_256);
define('ENCR_MODE', MCRYPT_MODE_CBC);
define('KEY',"This is test key");
function encryptData($plaintext){
$iv=generateIv();
$ciphertext = mcrypt_encrypt(ENCR_ALGO, getKey(), $plaintext, ENCR_MODE, $iv);
$ciphertext.=$iv;
return trim(base64_encode($ciphertext));
}
function decryptData($ciphertext){
$iv=getIvFromCiphertext($ciphertext);
$ciphertext=getActualCiphertext($ciphertext);
$plaintext = mcrypt_decrypt(ENCR_ALGO, getKey(), $ciphertext, ENCR_MODE, $iv);
return trim($plaintext);
}
function getIvFromCiphertext($encryptedData){
$encryptedData = base64_decode($encryptedData);
$cipherTextSize = strlen($encryptedData);
$ivStartIndex = $cipherTextSize-ivSize();
return substr($encryptedData, $ivStartIndex , ivSize());
}
function getActualCiphertext($encryptedData){
$encryptedData = base64_decode($encryptedData);
$cipherTextSize = strlen($encryptedData);
return substr($encryptedData, 0,$cipherTextSize-ivSize());
}
function ivSize(){
return mcrypt_get_iv_size(ENCR_ALGO, ENCR_MODE);
}
function keySize(){
return mcrypt_get_key_size(ENCR_ALGO, ENCR_MODE);
}
function generateIv(){
return mcrypt_create_iv(keySize(), MCRYPT_RAND );
}
function getKey(){
return substr(sha1(KEY), 0, keySize());
}
echo "<br/>";
echo encryptData("my pass");
echo decryptData("vuv6kZgweA2YqSU4vMOuYStrbwZayDYaL7UQ+JajFCVc2p4HW1o68OmIm2l3Rbi/IaCWtKD5m6an7LqnvwRYVA==");
?>
In my python3 file, it's like
from hashlib import sha1
import base64
from Crypto import Random
from Crypto.Cipher import AES
def actual_text(txt):
data = base64.b64decode(txt)
return data[:32], data[32:64]
passs = "vuv6kZgweA2YqSU4vMOuYStrbwZayDYaL7UQ+JajFCVc2p4HW1o68OmIm2l3Rbi/IaCWtKD5m6an7LqnvwRYVA=="
key = "This is test key"
text, iv = actual_text(passs)
class Encryptor:
def __init__(self, key):
self.key = key
def pad(self, s):
return s + b"\0" * (AES.block_size - len(s) % AES.block_size)
def encrypt(self, message, key, key_size=256):
message = self.pad(message)
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return iv + cipher.encrypt(message)
def decrypt(self, ciphertext, key, iv):
# 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 make_sha1(s, encoding='utf-8'):
return sha1(s.encode(encoding)).hexdigest()
make_sha1(key, encoding='utf-8')
key2 = make_sha1(key, encoding='utf-8')[:32]
print(iv)
enc = Encryptor(key2)
enc.decrypt(text,key2,iv)
The error is showing ValueError: IV must be 16 bytes long
I've expected result is "my pass"
I am just wondering is it possible to convert PHP encryption function to Python? I am using PHP function to encrypt USER ID and store it in Database and now I need to decrypt USER ID in Python, I using this PHP function:
function decrypt($id) {
$cryptKey = '123';
$decoded = rtrim( mcrypt_decrypt( MCRYPT_RIJNDAEL_256, md5( $cryptKey ), base64_decode( $id ), MCRYPT_MODE_CBC, md5( md5( $cryptKey ) ) ), "\0");
return( $decoded );
}
Here is your function 'translated' to python
from Crypto.Cipher import AES
from hashlib import md5
def decrypt(id):
cryptKey = '123'
cipher = AES.new(key=md5(cryptKey).hexdigest(), mode=AES.MODE_CBC, IV=md5(md5(cryptKey).hexdigest()).hexdigest()[:16])
decoded = cipher.decrypt(id.decode('base64')).rstrip('\0')
return decoded
A fiew suggestions
1. Use a random iv
2. Use a more complex key
3. Don't hardcode the key
4. Use openssl_decrypt , mcrypt_decrypt is deprecated
Note
This will not work with MCRYPT_RIJNDAEL_256 because it uses 32 byte blocks .
But you could use MCRYPT_RIJNDAEL_128 or openssl
Here is an example with openssl AES CBC in PHP :
function encrypt($data, $key) {
$method = "aes-" . strlen($key) * 8 . "-cbc";
$iv = openssl_random_pseudo_bytes(16);
$encoded = base64_encode($iv . openssl_encrypt($data, $method, $key, TRUE, $iv));
return $encoded;
}
function decrypt($data, $key) {
$method = "aes-" . strlen($key) * 8 . "-cbc";
$iv = substr(base64_decode($data), 0, 16);
$decoded = openssl_decrypt(substr(base64_decode($data), 16), $method, $key, TRUE, $iv);
return $decoded;
}
Python code :
from Crypto.Cipher import AES
from Crypto import Random
import base64
def encrypt(data, key):
pkcs7pad = lambda data: data + chr(16-(len(data)%16)).encode() * (16-(len(data)%16))
iv = Random.new().read(16)
cipher = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
encoded = base64.b64encode(iv + cipher.encrypt(pkcs7pad(data)))
return encoded
def decrypt(data, key):
pkcs7unpad = lambda data: data[:-ord(data[-1:])]
cipher = AES.new(key=key, mode=AES.MODE_CBC, IV=base64.b64decode(data)[:16])
decoded = cipher.decrypt(base64.b64decode(data)[16:])
return pkcs7unpad(decoded)
With the above functions you can encrypt in PHP - decrypt in python , and vice versa
Assuming that the key has a valid size (16, 24 or 32 bytes)
I am looking for two fitting code snippets to encode some text with python, which is to be decoded in php. I am looking for something "easy" and compatible, and I have not much encryption experience myself.
If someone could give a working example that would be great!
python encrypt
from Crypto.Cipher import AES
import base64
import os
# the block size for the cipher object; must be 16, 24, or 32 for AES
BLOCK_SIZE = 32
BLOCK_SZ = 14
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
secret = "332SECRETabc1234"
iv = "HELLOWORLD123456"
cipher=AES.new(key=secret,mode=AES.MODE_CBC,IV=iv)
my_text_to_encode = "password"
encoded = EncodeAES(cipher, my_text_to_encode)
print 'Encrypted string:', encoded
php decrypt (note the encoded text is just copy/pasted from python print above)
<?php
$enc = "x3OZjCAL944N/awRHSrmRBy9P4VLTptbkFdEl2Ao8gk=";
$secret = "332SECRETabc1234"; // same secret as python
$iv="HELLOWORLD123456"; // same iv as python
$padding = "{"; //same padding as python
function decrypt_data($data, $iv, $key) {
$cypher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
if(is_null($iv)) {
$ivlen = mcrypt_enc_get_iv_size($cypher);
$iv = substr($data, 0, $ivlen);
$data = substr($data, $ivlen);
}
// initialize encryption handle
if (mcrypt_generic_init($cypher, $key, $iv) != -1) {
// decrypt
$decrypted = mdecrypt_generic($cypher, $data);
// clean up
mcrypt_generic_deinit($cypher);
mcrypt_module_close($cypher);
return $decrypted;
}
return false;
}
$res = decrypt_data(base64_decode($enc), $iv, $secret);
print rtrim($res,$padding);
?>
You can use python-mcrypt for python. In php you have a corresponding decrypting function to mcrypt. I hope thedocumentation in php is clear enough to show how to decrypt for mcrypt. Good luck.
I need to communicate with a asp platform that uses the aspEncrypt from persits.
Can anyone provide an example how to decode a string with PHP and mcrypt that was created via the aspEncrypt routines.
An example page of aspEncrypt is available at this link:
http://support.persits.com/encrypt/demo_text.asp
So if I use the text "Test" and the key "test" it provides an base64 encoded string. I need a php example that convert this encoded string back to the text "Test" with usage of key "test".
This is how i finally solved it:
Expectation:
Key is known
IV is known (in my case, first 32 characters of encoded data)
Encrypted Text is known
In my special case all received data hex encoded.
This means IV and encrypted text.
function decrypt($sString, $sIv, $sKey, $iCipherAlg) {
$sDecrypted = mcrypt_decrypt($iCipherAlg, $sKey, $sString, MCRYPT_MODE_CBC, $sIv);
return trim($sDecrypted);
}
function hex2bin($sData) {
$iLen = strlen($sData);
$sNewData = '';
for($iCount=0;$iCount<$iLen;$iCount+=2) {
$sNewData .= pack("C",hexdec(substr($sData,$iCount,2)));
}
return $sNewData;
}
$sKey = 'this is my key';
// first 32 chars are IV
$sIv = hex2bin(substr($sEncodedData, 0, 32));
$sEncodedData = substr($sEncodedData, 32);
$sEncodedRaw = hex2bin($sEncodedData);
$sDecrypted = decrypt($sEncodedRaw, $sIv, $sKey, MCRYPT_RIJNDAEL_128);
A corresponding encryption works like that:
$sIv = mcrypt_create_iv(mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
$sKey = 'this is my key';
$sContent = 'a lot of content';
$sEncrypted = bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $sKey, $sContent, MCRYPT_MODE_CBC, $sIv));
$sFullEncodedText = bin2hex($sIv) . $sEncrypted;
I encountered an old VBScript project which was encrypting strings with AspEncrypt like this:
Function EncryptString(data, base64Iv)
Set CM = Server.CreateObject("Persits.CryptoManager")
Set Context = CM.OpenContextEx("Microsoft Enhanced RSA and AES Cryptographic Provider", "", True)
Set Key = Context.GenerateKeyFromPassword("secret encryption password", calgSHA512, calgAES256)
Set IVblob = CM.CreateBlob
IVblob.Base64 = base64Iv
Key.SetIV IVblob
Set Blob = Key.EncryptText(data)
EncryptString = Blob.Base64 & ":" & base64Iv
End Function
Based on the arguments to GenerateKeyFromPassword, a binary key is created by hashing the password with SHA-512, and data is encrypted with the aes-256-cbc algorithm. The random Base64-encoded initialization vector is appended to the encrypted value after a colon.
This can be replicated in PHP using the OpenSSL extension:
class Aes256Cbc
{
private string $algo = 'aes-256-cbc';
private string $key;
private int $ivLen;
public function __construct(string $password)
{
$this->key = hash('sha512', $password, true);
$this->ivLen = openssl_cipher_iv_length($this->algo);
}
public function encrypt(string $data): string
{
$iv = random_bytes($this->ivLen);
$ciphertext = openssl_encrypt($data, $this->algo, $this->key, OPENSSL_RAW_DATA, $iv);
return base64_encode($ciphertext) . ':' . base64_encode($iv);
}
public function decrypt(string $encrypted): string
{
[$ctPart, $ivPart] = explode(':', $encrypted);
$iv = base64_decode($ivPart);
$ciphertext = base64_decode($ctPart);
return openssl_decrypt($ciphertext, $this->algo, $this->key, OPENSSL_RAW_DATA, $iv);
}
}
Example usage:
$aes = new Aes256Cbc("secret encryption password");
$decrypted = $aes->decrypt($someValue);
Note: if AspEncrypt was used without setting an initialization vector, the IV will be sequence of null bytes. This fixed IV could be generated in the above PHP class as follows:
$iv = str_repeat("\0", $this->ivLen);
It depends on which cipher it uses, take a look at mcrypt as long as you know the cipher and key it should be easy to decrypt.
If you know the cipher and mode used by the encryption, the function mcrypt_decrypt can decrypt it.
http://uk3.php.net/manual/en/function.mcrypt-decrypt.php