I'm trying to convert a PHP encryption function to Python. For the sake of the example and making things easier, the iv and relevant data is preset.
$salt = sha1('12345'.'654321');
encrypt('12345678', 'cutekittens12345', $salt);
function encrypt($decrypted, $password, $salt)
{
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
$hexkey = hash('SHA256', $salt . $password, false);
// Build $iv and $iv_base64. We use a block size of 128 bits (AES compliant) and CBC mode. (Note: ECB mode is inadequate as IV is not used.)
#$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$iv = "0000000000000000";
if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22)
return false;
// Encrypt $decrypted and an MD5 of $decrypted using $key. MD5 is fine to use here because it's just to verify successful decryption.
$concatdecrypted = ($decrypted . md5($decrypted));
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $concatdecrypted, MCRYPT_MODE_CBC, $iv));
// We're done!
return $iv_base64 . $encrypted;
}
Which results in
$salt: 6ed52d21d5cc15e76e9879675f4bd0dd51593652
$hexkey: 879522bb98c1f5fb16acd6bf3454b0d4e313e8b71e0aa3cdf5cbf91158dfde71
$iv_base64: MDAwMDAwMDAwMDAwMDAwMA
$concatdecrypted: 1234567825d55ad283aa400af464c76d713c07ad
$encrypted: C3CWJPt2gtmg+id1ySmSazMvvWC7cgDpovJ/tDN0GeuVv9Pf/9+9ZSG+wjl6qD5h
My cleanest attempt at recreating the function is here and:
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
import hashlib
charid = "654321"
apikey = "12345"
vcode = "12345678"
password = "cutekittens12345"
salt = hashlib.sha1((apikey + charid).encode('utf-8')).hexdigest()
key = hashlib.sha256(salt + password).digest()
iv = "0000000000000000"
concatkey = vcode + hashlib.md5(vcode).hexdigest()
AES.key_size=128
encryptor=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
encoded = encryptor.encrypt(concatkey_pad)
encoded = b64encode(encoded)
print 'Encrypted string:', encoded
I can recreate everything except the final result. I've tried a combination of unhex to see if it's something to do with passing raw inputs, but none of the $encrypted results have been a match.
Any help would be appreciated thank you.
I tried M2Crypto, Crypto.Cipher and pyDes. After reading 'Lessons learned implementing AES in PHP using Mcrypt' I had success with Crypto.Cipher after padding the string with ascii 0's to bypass the multiples of 16 issue and the first half of the encoded string was a match to my php function.
concatkey_pad = concatkey.ljust(48, '\0')
AES.key_size=128
encryptor=AES.new(key=key,mode=AES.MODE_CBC,IV=iv)
encoded = encryptor.encrypt(concatkey_pad)
encoded = b64encode(encoded)
print 'Encrypted string:', encoded
Padding concatkey with binary 0's using ljust generated the correct encoded value.
Related
I have an API that requires me to encode data that I send to it through an AES-cipher.
However, the only example code I have been given is Node.js code.
I thought, how hard can it be to reimplement it in PHP as well ?
Pretty hard apparently.
Below you can see both approaches, yet you can also see different results.
Anyone an idea what might be going wrong ?
NODE.js version
var crypto = require('crypto');
var algorithm = 'aes-128-ctr';
function encrypt(text, password) {
const key = Buffer.from(password, "hex").slice(0, 16);
const ivBuffer = Buffer.alloc(16);
const cipher = crypto.createCipheriv(algorithm, key, ivBuffer);
let crypted = cipher.update(text, "utf8", 'hex');
crypted += cipher.final('hex');
console.log(crypted);
}
encrypt('test','ed8f68b144f94c30b8add43276f0fa14');
RESULT : 3522ca23
PHP version
function encrypt($text, $password) {
$iv = "0000000000000000";
$encrypted = openssl_encrypt($text, 'aes-128-ctr', $password, OPENSSL_RAW_DATA, $iv);
return bin2hex($encrypted);
}
echo encrypt('test', 'ed8f68b144f94c30b8add43276f0fa14');
RESULT: 8faa39d2
While browsing the related section (after my post) I came across this one:
C# and PHP have different AES encryption results
As mentioned by t-m-adam above as well, apparently I need to align the iv and password in both examples. In PHP my iv and password were 'regular' strings, where they should have been binary strings of the same length as the cipher’s block size. My iv should (in my case) be 16 zero bytes instead of 16x the 0 character. You can see the difference by doing an echo of the code below:
$iv = "00000000000000000000000000000000";
echo $iv;
echo strlen($iv);
$iv = pack("H*", "00000000000000000000000000000000");
echo $iv;
echo strlen($iv);
Both $iv variables are of length 16 (as requested by AES) , yet the second version is composed of 0-bytes, effectively unprintable.
Without further ado, the end result, working in PHP:
function encrypt($text, $password) {
$iv = pack("H*", "00000000000000000000000000000000");
$password = pack("H*", $password);
$encrypted = openssl_encrypt($text, 'aes-128-ctr', $inputKey, OPENSSL_RAW_DATA, $iv);
return bin2hex($encrypted);
}
echo encrypt('test', 'ed8f68b144f94c30b8add43276f0fa14');
RESULT: 3522ca23
Success !!
Currently I am using openssl_encrypt to encrypt the data and it return base64 value. I have to use AES encryption with salt.
Can any one tell how to implement AES encryption with salt?
Here is the code I use:
function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = 'This is my secret key';
$secret_iv = 'This is my secret iv';
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if ($action == 'encrypt') {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
} else if ($action == 'decrypt') {
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
Always use tested libraries for such purposes. Your encryption is vulnerable and completely insecure because you're not using IV correctly.
Consider using defuse/php-encryption library and get rid of what you've done.
Why is what you've done wrong:
The same IV (initialization vector) is used.
There is no salt in encryption, it's called Initialization Vector and it must be different every time you encrypt - your IV is always the same
When encryption is done, you must deliver the encrypted data and IV - you are not returning IV with encryption result, only the result.
Currently, you are not doing what I outlined and that's why you should invest your time into using a library that takes care of encryption so you don't roll out your own, insecure implementation. I'm deliberately not posting the code required for this encryption to work from fear that someone will use it, instead of library that I linked. Always use libraries made by other people if you have no idea what you're doing.
In case someone needs it, and yes you can add salt using openssl_pbkdf2
$ciphertext_b64 = "";
$plaintext = "hello alice";
$password = "XhcwO1NNI1Xi43EVAtVPS1vknOGgsgIu16OmUAtzlGoHtaPYWwLqxxAEHcBbocWiaYYtBSlgvkn5rVBu";
$salt = "CK4OGOAtec0zgbNoCK4OGOAtec0zgbNoCK4OGOAtec0zgbNoCK4OGOAtec0zgbNo";
$iv = "LQjFLCU3sAVplBC3";
$iterations = 1000;
$keyLength = 32;
$prepared_key = openssl_pbkdf2($password, $salt, $keyLength, $iterations, "sha256");
$ciphertext_b64 = base64_encode(openssl_encrypt($plaintext,"AES-256-CBC", $prepared_key,OPENSSL_RAW_DATA, $iv));
echo $ciphertext_b64 . "<br/>";
$plaintext = openssl_decrypt(base64_decode($ciphertext_b64),"AES-256-CBC", $prepared_key,OPENSSL_RAW_DATA, $iv);
echo $plaintext . "<br/>";
Hello i'm using Mcrypt to obfuscate some values i'm sending via mail.
When i encrypt the value on my local site, and decrypt it it works ok in every attempt, i mail the value, but when i link back to my site, and try to decrypt it in another page, it works sometimes only.
I'm kinda stuck and dunno why. I'm not so familiar with crypt functions.
This is the code im using for encrypt
function encrypt($decrypted, $password, $salt='!kQm*fF3pXe1Kbm%9') {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Build $iv and $iv_base64. We use a block size of 128 bits (AES compliant) and CBC mode. (Note: ECB mode is inadequate as IV is not used.)
srand(); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) return false;
// Encrypt $decrypted and an MD5 of $decrypted using $key. MD5 is fine to use here because it's just to verify successful decryption.
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $decrypted . md5($decrypted), MCRYPT_MODE_CBC, $iv));
// We're done!
return $iv_base64 . $encrypted;
}
This is the code i'm using for decrypt
function decrypt($encrypted, $password, $salt='!kQm*fF3pXe1Kbm%9') {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Retrieve $iv which is the first 22 characters plus ==, base64_decoded.
$iv = base64_decode(substr($encrypted, 0, 22) . '==');
// Remove $iv from $encrypted.
$encrypted = substr($encrypted, 22);
// Decrypt the data. rtrim won't corrupt the data because the last 32 characters are the md5 hash; thus any \0 character has to be padding.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0\4");
// Retrieve $hash which is the last 32 characters of $decrypted.
$hash = substr($decrypted, -32);
// Remove the last 32 characters from $decrypted.
$decrypted = substr($decrypted, 0, -32);
// Integrity check. If this fails, either the data is corrupted, or the password/salt was incorrect.
if (md5($decrypted) != $hash) return false;
// Yay!
return $decrypted;
}
The $password and $salt variables are being packed using
pack("H*", $string);
After the first failed attempts, i started using urlencode and urldecode for the values on the URL but still the same issue persists.
What i'm doing wrong? i'm really stuck here
Thanks
Your encoded string is being sent with plus (+) signs, wich are being interpreted on the url as blank spaces, you can encode the URL or use str_replace to change empty spaces on the string for plus's sign
Such.
$encrypted_string= "random1234string with blank space";
$empty = array(" ");
$plus = array("+");
$new_encrypted_string = str_replace($empty, $plus, $encrypted_string);
Outputs: "random1234string+with+blank+space"
I'm having totally confounding encryption/decryption results. I have two different strings that I need to encode and later decode, the encrypted string being stored in a MySQL database in between. The first of these strings comes out just fine. However, the second one always returns from decryption with the value FALSE. I've stripped out all of the non-essential factors, and am directly passing a plaintext value of "test" to both encryption routines. Again, the first one returns correctly (as "test") the second one returns as "false").
I'm banging my head against the wall trying to figure out what I am doing wrong. I'm using the same password and the same salt in both files. How is it possible that one will work but the second won't???
One clue: if I put this code into a single php file and bypass the database, it all works just fine. Not sure what to make of this, but it's interesting at least.
Here is the code. The encryption/decryption routine comes from a user post on the php site for mcrypt. Can anyone see it? It's probably something stupid.
setValues.php
$encrypted_email_pw = encrypt("test", $password);
$encrypted_default_pw = encrypt("test", $password);
$sql = "UPDATE Settings
SET email_password='$encrypted_email_pw',
default_pw='$encrypted_default_pw'
WHERE id='$id'";
$result = mysql_query($sql);
getValues.php
$sql = "SELECT * FROM Settings";
$result = mysql_query($sql);
$row = mysql_fetch_array($result); //there is only one row in this table
$decrypted_email_pw = decrypt($row['email_password'], $password);
$decrypted_default_pw = decrypt($row['default_pw'], $password);
echo $decrypted_email_pw . " | " . $decrypted_default_pw;
//output: test | false
die();
crypto.php
<?php
function encrypt($decrypted, $password, $salt='6rVDB?zKe6batB+k') {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Build $iv and $iv_base64.
// We use a block size of 128 bits (AES compliant) and CBC mode.
// (Note: ECB mode is inadequate as IV is not used.)
srand(); $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
if (strlen($iv_base64 = rtrim(base64_encode($iv), '=')) != 22) return false;
// Encrypt $decrypted and an MD5 of $decrypted using $key.
// MD5 is fine to use here because it's just to verify successful decryption.
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $decrypted . md5($decrypted), MCRYPT_MODE_CBC, $iv));
// We're done!
return $iv_base64 . $encrypted;
}
function decrypt($encrypted, $password, $salt='6rVDB?zKe6batB+k') {
// Build a 256-bit $key which is a SHA256 hash of $salt and $password.
$key = hash('SHA256', $salt . $password, true);
// Retrieve $iv which is the first 22 characters plus ==, base64_decoded.
$iv = base64_decode(substr($encrypted, 0, 22) . '==');
// Remove $iv from $encrypted.
$encrypted = substr($encrypted, 22);
// Decrypt the data.
// rtrim won't corrupt the data because the last 32 characters are the md5 hash;
// thus any \0 character has to be padding.
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($encrypted), MCRYPT_MODE_CBC, $iv), "\0\4");
// Retrieve $hash which is the last 32 characters of $decrypted.
$hash = substr($decrypted, -32);
// Remove the last 32 characters from $decrypted.
$decrypted = substr($decrypted, 0, -32);
// Integrity check. If this fails, either the data is corrupted, or the password/salt was incorrect.
if (md5($decrypted) != $hash) return false;
return $decrypted;
}
?>
Have you checked the two columns in the Settings table? Do they have the same data types?
And are you sure the encrypt() and decrypt() methods work correctly?
After you get this working, you should consider using a random generated salt for each password and store the salt in the table together with the password.
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