PHP OpenSSL encrypt with DES-ECB algorithm unexpected output - php

I've tried to encrypt the value with DES-ECB via PHP 7.4 (Laravel) and OpenSSL, and I try the online tool https://emvlab.org/descalc/ to encrypt value, the result was the expected.
key: 96187BBAB2BD19ACF899B74FB4E37972
Input Data: F01DCCE40F8C365ADE0A7DC03BC11DDE
The encrypted data is 355A627E977D8ECF4953C98D801E472F on online tool.
https://emvlab.org/descalc/?key=96187BBAB2BD19ACF899B74FB4E37972&iv=0000000000000000&input=f01dcce40f8c365ade0a7dc03bc11dde&mode=ecb&action=Encrypt&output=D5131A2B35766D371420F679F15969FF
When the next code written in PHP I can't replicate the result:
$data = "F01DCCE40F8C365ADE0A7DC03BC11DDE";
$key = "96187BBAB2BD19ACF899B74FB4E37972";
$encrypted = openssl_encrypt($data, 'DES-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
echo base64_encode($encrypted); // KV8Vie63pG2ZyjW7U5NRJUwOrbsEt+lnG1hNyS331Yk=
Is there a way to get the same string that the online tool?
Thank you!

What must be considered for the website?
The website allows encryption with DES and TripleDES in 2TDEA variant (double length keys). The algorithm is determined by the key size: For an 8 bytes key DES is used, for a 16 bytes key TripleDES/2TDEA. The supported modes are ECB and CBC.
Key, IV (in case of CBC mode), plaintext and ciphertext have to be entered hex encoded on the website. Zero padding is applied (the variant that does not pad if the plaintext length is already an integer multiple of the DES/TripleDES block size of 8 bytes).
What does this mean for the posted data?
Since the posted hex encoded key 96187BBAB2BD19ACF899B74FB4E37972 has a size of 16 bytes, TripleDES/2TDEA is used (in ECB mode).
The hex encoded plaintext F01DCCE40F8C365ADE0A7DC03BC11DDE has a size of 16 bytes and is therefore not padded. The resulting hex encoded ciphertext 355A627E977D8ECF4953C98D801E472F thus has the same length.
How can the PHP code be fixed?
The posted PHP code gives a different result because DES is used as algorithm (in ECB mode) and also the hex encoding/decoding is missing. Thus to produce the result of the website, the encoding/decoding has to be fixed and DES-EDE-ECB has to be applied as algorithm (not to be confused with DES-EDE3-ECB for 3TDEA/triple length keys):
$data = hex2bin("F01DCCE40F8C365ADE0A7DC03BC11DDE"); // hex decode the 16 bytes data
$key = hex2bin("96187BBAB2BD19ACF899B74FB4E37972"); // hex decode the 16 bytes key key
$encrypted = openssl_encrypt($data, 'DES-EDE-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); // apply DES-EDE-ECB; use OPENSSL_ZERO_PADDING
echo bin2hex($encrypted); // 355a627e977d8ecf4953c98d801e472f // hex encode the 16 bytes ciphertext
with the output:
355a627e977d8ecf4953c98d801e472f
that is equal to the result of the web site, s. here
How can a consistent result be achieved for DES?
The current PHP code applies DES and therefore implicitly truncates the key to 8 bytes by using only the first 8 bytes. This can be easily verified by removing the last 8 bytes of the key. The result does not change.
If the shortened key is applied to the website, the results match again.
$data = hex2bin("F01DCCE40F8C365ADE0A7DC03BC11DDE"); // hex decode the 16 bytes data
$key = hex2bin("96187BBAB2BD19AC"); // hex decode the 8 bytes key
$encrypted = openssl_encrypt($data, 'DES-ECB', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING); // apply DES-ECB; use OPENSSL_ZERO_PADDING
echo bin2hex($encrypted); // 692370230b8176f011760ddc07392a4f // hex encode the 16 bytes ciphertext
with the output:
692370230b8176f011760ddc07392a4f
that is equal to the result of the web site, s. here.

Related

Decrypt file ciphered in bash with AES-256-CBC and PBKDF2 using php

I have a string encrypted using this command :
echo 'hello world' | openssl enc -pass pass:MYPASSWORD -aes-256-cbc -md sha3-256 -pbkdf2 -iter 10000 -out crypt
that is stored in a crypt file
I would like to know how to decrypt this file in php
I understand I need to use a combination of these two functions
openssl_pbkdf2
openssl_decrypt
However I fail to understand
openssl_pbkdf2 requires a salt parameter and key_length
openssl_decrypt requires a iv parameter
how do i provide these 3 parameters ?
Ok as the information were scatered all accross the internet I gather all it here, hoping it will save somebody times in the future
if you just want the code, jump to the end
First: The salt is stored in the output file by openssl
it took me quite some time to understand this fact. As this question state, the format is not based on any standard.
so you need to extract these values from the file, as of openssl 1.1.1 the format is the following
(you can find the exact implementation here: https://github.com/openssl/openssl/blob/f7d2427ac3404ce1ed555bf61885eeb0432b5789/apps/enc.c )
8 bytes containing Salted__
8 bytes containing the salt itself
Second: The IV and KEY are derivated from PASSWORD + SALT using openssl_pbkdf2
The IV is not a random one, uncorrelated from anything. The IV is derivated from the password
openssl_pbkdf2 returns 1 string, but this is BOTH the IV and the KEY concatenated (it's not explained in the php documentation) but you can see it here in the c code
The code
Once you get this it's actually straitghforward
read the file
extract the salt
give the password + salt to openssl_pbkdf2 which gives you key + iv
split the key and iv in dedicated variable
feed key and iv to openssl_decrypt along with the ciphered text (stripped from the salt header)
profit
Here's the code
<?php
$content = file_get_contents('crypt');
$salt = mb_substr($content, 8, 8, '8bit');
$derivatedKey = openssl_pbkdf2(
'MYPASSOWRD',
$salt,
// key_length is 48 bytes because
// the key itself is 32 bytes (256 bits, because aes 256)
// and the IV is 16 bytes (returned by openssl_cipher_iv_length)
// so 32+16 -> 48
key_length: 48,
// 10000 is a of 2021 the amount recommended by the NIST
// see https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-63b.pdf section 5.1.1.2
// quote:
// For PBKDF2, the cost factor is an iteration count: the more times the PBKDF2 function is
// iterated, the longer it takes to compute the password hash. Therefore, the iteration count
// SHOULD be as large as verification server performance will allow, typically at least 10,000
// iterations.
iterations: 10000,
// for the same reason we use sha3-256
// quote:
// A memory-hard function SHOULD be used because it increases the cost of an attack.
// The key derivation function SHALL use an approved one-way function such as
// Keyed Hash Message Authentication Code (HMAC) [FIPS 198-1],
// any approved hash function in SP 800-107, Secure Hash Algorithm 3 (SHA-3)
digest_algo: 'sha3-256'
);
// the key itself is 32 bytes (i.e 256 bits, because aes *256*)
$key = mb_substr($derivatedKey, 0, 32, '8bit');
$iv = mb_substr($derivatedKey, 32, openssl_cipher_iv_length('aes-256-cbc'), '8bit');
// 16 is the 8 bytes of `Salted__` and 8 bytes of salt itself
$cypherText = mb_substr($content, 16, encoding: '8bit');
echo openssl_decrypt($cypherText, 'aes-256-cbc', $key, iv: $iv);

AES encryption differences between php mcrypt and a Delphi component

I am using a Delphi component from chillkat which does AES encryption for me. It works like a charm and the server accepts my encrypted requests. So I tried to create a php pendant by using mcrypt. But the PHP mcypt result is different in comparison with the Delphi Chillcat result - even if all the parameters are the same. Therefore the server rejects the php requests.
All the encryption settings are the same:
Cipher Name: AES 128
Cipher mode: ECB
Padding scheme: Pad with NULL
Key length: 128
Key: 1234567890ABE1234567890ABE1234DB
String to encrypt: This is a really cool teststring
This is the little php script:
<?php
$key = '1234567890ABE1234567890ABE1234DB';
function string_encrypt($string, $key) {
$crypted_text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_ECB);
return $crypted_text;
}
function string_decrypt($encrypted_string, $key) {
$decrypted_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted_string, MCRYPT_MODE_ECB);
return trim($decrypted_text);
}
echo $test_str = 'This is a really cool teststring'; echo '<br />';
$enc_str = string_encrypt($test_str, $key);
echo bin2hex($enc_str); echo '<br />';
echo string_decrypt($enc_str, $key); echo '<br />';
?>
The php output is:
e355fbcd91ada4b835e1b030cc9741759219f59fe441ba62e628eca2e8289eb3
This is the Delphi Code:
function encrypt(s:PWideChar;mode,padding:integer;algo,cipher,keylength:string):string;
var
crypt: HCkCrypt2;
success: Boolean;
ivHex: PWideChar;
keyHex: PWideChar;
encStr: PWideChar;
decStr: PWideChar;
begin
crypt := CkCrypt2_Create();
// AES is also known as Rijndael.
CkCrypt2_putCryptAlgorithm('aes');
// "pki", "aes", "blowfish", "blowfish2", "des", "3des", "rc2", "arc4", "twofish", "pbes1" and "pbes2"
// CipherMode may be "ecb" or "cbc"
CkCrypt2_putCipherMode(crypt,'ecb');
// KeyLength may be 128, 192, 256
try
CkCrypt2_putKeyLength(crypt,128);
Except
showmessage('The encryption key you have used seems to be invalid');
end;
// The padding scheme determines the contents of the bytes
// that are added to pad the result to a multiple of the
// encryption algorithm's block size. AES has a block
// size of 16 bytes, so encrypted output is always
// a multiple of 16.
{
Possible values are:
0 = RFC 1423 padding scheme: Each padding byte is set to the number of padding bytes.
If the data is already a multiple of algorithm's block size bytes, an extra block is
appended each having a value equal to the block size. (for example, if the algorithm's
block size is 16, then 16 bytes having the value 0x10 are added.). (This is also known as
PKCS5 padding: PKCS #5 padding string consists of a sequence of bytes, each of which
is equal to the total number of padding bytes added. )
1 = FIPS81 (Federal Information Processing Standards 81) where the last byte contains
the number of padding bytes, including itself, and the other padding bytes are set to random values.
2 = Each padding byte is set to a random value. The decryptor must know
how many bytes are in the original unencrypted data.
3 = Pad with NULLs. (If already a multiple of the algorithm's block size,
no padding is added).
4 = Pad with SPACE chars(0x20). (If already a multiple of algorithm's block size, no padding is added).
}
CkCrypt2_putPaddingScheme(crypt,3);
// EncodingMode specifies the encoding of the output for
// encryption, and the input for decryption.
// It may be "hex", "url", "base64", or "quoted-printable".
CkCrypt2_putEncodingMode(crypt,'hex');
// An initialization vector is required if using CBC mode.
// ECB mode does not use an IV.
// The length of the IV is equal to the algorithm's block size.
// It is NOT equal to the length of the key.
ivHex := '';
CkCrypt2_SetEncodedIV(crypt,ivHex,'hex');
// The secret key must equal the size of the key. For
// 256-bit encryption, the binary secret key is 32 bytes.
// For 128-bit encryption, the binary secret key is 16 bytes.
keyHex := '1234567890ABE1234567890ABE1234DB';
CkCrypt2_SetEncodedKey(crypt,keyHex,'hex');
// Encrypt a string...
// The input string is 44 ANSI characters (i.e. 44 bytes), so
// the output should be 48 bytes (a multiple of 16).
// Because the output is a hex string, it should
// be 96 characters long (2 chars per byte).
//encryption
if mode = 0 then
begin
encStr := CkCrypt2__encryptStringENC(crypt,s);
result := encStr;
end
else
begin
result := CkCrypt2__decryptStringENC(crypt,s);
end;
CkCrypt2_Dispose(crypt);
End;
The chillkat Delphi component's output is:
780F849AB30690433409D4FB7B3357735296A6E76D3AA6B6D6C769BE99F32041
I thought both outputs should produce the same value, as all the input parameters are equal, right ?
MCrypt expects the key to be a binary string, but you pass a hex encoded string into it.
Use
$key = hex2bin($key);
or
$key = pack('H*', $key);
depending on PHP support. This must be done before calling mcrypt_encrypt().
Security:
Don't ever use ECB mode. It's not semantically secure. There is a lot that an attacker can get from ciphertexts without decrypting them. You need to use at the very least CBC mode with a random IV. You should also have an authentication tag of your ciphertexts.
This answer gives you every thing you need to know about this. One thing left, don't use MCrypt. It's an old unmaintained library.
This answer is also very helpful in that regard with a different way of achieving the same thing.
thanks to your your nice inputs, I was able to receive a positive answer from the server with the following 4 lines of code:
$key = pack('H*', "*5DB");
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,$mytxt, MCRYPT_MODE_ECB);
$hex2 = bin2hex($ciphertext);
echo strtoupper($hex2);
However, I have learned that
ECB is unsecure
mcrypt is old and unmaintained
I will check alternatives and update my code accordingly. Thanks again !

32-character PHP AES Key for mcrypt_encrypt

Consider the following PHP code:
<?php
$key = "1234567812345678";
$iv = "1234567812345678";
$data = "Test string";
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,
$key,
$data,
MCRYPT_MODE_CBC,
$iv);
print "Encoded1: " . base64_encode($encrypted) . "\n";
$key = "12345678123456781234567812345678";
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128,
$key,
$data,
MCRYPT_MODE_CBC,
$iv);
print "Encoded2: " . base64_encode($encrypted) . "\n";
When run, this produces the output:
Encoded1: iz1qFlQJfs6Ycp+gcc2z4w==
Encoded2: n3D26h/m8CSH0CE+z6okkw==
Note that I stole the first bit of code from PHP Java AES CBC Encryption Different Results
Now - here's the question:
In the first case, the key that was passed in was a string of 16 characters. If each of the individual characters was interpreted as an 8-bit quantity, this gives the 128-bit key size that one would expect. Indeed, the Java code that's on the StackOverflow page that I referenced above does exactly that, and obtains the same result as the PHP.
In the second call to mcrypt_encrypt above, I have doubled the length of the key. mcrypt_encrypt accepts this happily, but produces a different encrypted output than in the first case. Clearly, therefore, it considers this a different key - it does not, for example take only the first 128 bits and discard any past that.
So, how does mcrypt_encrypt process the input key string to come up with the 128-bit key that the MCRYPT_RIJNDAEL_128 algorithm requires?
If it makes any difference, the case I'm specifically interested in is when a 32-character string is passed in like my second example - I have to create a matching decryption routine (in Java), so I need to figure out how the key is actually generated in this case. The page I cited has perfectly-good Java code (which works with all my test cases) - I'm just missing the proper set of key bytes.
There are two important parameters for the Rijndael algorithm. There is the key size (128-bit, 192-bit and 256-bit) and then there is the block size (128-bit, 192-bit and 256-bit). The 128 in MCRYPT_RIJNDAEL_128 refers to the block size. The key size is variable.
When you pass keys of different lengths into MCrypt, it will select the appropriate key size automatically, so you don't and can't set it. MCRYPT_RIJNDAEL_128 is AES (AES-128, AES-192, AES-256). MCRYPT_RIJNDAEL_192 and MCRYPT_RIJNDAEL_256 are not AES anymore.
If the Java code produced a matching result for the 128-bit key, then it will produce a matching result for the 256-bit key as well.
MCrypt is a little strange. Before PHP version 5.6.0 it would take any key length and not just 128-bit, 192-bit or 256-bit. The key would be filled up with 0x00 bytes up to the next valid key length.
Since Java doesn't support ZeroPadding out of the box, you should use a proper padding scheme such as PKCS#5/PKCS#7 padding in PHP. This answer has a very good implementation of it.

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.

