Encrypting to AES256 CBC in PHP with a 40 character key - php

I'm having a bit of trouble trying to wrap my head around mcrypt_cbc. I have a 40 character key (OAuth key - doing the encryption/signing manually instead of using an OAuth library) to use, but I'm not sure how I should be using this.
End result needs to be a json pair encrypted to SHA256 CBC, and that encoded in base64. What I have is..
$key = '123456789012345678901234567890123456789012345678901234567890';
$pair = 'user:pass';
$encrypted = base64_encode(mcrypt_cbc(MCRYPT_RIJNDAEL_128,$key,$pair,MCRYPT_ENCRYPT,""));
I'm sure this isn't right, but I have the following questions:
If I read the PHP documentation correctly, RIJNDAEL_128 can used for for SHA256 - is this correct?
For the Key and IV.. I have seen the following as well:
$encrypted = base64_encode(mcrypt_cbc(MCRYPT_RIJNDAEL_128,substr($key,0,32),$pair,MCRYPT_ENCRYPT,substr($key,32,16)));
I'm a bit puzzled - the first 32 characters are used for the key, and the remaining 16 are for an IV? Is the IV always just a section of the key? And how would this be done for 40 characters? Using an IV of...
substr($key,32,8)
results in
The IV parameter must be as long as the blocksize
The below Ruby code is an example from the API I am referencing, but I have zero knowledge when it comes to Ruby
def encrypt_aes(data)
sha_key = Digest::SHA2.digest(#secret_key)
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
aes.encrypt
aes.key = sha_key
aes.iv = "\x00" * 16
encrypted = aes.update(data) + aes.final
Base64.encode64(encrypted)
end
Any guidance at all would be greatly appreciated. I was fairly experienced in PHP back in the day, but cryptography was always my weakest area in general. The most experience I have in it is using the Botan library in c++, and that alone was all thanks to modifying various snippets.

For future reference for anyone else working on this, this is how I got it working:
$CREDENTIALS = 'user:pass';
$SECRET_KEY = 'xxxx'; //40 character secret key
$key256 = #pack("H*" , #hash('sha256', $SECRET_KEY));
$encrypted_string = #base64_encode( openssl_encrypt( $CREDENTIALS, "aes-256-cbc", $key256, true) );
Once that was finished, I was able to add that into the URL a 'credentials' parameter, along with other necessary parameters, generate a signature via base64 sha256, then send the signature w/ the parameters out to the api server.

Related

Nodejs how to implement OpenSSL AES-CBC encryption (from PHP)?

I am currently working on translating an encryption algorithm from PHP to Typescript, to use in a very specific API that requires the posted data to be encrypted with the API key and Secret. Here is the provided example of how to correctly encrypt data in PHP for use with the API (the way of implementing the key and IV can't be changed):
$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);
$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);
return $completedEncryption;
In the above code, the only thing the base64Url_Encode function does is convert the binary data to a valid Base64URL string.
And now the code as I have implemented it inside Typescript:
import { createHash, createCipheriv } from 'node:crypto'
const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)
// Generate key
/*
Because the OpenSSL function in PHP automatically pads the string with /null chars,
do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key,
instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)
// Create Cipher
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');
// Return base64URL string
return Buffer.from(encrypted).toString('base64url');
The above Typescript code only does NOT give the same output as the PHP code given earlier. I have looked into the original OpenSSL code, made sure that the padding algorithms are matching (pcks5 and pcks7) and checked if every input Buffer had the same byte length as the input inside PHP. I am currently thinking if it is some kind of binary malform that is causing the data to change inside Javascript?
I hope some expert can help me out with this question. Maybe I have overlooked something. Thanks in advance.
The stupidity is in the md5 function in PHP, which defaults to hexadecimal output instead of binary output:
md5(string $string, bool $binary = false): string
This is also why the code doesn't complain about the key (constructed from the MD5 hash) is being too small, it is fed 32 bytes after ASCII or UTF8 encoding, instead of the 16 bytes you'd use for AES-128.
Apparently it is using lowercase encoding, although not even that has been specified. You can indicate the encoding for NodeJS as well, see the documentation of the digest method. It also seems to be using lowercase, although I cannot directly find the exact specification of the encoding either.
Once you have completed your assignment, please try and remove the code ASAP, as you should never calculate the IV from the key; they key and IV combination should be unique, so the above code is not IND-CPA secure if the key is reused.
In case you are wondering why it is so stupid: the output of MD5 has been specified in standards, and is binary. Furthermore, it is impossible from the function to see what it is doing, you have to lookup the code. It will also work very badly if you're doing a compare; even if you are comparing strings then it is easy to use upper instead of lowercase (and both are equally valid, uppercase hex is actually easier to read for humans as we focus on the top part of letters more for some reason or other).
Basically it takes the principle of least surprise and tosses it out of the window. The encoding of the output could be made optimal instead, the NodeJS implementation does this correctly.

Convert .net aes256 encryption code into php

The code below is a simple .NET snippet, having test on the input it returns p+cTm2VODfvQnreAl02wUQ== as an output.
Dim aesEncryptObj As New System.Security.Cryptography.RijndaelManaged()
Dim encoder As System.Text.ASCIIEncoding = New System.Text.ASCIIEncoding()
Dim tempKey As Byte() = encoder.GetBytes("00000011111111111111111111111111")
aesEncryptObj.Key = tempKey
aesEncryptObj.BlockSize = 128
aesEncryptObj.Mode = System.Security.Cryptography.CipherMode.ECB
aesEncryptObj.Padding = System.Security.Cryptography.PaddingMode.PKCS7
aesEncryptObj.GenerateIV()
Dim EncryptedBytes As Byte()
Dim encryptor As System.Security.Cryptography.ICryptoTransform = aesEncryptObj.CreateEncryptor(aesEncryptObj.Key, aesEncryptObj.IV)
Using msEncrypt As New System.IO.MemoryStream()
Using csEncrypt As New System.Security.Cryptography.CryptoStream(msEncrypt, encryptor, System.Security.Cryptography.CryptoStreamMode.Write)
Using swEncrypt As New System.IO.StreamWriter(csEncrypt)
swEncrypt.Write(txtInput.Text)
End Using
EncryptedBytes = msEncrypt.ToArray()
End Using
End Using
txtOutput.Text = Convert.ToBase64String(EncryptedBytes)
Now, here is the PHP code:
const ENCRYPT_METHOD = 'aes-256-ecb';
$aesKey = pack('H*', '00000011111111111111111111111111');
$ivSize = openssl_cipher_iv_length(ENCRYPT_METHOD);
$plainText = "test";
$iv = openssl_random_pseudo_bytes($ivSize);
$cipherText = openssl_encrypt(
$plainText,
ENCRYPT_METHOD,
$aesKey,
OPENSSL_RAW_DATA,
$iv
);
$encryptedText = $iv . $cipherText;
echo base64_encode($encryptedText);
It returns 1W3UvYVNKWEoFrpPZPd+Qw== which differs from the .NET one. I've tried both aes-256-ecb and aes-128-ecb and the result is always different from the .NET's one.
As far as I know, openssl_encrypt do the PKCS7 padding by default, is that right? Can you see the reason why PHP is giving different result?
Your code isn't working because:
GetBytes in .NET returns you the byte values of that string. That is, you are going to get a byte array with a length of 32 (AES-256). However, pack in PHP with H* decodes a hex string, which is going to give you a key of length 16 (AES-128). This whole time you've been encrypting with not only two different keys, but two different key sizes.
Fixing the above will make your code work, but it will be far from actually secure and should not be used. If you want to make your code secure, you need to:
Stop using ECB mode. Use GCM mode if you can, CBC otherwise. You are most likely going to end up using CBC. If you do, you need to prepend the IV to your ciphertext so that the decrypting party can retrieve it and use it when decrypting. IVs don't need to be secret, they just need to be random, so your use of OpenSSL random bytes and the GenerateIV method is good.
Since you are likely going to be using CBC mode, you will need to apply an HMAC to ensure integrity. Otherwise anyone can modify your ciphertext and you won't know. When applying an HMAC, always encrypt-then-mac. Remember to include your IV before applying the HMAC.
Lastly, and I don't think you actually meant to do this, but never use the ASCII values of a string as a key. If you want to create a key from a passphrase, you should apply a KDF. PBKDF2 is probably the best suited and has implementations in both PHP and .NET.

IV too long in PHP mcrypt_generic_init

I am working on a project where all the data from the web services is being encrypted using Triple DES Encryption. In my specific case, I am receiving a query string from a URL that has been encrypted. The web service provider has given me two values for decryption: the encryption Key_192 and the initialization vector IV_192. Both these keys are 24 characters long.
When I attempt to decrypt the query string I have received in PHP, I am using the mcrypt library. When initializing the generic decrypt methods, part of my function is:
$key = "XXXXXXXXXXXXXXXXXXXXXXXX";
$iv = "YYYYYYYYYYYYYYYYYYYYYYYY";
$cipher = mcrypt_module_open(MCRYPT_3DES, '', 'cbc', '');
mcrypt_generic_init($cipher, $key, $iv);
$result = rtrim(mdecrypt_generic($cipher, $this->hex2bin($buffer)), "\0");
mcrypt_generic_deinit($cipher);
return $result;`
However, when I execute that portion of my code, I receive the following message:
mcrypt_generic_init(): Iv size incorrect; supplied length: 24, needed: 8
The web services provider was not able to provide any guidance on the error, instead directing me to their VB.NET implementation which has a line like:
Dim cs As CryptoStream = New CryptoStream(ms, cryptoProvider.CreateDecryptor(KEY_192, IV_192), CryptoStreamMode.Read)
where they pass the two keys in directly, similar to the mcrypt_generic_init() function.
I understand that the IV size is dependent upon the cypher method (Triple DES), but am confused as to why I have an IV longer than the function appears to support. How could that be? My experience with this kind of encryption is limited, and I have been unable to decrypt the query string into anything that doesn't look like a field of random characters.
Run mcrypt_enc_get_iv_size() to figure out the required IV size. For Triple DES, it will be 8. mcrypt_generic_init() requires a string of exactly the correct length, so you should use a shorter string (or, to do it on the fly, use substr()).
It turns out that my issue in decrypting was caused by differences in how the mcrypt PHP library and the VB.NET libraries pad strings while encrypting. Also, with regard to the original question, only the first 8 characters of the IV are actually used. The others are discarded. Better description is located here:
http://mishu666.wordpress.com/2007/08/20/problem-and-solve-of-3des-incompatibilities-with-nets-tripledescryptoserviceprovider/

PHP (mcrypt_encrypt) Ruby (AES-256-CBC) Encryption Different Results

I have been trying to encrypt a string in PHP and Ruby using the same key and iv but I always got different results.
Below is the PHP Code
$data = "This string needs to be encrypted";
$key = "1234567887654321abcdefghabcdefgh";
$iv = "1234567887654321abcdefghabcdefgh";
echo $encrypted_data = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
Below is the Ruby Code
data = "This string needs to be encrypted"
key = "1234567887654321abcdefghabcdefgh"
iv = "1234567887654321abcdefghabcdefgh"
aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
aes.encrypt
aes.key = key
aes.iv = iv
encrypted_data = aes.update(data) + aes.final
Could somebody please help me get the same encrypted data in PHP and Ruby? I encrypted some data in PHP and then decrypted in Ruby but didn't get the data back. So I think the issue is PHP and Ruby encryption and decryption mechanism work differently. Please correct me if I am wrong. Thanks
Don't hard code IV's , it is insecure. IVs must be random but can be public , so just use
mcrypt_create_iv and prepend it to the front of the ciphtertext and then extract it before
decrypting
You likely have three problems
MCRYPT_RIJNDAEL_256 is nott AES. AES is a specific version RIJNDAEL that was standardized with a 128 bit block size and either 128 or 256 bit keys. MCRYPT_RIJNDAEL_256 is RIJNDAEL with a 256 bit block size. You want to use MCRYPT_RIJNDAEL_128 which is actually AES. For php, key length is just determined by the length of the key. So just give it a 256 bit ( 32 character) key and you will be fine. Note block size does not effect security really, so don't worry about the deference and just use AES with a 256 bit key: its good enough for the NSA and top secret data.
Padding. AES only takes fixed 128 bit chunks, so you must pad out the text to be a multiple of that size. PHP doesn't really bad and i believe SSL uses pkcs7 padding. Note that even with different padding schemes, for most of them the start of the cipher text should the the same there just may be garbage at the end.
String encoding. AES is defined with bit inputs, in c typically this is a byte array. Ruby and PHP use strings. I'd be willing to bet your string encodings are different.

Can I replicate the exact behaviour of PHP's AES encryption in ruby?

I'm in the process of rebuilding a PHP web app in Ruby on Rails, and would dearly love to avoid forcing all existing users to reset their encrypted passwords. The PHP site uses mcrypt_encrypt with AES-256-ECB, and I can't for the life of me get the same cipher text using ruby's OpenSSL. I can't decrypt them either (which is good in principle) since what's actually stored in the user DB is an MD5 hash of the AES cipher text.
I've read these previous, closely related questions and the very helpful answers:
How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together
Part II: How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together
including the pages referenced there, and if I understand correctly, the PHP and ruby implementations use different padding methods. Since I have to live with how things work on the PHP side, is there any way to force the same padding method on ruby/OpenSSL somehow? I'm using ruby 1.9.2-p180.
Here's the sample code in PHP:
$salt = "12345678901234567890123456789012";
$plain = "password";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$cipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $salt, $plain, MCRYPT_MODE_ECB, $iv);
echo md5($cipher);
Output: 6337137fd88148250fd135a43dbeb84a
and in ruby:
require 'openssl'
salt = "12345678901234567890123456789012"
plain = "password";
c = OpenSSL::Cipher.new("AES-256-ECB")
c.encrypt
c.key = salt
cipher = c.update(plain)
cipher << c.final
puts Digest::MD5.hexdigest(cipher)
Output: 18dee36145c07ab83452aefe2590c391
Actually not in general an openssl solution but maybe it is ok for you to have a working example.
require 'mcrypt'
require 'openssl'
plaintext = 'password'
puts plaintext
key = '12345678901234567890123456789012'
enc = Mcrypt.new(:rijndael_256, :ecb, key, nil, :zeros)
encrypted = enc.encrypt(plaintext)
puts Digest::MD5.hexdigest(encrypted)
I used an additional gem(ruby-mcrypt). Seems to be an issue with openssl. Actually the issue seems to be that Openssl does not support zero padding and uses either no-padding or default-openssl-padding. Due to the fact that you use zero padding in php you must use zero padding also in ruby.
Output on my machine for the php script:
[~/test] ➔ php5 t.php
6337137fd88148250fd135a43dbeb84a
and for the ruby script:
[~/test] ➔ ruby t2.rb
password
6337137fd88148250fd135a43dbeb84a
and my ruby version:
[~/test] ➔ ruby -version
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]
Hope this helps.
if key size is not standard on php side, you need to fill the key with zeros to next valid key size, in order to make ruby side works like this:
php_encrypted = string_encoded_with_php_mcrypt
key = "longerthan16butnot24".to_a.pack('a24')
enc = Mcrypt.new(:rijndael_256, :ecb, key, nil, :zeros)
enc.decrypt(php_encrypted)
In this case next valid key length is 24.
For :rijndael_256 valid key lengths are: 16, 24, 32
You can get more info on algorithms:
Mcrypt.algorithm_info(:rijndael_256
if you can use other encrypt methods, you can try TEA Block Encryption. I have adopted the method across Ruby, JS, ActionScript. It should work with PHP as well. github repo is here

Categories