Im trying to convert some ruby code that encrypts data with AES 265 in CBC mode to php but its not working, the converted php code returns a null string. Here is what i have:
Ruby:
require 'openssl'
module AESCrypt
def self.encrypt(message, password)
Base64.encode64(self.encrypt_data(message.to_s.strip, self.key_digest(password), nil, "AES-256-CBC"))
end
def self.decrypt(message, password)
base64_decoded = Base64.decode64(message.to_s.strip)
self.decrypt_data(base64_decoded, self.key_digest(password), nil, "AES-256-CBC")
end
def self.key_digest(password)
OpenSSL::Digest::SHA256.new(password).digest
end
def self.decrypt_data(encrypted_data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.decrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(encrypted_data) + aes.final
end
def self.encrypt_data(data, key, iv, cipher_type)
aes = OpenSSL::Cipher::Cipher.new(cipher_type)
aes.encrypt
aes.key = key
aes.iv = iv if iv != nil
aes.update(data) + aes.final
end
end
And the php code:
echo base64_encode($encrypted_data = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, hash('sha256', 'p4ssw0rd'), 'hey', MCRYPT_MODE_CBC));
have a look at
https://github.com/nirnanaaa/xlix/blob/master/xlix/lib/Xlix/Bundle/Crypto/Aes/TwoLevel.php
I wrote this a while ago for my crypto functions based on a GIST I have found on the web
Related
I am currently working on encryption and decryption. I have encrypted my api key using https://medium.com/#amitasaurus/encrypting-decrypting-a-string-with-aes-js-1d9efa4d66d7 like below
var api_key = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
var d = new Date();
var n = d.getTime();
var final_key = api_key+'/'+n;
var encrypted = CryptoJS.AES.encrypt('encryption', final_key);
var encrypted_key = encrypted.toString();
and passed the encrypted key to the server side. I used
<?php
$key = pack("H*", "0123456789abcdef0123456789abcdef");
$iv = pack("H*", "abcdef9876543210abcdef9876543210");
$encrypted = base64_decode('U2FsdGVkX19gHSzwsrc5H9K6rqDYr2E8oYoVNSp8INU=');
$decrypt_string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
echo $decrypt_string;
?>
for decrypting the encrypted string. When i print decrypted string , it is like this ���9Һ��دa<��5*| աT�;��놻��V�[�}��ID-�}��硵�
Any suggestions to print as decoded string?
mcryptdefaults to zero padding. That means that, no matter what kind of ciphertext and key combination you are using, that the unpadding will not fail. Instead, it just returns invalid, randomized plaintext.
CryptoJS by default uses OpenSSL key derivation from a given password. Your decryption will return randomized plaintext as long as you cannot mimic the final AES key value that is generated by CryptoJS.
Modern modes such as GCM include an authentication tag with the ciphertext so that the validity of the ciphertext / key combination is ensured, or a verification error will be generated. Note that CBC mode is absolutely not secure when directly used for transport mode security.
After encrypting with PHP 7, whenever I decrypt using Python 2, first 16 characters are gone..
IE:
before encryption:
THIS IS A TEXT THAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE).
after decryption:
HAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE)
PHP to encrypt
$method = 'AES-256-CBC';
$key = hash('sha256', 'password', true);
$ivlen = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivlen);
$enc = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
$encrypted = base64_encode($enc);
Python to decrypt
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
class SomeClass(object):
def __init__(self, key):
self.bs = 32
self.key = hashlib.sha256(key.encode()).digest()
def decrypt(self, enc):
enc = base64.b64decode(enc)
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')
def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)
#staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
so if I encrypt this in PHP (decrypting it on PHP works fine)
THIS IS A TEXT THAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE).
but i decrpyt it on Python, the result is missing first 16 parts of string
HAT NEED TO BE ENCRYPTED AND ALSO NEED TO BE DECRYPT USING PYTHON (OR OTHER LANGAUGE)
I have a Python application and PHP website that communicate over some specific network layer sending messages. My task is to send all messages AES-encrypted and base64-encoded using that channel. Encryption key is pre-shared for both parties manually.
In my PHP, i used this code to create final message text called $payload:
$key = substr('abdsbfuibewuiuizasbfeuiwhfashgfhj56urfgh56rt7856rh', 0, 32);
$magic = 'THISISANENCRYPTEDMESSAGE';
function crypted($data) {
global $key, $magic;
// serialize
$payload = json_encode($data);
// encrypt and get base64 string with padding (==):
$payload = #openssl_encrypt($payload, 'AES-192-CBC', $key);
// prepend with magic
$payload = $magic.$payload;
return $payload;
}
And i receive such message in my Python application, stripping the magic, getting base64 byte data. The problem that i cannot find a sample to make compatible AES cipher to decode this message.
Key and "Magic" are only values pre-shared and known on both sides, is this correct? Do i need an IV?
Here is Python solution from SO that does not work for my crypted messages.
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
class AESCipher:
class InvalidBlockSizeError(Exception):
"""Raised for invalid block sizes"""
pass
def __init__(self, key):
self.key = key
self.iv = bytes(key[0:16], 'utf-8')
def __pad(self, text):
text_length = len(text)
amount_to_pad = AES.block_size - (text_length % AES.block_size)
if amount_to_pad == 0:
amount_to_pad = AES.block_size
pad = chr(amount_to_pad)
return text + pad * amount_to_pad
def __unpad(self, text):
pad = ord(text[-1])
return text[:-pad]
def encrypt( self, raw ):
raw = self.__pad(raw)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
return b64encode(cipher.encrypt(raw))
def decrypt( self, enc ):
enc = b64decode(enc)
cipher = AES.new(self.key, AES.MODE_CBC, self.iv )
r = cipher.decrypt(enc) # type: bytes
return self.__unpad(r.decode("utf-8", errors='strict'))
It fails on last line with decode problem. "ignore" decoding mode returns empty string.
# with magic: "THISISANENCRYPTEDMESSAGE8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI="
# contains: {'test': 'hello world'}
payload = '8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI='
aes = AESCipher('abdsbfuibewuiuizasbfeuiwhfashgfh')
print(aes.decrypt(payload))
Raises:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "../test.py", line 36, in decrypt
return self.__unpad(cipher.decrypt(enc).decode("utf-8"))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9e in position 0: invalid start byte
What am i missing?
You are using Cipher Block Chaining, but didn't pass in an IV to openssl_encrypt(); this means the IV is then 16 times the NUL byte. But your Python code uses the key as the IV instead, so that'll produce a different decryption result altogether.
Next, you picked AES-192-CBC, not AES-256-CBC, so only 192 bits are used for the key. 192 bits == 24 bytes, and not 32 as you thought.
You also need to drop the __unpad() call entirely, there is no padding in your encrypted data, removing data from the end before decrypting will only lead to the decryption failing.
So to decrypt on the Python side, use 24 characters for the key, give an IV that is 16 times \x00, and pass in all data you decoded from Base64:
>>> from Crypto.Cipher import AES
>>> from base64 import b64decode
>>> key = 'abdsbfuibewuiuizasbfeuiwhfashgfh'[:24]
>>> key
'abdsbfuibewuiuizasbfeuiw'
>>> payload = '8wZVLZpm7UNyUf26Kds9Gwl2TBsPRo3zYDFQ59405wI='
>>> enc = b64decode(payload)
>>> cipher = AES.new(key, AES.MODE_CBC, '\x00' * 16)
>>> cipher.decrypt(enc)
b'{"test":"hello world"}\n\n\n\n\n\n\n\n\n\n'
If you wanted to use the full 32 characters of the key, use AES-256-CBC instead.
You really want to produce a random IV, so that someone snooping on the traffic can't determine patterns (where the same payload produces the same encrypted message each time). Generate the IV, include it in the data you send, and extract it on the Python side to pass to the AES.new() function.
I'm trying to encrypt data in Delphi then decrypt it in PHP, but the Delphi output differs from what I get with PHP, so I am first testing whether both the PHP and Delphi output from encrypting a string is the same:
Delphi encryption, using DCPcrypt:
Uses DCPcrypt2, DCPblockciphers, DCPrijndael;
var Cipher : TDCP_rijndael;
Data, Key, IV : Ansistring;
begin
key := '12345678901234567890123456789012';
iv := '1234567890123456';
Data := 'thisis128bitstxt';
Cipher := TDCP_rijndael.Create(nil);
Cipher.Init(Key[1],128,#IV[1]);
Cipher.EncryptCBC(Data[1],Data[1],Length(Data));
Cipher.Free;
With TMemoryStream.Create do begin
Write(Data[1],Length(Data));
SaveToFile('z:\delphi');
Free;
end;
Now the code in PHP:
<?php
$source = "thisis128bitstxt"; //128-bits block
$key = "12345678901234567890123456789012"; // 32
$iv = "1234567890123456"; // 16
$source = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$source,MCRYPT_MODE_CBC,$iv);
$out = fopen ('php', 'w');
fwrite ($out, $source);
fclose ($out);
?>
Results:
What's wrong?
Change:
$source = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,$key,$source,MCRYPT_MODE_CBC,$iv);
to:
$source = mcrypt_cbc(MCRYPT_RIJNDAEL_128,$KEYkey$source,MCRYPT_ENCRYPT,$iv);
I have a function in PHP that encrypts text as follows:
function encrypt($text)
{
$Key = "MyKey";
return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Key, $text, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
}
How do I decrypt these values in Python?
To decrypt this form of encryption, you will need to get a version of Rijndael. One can be found here. Then you will need to simulate the key and text padding used in the PHP Mcrypt module. They add '\0' to pad out the text and key to the correct size. They are using a 256 bit block size and the key size used with the key you give is 128 (it may increase if you give it a bigger key). Unfortunately, the Python implementation I've linked to only encodes a single block at a time. I've created python functions which simulate the encryption (for testing) and decryption in Python
import rijndael
import base64
KEY_SIZE = 16
BLOCK_SIZE = 32
def encrypt(key, plaintext):
padded_key = key.ljust(KEY_SIZE, '\0')
padded_text = plaintext + (BLOCK_SIZE - len(plaintext) % BLOCK_SIZE) * '\0'
# could also be one of
#if len(plaintext) % BLOCK_SIZE != 0:
# padded_text = plaintext.ljust((len(plaintext) / BLOCK_SIZE) + 1 * BLOCKSIZE), '\0')
# -OR-
#padded_text = plaintext.ljust((len(plaintext) + (BLOCK_SIZE - len(plaintext) % BLOCK_SIZE)), '\0')
r = rijndael.rijndael(padded_key, BLOCK_SIZE)
ciphertext = ''
for start in range(0, len(padded_text), BLOCK_SIZE):
ciphertext += r.encrypt(padded_text[start:start+BLOCK_SIZE])
encoded = base64.b64encode(ciphertext)
return encoded
def decrypt(key, encoded):
padded_key = key.ljust(KEY_SIZE, '\0')
ciphertext = base64.b64decode(encoded)
r = rijndael.rijndael(padded_key, BLOCK_SIZE)
padded_text = ''
for start in range(0, len(ciphertext), BLOCK_SIZE):
padded_text += r.decrypt(ciphertext[start:start+BLOCK_SIZE])
plaintext = padded_text.split('\x00', 1)[0]
return plaintext
This can be used as follows:
key = 'MyKey'
text = 'test'
encoded = encrypt(key, text)
print repr(encoded)
# prints 'I+KlvwIK2e690lPLDQMMUf5kfZmdZRIexYJp1SLWRJY='
decoded = decrypt(key, encoded)
print repr(decoded)
# prints 'test'
For comparison, here is the output from PHP with the same text:
$ php -a
Interactive shell
php > $key = 'MyKey';
php > $text = 'test';
php > $output = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB);
php > $encoded = base64_encode($output);
php > echo $encoded;
I+KlvwIK2e690lPLDQMMUf5kfZmdZRIexYJp1SLWRJY=
If you're willing to use MCRYPT_RIJNDAEL_128 rather than 256 on the PHP side, this is as simple as:
from Crypto.Cipher import AES
import base64
key="MyKey"
def decrypt(text)
cipher=AES.new(key)
return cipher.decrypt(base64.b64decode(text))
Although the answer from #101100 was a good one at the time, it's no longer viable. The reference is now a broken link, and the code would only run on older Pythons (<3).
Instead, the pprp project seems to fill the void nicely. On Python 2 or Python 3, just pip install pprp, then:
import pprp
import base64
KEY_SIZE = 16
BLOCK_SIZE = 32
def encrypt(key, plaintext):
key = key.encode('ascii')
plaintext = plaintext.encode('utf-8')
padded_key = key.ljust(KEY_SIZE, b'\0')
sg = pprp.data_source_gen(plaintext, block_size=BLOCK_SIZE)
eg = pprp.rjindael_encrypt_gen(padded_key, sg, block_size=BLOCK_SIZE)
ciphertext = pprp.encrypt_sink(eg)
encoded = base64.b64encode(ciphertext)
return encoded.decode('ascii')
def decrypt(key, encoded):
key = key.encode('ascii')
padded_key = key.ljust(KEY_SIZE, b'\0')
ciphertext = base64.b64decode(encoded.encode('ascii'))
sg = pprp.data_source_gen(ciphertext, block_size=BLOCK_SIZE)
dg = pprp.rjindael_decrypt_gen(padded_key, sg, block_size=BLOCK_SIZE)
return pprp.decrypt_sink(dg).decode('utf-8')
key = 'MyKey'
text = 'test'
encoded = encrypt(key, text)
print(repr(encoded))
# prints 'ju0pt5Y63Vj4qiViL4VL83Wjgirq4QsGDkj+tDcNcrw='
decoded = decrypt(key, encoded)
print(repr(decoded))
# prints 'test'
I'm a little dismayed that the ciphertext comes out different than what you see with 101100's answer. I have, however, used this technique to successfully decrypt data encrypted in PHP as described in the OP.