AESCrypt decryption between iOS and PHP

I am having a heck of a time figuring out how to decrypt a string encrypted with the NSData+AESCrypt.m (Explained here)
I have been looking at a handful of other threads, but I only need the iDevice to send a string to a PHP file encrypted, and then it gets decrypted inside PHP (where it gets stored into a database).
This code :
NSString *encryptedString = [#"Hello" AES256EncryptWithKey:#"a16byteslongkey!"];
NSLog(#"The strign encrypted : %#",encryptedString);
Returns the string encrypted : 7opqbb7sEVNoXplyQv/X8g==
And here is my PHP code for decryption:
function decrypt_data($data, $key) {
return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key,$data,MCRYPT_MODE_ECB);
}
function unpadPKCS7($data, $blockSize) {
$length = strlen ( $data );
if ($length > 0) {
$first = substr ( $data, - 1 );
if (ord ( $first ) <= $blockSize) {
for($i = $length - 2; $i > 0; $i --)
if (ord ( $data [$i] != $first ))
break;
return substr ( $data, 0, $i );
}
}
return $data;
}
function decrypt_string($string) {
$string = unpadPKCS7($string,128);
$string = decrypt_data($string,"a16byteslongkey!");
return $string;
}
die('<br>Basic :'.decrypt_string('7opqbb7sEVNoXplyQv/X8g=='));
UPDATE:
Been doing some MD5 decryption and experimenting a lot, but still far from achieving usable results. This is what I got so far:
Original string : Hello
AES256Encrypt result : 7opqbb7sEVNoXplyQv/X8
base64_decode Decrypted: îŠjm¾ìSh^™rBÿ×
mcrypt_rijndael_128 : Õ¯Öå«Ž(ás2’'u)
mcrypt_rijndael_128 & hex2bin : UÃ)ı+úy´e
Sadly, no matter how I bend and twist this, I just get jibberish. Can anyone see what I'm doing wrong?
Disclaimer: I have zero iPhone development experience.
Short answer - what tc. said. Something is horribly wrong with the AES256EncryptWithKey:
Being AES256 you would expect it to require a 32 byte key, not a 16 byte key. But OK, say it pads shorter keys with null bytes to make them 32 bytes. This might explain why your 16 byte key is being padded with 16 null characters.
But, when it comes to the actual act of encryption, it's using AES 128, but with the 32 byte key. Say wha?
Converting tc.'s Python to PHP:
$base64encoded_ciphertext = '7opqbb7sEVNoXplyQv/X8g==';
$key = 'a16byteslongkey!';
$padded_key = $key . str_repeat(chr(0x00), 16); // Argh!
$result = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $padded_key, base64_decode($base64encoded_ciphertext), 'ecb');
// Yetch - $result ends up being padded with 0x0b's (vertical tab).
var_dump(rtrim($result, chr(0x0b)));
Result:
string(5) "Hello"
~~
Edit: This post from Henno has some relevant details.
~~
Did some additional research. The null padding on your key is likely because AES256 requires a 32 byte key. The 0x0B padding on the plaintext is thanks to PKCS7. PKCS7 is a padding scheme where the byte used for padding is equal in value to the number of bytes added. In this example, 11 bytes were added to the end of 'Hello' turning your 5 byte input into a 16 byte block for AES. 11 = 0x0B.
Thus, the code above will not work when the plaintext is not length = 5. Try the following instead:
$pad_char = ord(substr($result, -1));
$result_without_padding = substr($result, 0, strlen($result) - $pad_char);
The encrypted string looks like it's been base64 encoded. Try decoding it before you decrypt it.
First off, the Objective-C code you're using is pretty terrible:
The keyspace is severely limited (presumably UTF-8 bytes terminated by a null byte, extended with null bytes to 32 bytes). The easiest way to generate a random key is to stick to ASCII, which limits you to about 223.6 bits for the default key size of 256 bits.
Encryption is done in ECB mode.
Data appears to be irreversibly padded with 0x0B.
Avoid it at all costs. It is not secure.
It can be "decrypted" in Python with something like this:
>>> import Crypto.Cipher.AES
>>> import base64
>>> Crypto.Cipher.AES.new('a16byteslongkey!'+'\0'*16).decrypt(base64.b64decode('7opqbb7sEVNoXplyQv/X8g=='))
'Hello\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
see my post here: PHP iOS AES Encryption
I just got through this same sort of project. I used the library you referenced in "also considered..."
Here is some example code to decrypt with php:
$iv2 = '';
for($i=0;$i<16;$i++){
$iv2 .= "\0";
}
$plain_text_CBC = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted_text, MCRYPT_MODE_CBC, $iv2);
var_dump($plain_text_CBC);
Make sure your keys are both 256-bit (32 characters, I have not yet had any encoding issues, but if you do, remember that you are encrypting bytes, not characters). Note that 128 in MCRYPT_RIJNDAEL_128 is the block size and not the key size, while in the method AES256DecryptWithKey, 256 is a reference to the key size, while the block size is 128. AES256DecryptWithKey runs in CBC mode, but has a null initialization vector (iv).
CBC means that each block depends on the last block, and so it uses a pre-set, usually random, "block -1" called the IV
ECB means that each block is encrypted in the same way, hence it reveals when two blocks in the same message are the same. The library mentioned does not use it, so I mentioned it just for contrast.
The use of a zero iv (0000000000000000 in bytes) is considered insecure, but it does provide you with some additional security (but one might still be able to tell if the fist 16 characters of your plain text were the same each time). To fix this you would have to create an NSData *iv variable for the IV and modify the CCcrypt argument of NSData+AESCrypt.m to add [iv bytes] for the iv parameter (I have not yet tested this code), and you would need to store this iv and pass it to the php along with you message. But first I would test and have everything working with a zero iv.

Categories