Link to home
Start Free TrialLog in
Avatar of Jim Dettman (EE MVE)
Jim Dettman (EE MVE)Flag for United States of America

asked on

Trying to decrypt chiper text encrypted with OpenSSL in PHP.

Trying to decrypt some cipher text that was generated by an OpenSSL() call from PHP in an outside system.

Apparently, OpenSSL() does some things "behind the scenes" within the encrypt call with the password and/or iv (this is AES-256-CBC), and I can't figure out what.  

Knowing the cipher text, password, and IV, I am unable to decypt it.

Anyone know exactly what OpenSSL_encrypt() does with the password and IV that it gets, or what it expects in terms of the format of those (i.e. assumes they are in Hex, base 64, or whatever)?  The OpenSSL_decrypt() call is being handed the following:

Password: add7fc636935108704da3474402cb19263ca3d2fa06d6f1d9c56a4f0d25e78d6
IV:  9a0670164d48f8d9
CipherTest (base 64): M2VyRThFSkVUZW5uc0Jna0h0VEFMUT09
Plain Text result should be:  12345678

But when I try using these values with other routines, I am unable to dervie the plain text.  I think the problem lies with the IV, but I am not positive.

Jim.
Avatar of noci
noci

I think you forgot about the salt....
The strings seems legit.
In shell commands decode would be like....

echo "M2VyRThFSkVUZW5uc0Jna0h0VEFMUT09" | openssl enc -d -a -aes-256-cbc -iv 9a0670164d48f8d9 -pass pass:add7fc636935108704da3474402cb19263ca3d2fa06d6f1d9c56a4f0d25e78d6
Yielding "bad magic number" as error message
Avatar of Jim Dettman (EE MVE)

ASKER

Yielding "bad magic number" as error message

Not sure what that is.    This is my first foray into encryption and I don't know PHP either, but I've been able to reverse engineer what the plug-in does fairly well.    I am able to view and modify the source code, so I traced all the routines through and added logging to record the values of the variables at each point.

What is in PHP for the actual encryption is:

$text = 'GFEncrypt: ' . $iv . $gfef_search_key . trim(base64_encode(openssl_encrypt($creatinguser . $text, $ssl_cipher_name, $key, 0, $iv)));

 and the values being passed to this call immediately before this line are:

User generated image

 I'm not sure what the $gfef_search_key is used for by this plug-in, but it's added outside of the string being encrypted along with the IV, both of which are stripped off before the decrypt:

 $creatinguser, which is part of the encrypted data  is a zero length string, so nothing gets added there.

 That leaves $text being encrypted, which is "12345678"

 I know a salt value can be used with the command line OpenSSL, but in PHP there is no parameter for that with the openssl_encrypt() call and maybe that's where the problem lies.

 I know I am passing the $text correctly, but it seems like this call is doing something with the $key and/or $iv.    When I use the same key and text in another crypto test bench, I get different results for the cipher text:

User generated image
So there is something going on here, possibly with the $iv as it is supplied as a 16 byte string to  openssl_encrypt(), but I believe it needs to be 32 hex characters.  Just a gut hunch this is where things are getting goofed up.

Jim.
When I run this code:
<?php
$cipher = "AES-256-CBC";
$data = "M2VyRThFSkVUZW5uc0Jna0h0VEFMUT09";
$key = "add7fc636935108704da3474402cb19263ca3d2fa06d6f1d9c56a4f0d25e78d6";
$iv = "9a0670164d48f8d9";
$options = 0;
echo trim(openssl_decrypt(base64_decode($data), $cipher, $key, $options, $iv));

Open in new window

...it works fine. I get "12345678" as the output.

It's worth noting that your encryption script is doubling up on Base64. The call to openssl_encrypt will return base64-encoded results by default, so when your code calls base64_encode() on the result, you're ending up with 2 layers of Base64 encoding, so you have to decode it at least one additional time first or change your encryption code so it doesn't double-encode.
Also, in your testbed app, you have "hex" selected as your key format, but you provided the alpha version of your key.

Your IV is fine - that's already assumed to be hex, so you're good there, but that "Key Format" radio button set should be set to Alpha if you're going to use the "add7fc..." key.
<<...it works fine. I get "12345678" as the output.>>

  yes, and the web site test utility works fine as well.   I can encrypt/decrypt on the web site without issue.   My problem is in moving to an outside system; I can't get a decrypt to work.  

  The key is definitely in hex format already, as it is the output of a Hash('sha256'   call.    

  But what OpenSSL_encrypt does when you hand it a key and IV is not clear.   I've been reading comments all over the place...some say it accepts those as raw binary data.   Others say it needs to be a hex value.   Others say it's taken "as is" and padded automatically as required.   Which by the way is what the test bed does if it's in "alpha' mode:

User generated image
 But it's not clear what OpenSSL calls does with the parameters supplied (if anything) and what form they need to be in.

<<It's worth noting that your encryption script is doubling up on Base64.>>

 I actually caught that myself  this morning when I was running through the PHP docs and that may be part of the problem in trying to compare the two.   They didn't pass the RAW option, so the calls are assuming something base64 encoded, but they are doing an encode/decode on top of that.   So the chiper text I'm using in the test bed may not be the actual chiper text.   But I think there is more to it then that.   I was going to check that out, but so far today has been nuts.

 and for what it's worth, the code from the plug-in is below , along with the output of the logging I added (and thanks once again for that).

 Jim.

// GET/CREATE KEY
function gfef_get_key($gfe_key, $operation) {
	$gfe_key_override 		= esc_html__(get_option('gfe_encryption_key_override'));
	if ($operation) {
		if ($operation === 'decrypt') {
			if ($gfe_key_override) {
				$gfe_key = md5($gfe_key_override);
			}
		}
	}
	$password 				= "!c64l?" . trim($gfe_key) . "h4s?09aq-p3x";
	$salt 					= gfef_get_salt() === false ? gfef_create_salt(false) : gfef_get_salt();
	$key 					= md5(hash('SHA256', $salt . $password, true));
	
	return $key;
}

// OPEN SSL Encrypt
function gfef_ssl_encrypt($text, $creatinguser, $key) {
    $ssl_cipher_name = "AES-256-CBC";
	$gfef_search_key = substr(hash('sha256', substr(hash('sha256', md5(substr($key, 12, 4) . $text)),24, 6) . substr(hash('sha256', md5(substr($key, 30, 10) . $text)),54, 6)), 33, 10);
    $key 			 = hash('sha256', $key);
	$iv_size		 = openssl_cipher_iv_length($ssl_cipher_name);  // iv for AES-256-CBC = 16 bytes
	$iv 			 = substr(hash('sha256', base64_encode(openssl_random_pseudo_bytes($iv_size))), 0, $iv_size);
    $text 			 = 'GFEncrypt: ' . $iv . $gfef_search_key . trim(base64_encode(openssl_encrypt($creatinguser . $text, $ssl_cipher_name, $key, 0, $iv)));
	
    return $text;
}

// OPEN SSL Decrypt
function gfef_ssl_decrypt($text, $key) {
    $ssl_cipher_name = "AES-256-CBC";
    $key 			 = hash('sha256', $key);
	$iv_size 		 = openssl_cipher_iv_length($ssl_cipher_name);  // iv for AES-256-CBC = 16 bytes
	$text_decode 	 = substr($text, 11);
    $iv 			 = substr($text_decode, 0, $iv_size);
	$text_decode 	 = substr($text_decode, $iv_size + 10);
    $text			 = trim(openssl_decrypt(base64_decode($text_decode), $ssl_cipher_name, $key, 0, $iv));

    return $text;
}

// Encrypt
function gfef_encrypt($text, $creatinguser, $key = null) {
	$use_mcrypt 			= apply_filters('gform_use_mcrypt', function_exists( 'mcrypt_encrypt'));
	$use_openssl			= function_exists('openssl_encrypt') && extension_loaded('openssl');
	$gfe_key 				= false;
	if (get_option('gfe_encryption_key')) {
		$gfe_key 			= md5(esc_html__(get_option('gfe_encryption_key')));
	}
	$gfe_bypass				= esc_html__(get_option('gfe_encryption_bypass'));
	$gfe_type			 	= get_option('gfe_encryption_method');
	$key 					= gfef_get_key($gfe_key, 'encrypt');
	$gfef_search_key 		= substr(hash('sha256', substr(hash('sha256', md5(substr($key, 12, 4) . $text)),24, 6) . substr(hash('sha256', md5(substr($key, 30, 10) . $text)),54, 6)), 33, 10);
	
	if ($creatinguser) {
		$creatinguser = 'GFEFU[[[' . $creatinguser . ']]]GFEFU';
	}
	
	if ($gfe_type == 2 && $use_mcrypt && $gfe_key && !$gfe_bypass) {
		$mcrypt_cipher_name = MCRYPT_RIJNDAEL_128;
		$iv_size            = mcrypt_get_iv_size($mcrypt_cipher_name, MCRYPT_MODE_CBC);
		//$iv 				= mcrypt_create_iv($iv_size, MCRYPT_RAND);
		$iv					= substr(hash('sha256', base64_encode(mcrypt_create_iv($iv_size, MCRYPT_RAND))), 0, $iv_size);
		$encrypted_value 	= 'GFEncrypt: ' . $iv . $gfef_search_key . trim(base64_encode(mcrypt_encrypt($mcrypt_cipher_name, $key, $creatinguser . $text, MCRYPT_MODE_CBC, $iv)));
	} else if ($gfe_type == 1 && $gfe_key && $use_openssl && !$gfe_bypass) {
		$encrypted_value	= gfef_ssl_encrypt($text, $creatinguser, $key);
	} else {
		$encrypted_value 	= $text;
	}
	return $encrypted_value;
}

// Decrypt
function gfef_decrypt($text, $key = null) {
	$use_mcrypt 			= apply_filters('gform_use_mcrypt', function_exists( 'mcrypt_decrypt'));
	$use_openssl			= function_exists('openssl_encrypt') && extension_loaded('openssl');
	$gfe_type			 	= get_option('gfe_encryption_method');
	$gfe_key 				= false;
	if (get_option('gfe_encryption_key')) {
		$gfe_key 			= md5(esc_html__(get_option('gfe_encryption_key')));
	}
	$secure_key 			= substr($text, 0, 11);
	$key 					= gfef_get_key($gfe_key, 'decrypt');
	
	if ($secure_key === 'GFEncrypt: ') {
		$secure_key = 'locked';
	} else {
		$secure_key = 'unlocked';
	}
	
	if ($gfe_type == 2 && $use_mcrypt && $gfe_key && $secure_key === 'locked') {
		$mcrypt_cipher_name = MCRYPT_RIJNDAEL_128;
		$iv_size            = mcrypt_get_iv_size($mcrypt_cipher_name, MCRYPT_MODE_CBC);
		$text_decode 	 	= substr($text, 11);
		$iv 			 	= substr($text_decode, 0, $iv_size);
		$text_decode 	 	= base64_decode(substr($text_decode, $iv_size + 10));
		$decrypted_value 	= trim(mcrypt_decrypt($mcrypt_cipher_name, $key, $text_decode, MCRYPT_MODE_CBC, $iv));
	} else if ($gfe_type == 1 && $gfe_key && $use_openssl && $secure_key === 'locked') {
		$decrypted_value	= gfef_ssl_decrypt($text, $key);
	} else {
		$decrypted_value 	= $text;
	}
	return $decrypted_value;
}

Open in new window



gfef__ssl_encrypt:_text::12345678
gfef__ssl_encrypt:_creatinguser::
gfef__ssl_encrypt:_key::5b0a50735ab704426ab91cc38f7d47be
gfef__ssl_encrypt:_
gfef__search_key::9bcf6f6c7d
gfef__ssl_encrypt:_key_BeforeHash::5b0a50735ab704426ab91cc38f7d47be
gfef__ssl_encrypt:_key_AfterHash::add7fc636935108704da3474402cb19263ca3d2fa06d6f1d9c56a4f0d25e78d6
gfef__ssl_encrypt:_iv_size::16
gfef__ssl_encrypt:_iv::9a0670164d48f8d9
gfef__ssl_encrypt:_text::GFEncrypt: 9a0670164d48f8d99bcf6f6c7dM2VyRThFSkVUZW5uc0Jna0h0VEFMUT09

gfef__decrypt:gfe_encryption_key:Before_Hash::12345678a!A
gfef__decrypt:gfe_encryption_key:Aefore_Hash::3512dfc28ce9b1db8c8b7eb9b588f91a
gfef__decrypt:_secure_key::GFEncrypt:

gfef__get_key:_gfe_key::3512dfc28ce9b1db8c8b7eb9b588f91a
gfef__get_key:_password::!c64l?3512dfc28ce9b1db8c8b7eb9b588f91ah4s?09aq-p3x
gfef__get_key:_salt::a185512c80a3ee6f1bfd438a0e468e41
gfef__get_key:_SaltAndPassword::a185512c80a3ee6f1bfd438a0e468e41!c64l?3512dfc28ce9b1db8c8b7eb9b588f91ah4s?09aq-p3x
gfef__get_key:_HashOfSaltAndPassword::”üú >¶*gC„ ° Ë«<² ٍêço9ƒ•Ù=ë
gfef__get_key:_key::5b0a50735ab704426ab91cc38f7d47be

gfef__decrypt:_key::5b0a50735ab704426ab91cc38f7d47be
gfef__ssl_decrypt:_text::GFEncrypt: 9a0670164d48f8d99bcf6f6c7dM2VyRThFSkVUZW5uc0Jna0h0VEFMUT09
gfef__ssl_decrypt:_key_BeforeHash::5b0a50735ab704426ab91cc38f7d47be
gfef__ssl_decrypt:_key_AfterHash::add7fc636935108704da3474402cb19263ca3d2fa06d6f1d9c56a4f0d25e78d6
gfef__ssl_decrypt:_iv_size::16
gfef__ssl_decrypt:_text_decode::9a0670164d48f8d99bcf6f6c7dM2VyRThFSkVUZW5uc0Jna0h0VEFMUT09
gfef__ssl_decrypt:_iv::9a0670164d48f8d9
gfef__ssl_decrypt:_text_decode::M2VyRThFSkVUZW5uc0Jna0h0VEFMUT09
gfef__ssl_decrypt:_text::12345678
actually, on the logging and the code, let me come up with a clean test of that.  I've been going back and forth with so many e-mails and tests, I'm not sure that's totally clear.

I also want to break up the double base64 encode so I can see what the values are at each step.

I'll post back later today.

Jim.
Instead of:

   $text                    = 'GFEncrypt: ' . $iv . $gfef_search_key . trim(base64_encode(openssl_encrypt($creatinguser . $text, $ssl_cipher_name, $key, 0, $iv)));


 I'll do:

  $text_chiper  = openssl_encrypt($creatinguser . $text, $ssl_cipher_name, $key, 0, $iv)
  $text                    = 'GFEncrypt: ' . $iv . $gfef_search_key . trim(base64_encode($text_chiper))

 and log each value.

Jim.
The key is definitely in hex format already, as it is the output of a Hash('sha256'   call.    
Yes and no. The hash() call returns the hex-encoded value. However, that hex-encoded value is technically just a regular string, and that regular string is being used as the key. So it's a little bit of a misnomer to refer to it as a hex string. It might be easier to think of it as a string that consists of characters that all happen to be hex-compatible (0-9 a-f) characters.

But it's not clear what OpenSSL calls does with the parameters supplied (if anything) and what form they need to be in.
You might be overthinking this. OpenSSL isn't going to do more than what's requested by default EXCEPT for base64-encoding the result (unless you explicitly tell it to return the raw data). So the options you pass in are exactly what's going to be used to run the cipher - nothing more, nothing less.

Your encryption code didn't really pass any values - the $options parameter was set to 0, so you're not specifying any extra options. All the defaults are in play.

If the problem is that the outside system doesn't decrypt, then I'd for sure say that you need to handle that double Base64 decoding, since most external systems are going to expect raw byte data as input, NOT base64-encoded data. PHP just happens to be so web-centric that the base64 encoding is an acceptable default. Meanwhile, desktop applications (.NET, VBscript, etc...) are probably going to want a byte array with the raw bytes for the cipher text.

If you have specific code that you're trying for the decode side of things and it's not working, provide it here, and let's see if we can find the missing link. PHP and C# are both within my wheelhouse, and I used to do classic VB a few decades ago, so I might be rusty, but I might be able to point out the problem.
<<So the options you pass in are exactly what's going to be used to run the cipher - nothing more, nothing less.>>

  Good to know.

<< then I'd for sure say that you need to handle that double Base64 decoding, >>

 That will be the next step.

<<since most external systems are going to expect raw byte data as input, >>

 The Crypto lib I bought has a whole series of routines, one for raw, hex, and base64, but it works in a rather odd way; all the parameters need to be passed the same way for a given routine.   If for example you use the base64 version, then the key, iv, and ciphertext all need to be base64 encoded.  If you use the hex version, they all need to be expressed in hex.

 It does have conversion routines though, so I can go back and forth between anything, but the rub is what exactly that is.

<< Yes and no. The hash() call returns the hex-encoded value. However, that hex-encoded value is technically just a regular string,>>
<<It might be easier to think of it as a string that consists of characters that all happen to be hex-compatible (0-9 a-f) characters.>>

  Yes, your right.   I didn't think that through.   If each were treated as a hex digit, that would only be 4 bits, and since there are only 32 of them, that would only give you half a key (128 bits in total).   Each character then must be looked at on it's own, and represent a 8 bit binary value.   They would yield 256 bits as needed.

 Let me make another pass at all this and see if I can't figure it out with everything that has been said.

Jim.
OK, so made another pass and no dice.     I modified the plug-in and got the value before/after the second base 64 conversion.  This is what I got on the encrypt:

gfef_ssl_encrypt:_text::1234567890
gfef_ssl_encrypt:_creatinguser::
gfef_ssl_encrypt:_key::5b0a50735ab704426ab91cc38f7d47be
gfef_ssl_encrypt:_gfef_search_key::277948d3e1
gfef_ssl_encrypt:_key_BeforeHash::5b0a50735ab704426ab91cc38f7d47be
gfef_ssl_encrypt:_key_AfterHash::add7fc636935108704da3474402cb19263ca3d2fa06d6f1d9c56a4f0d25e78d6
gfef_ssl_encrypt:_iv_size::16
gfef_ssl_encrypt:_iv::f7331cecd53332db
gfef_ssl_encrypt:_text_cipher::qvGZP3KAMZA/v1vlapAtGw==
gfef_ssl_encrypt:_text_cipher_AfterBase64encoded::cXZHWlAzS0FNWkEvdjF2bGFwQXRHdz09
gfef_ssl_encrypt:_text::GFEncrypt: f7331cecd53332db277948d3e1cXZHWlAzS0FNWkEvdjF2bGFwQXRHdz09

  So the real value of the of cipher text is:

qvGZP3KAMZA/v1vlapAtGw==

 Which when I run the test bed, I don't get:

User generated image
 as it shows:

uQjND7gqDFQP8EqcHCS1mw==

  Bit bummed....really thought that was the problem and not sure where to go from here.

Jim.
Am I under the wrong assumption here?   ie. that the cipher text result will be the same given the same text, key, and IV value?

 I suppose I should just try and dechiper it with the outside routines....I'll try that next.

Jim.
ASKER CERTIFIED SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
ah, I missed that, thanks!

Jim.
qvGZP3KAMZA/v1vlapAtGw==
qvGZP3KAMZA/v1vlapAtGw==

  and we are there!!!!

  So it was just the double base 64 encoding.

 Thanks a bunch for helping me through this.

Jim.
excellent help.
By the way, it's worth noting that when you involve a salt or key generator, the encrypted output can be different on each iteration.