Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TripleDES in Perl/PHP/ColdFusion

Recently a problem arose regarding hooking up an API with a payment processor who were requesting a string to be encrypted to be used as a token, using the TripleDES standard. Our Applications run using ColdFusion, which has an Encrypt tag - that supports TripleDES - however the result we were getting back was not what the payment processor expected.

First of all, here is the resulting token the payment processor were expecting.

AYOF+kRtg239Mnyc8QIarw==

And below is the snippet of ColdFusion we were using, and the resulting string.

<!--- Coldfusion Crypt (here be monsters) --->
<cfset theKey="123412341234123412341234">
<cfset theString = "username=test123">
<cfset strEncodedEnc = Encrypt(theString, theKey, "DESEDE", "Base64")>
<!---
 resulting string(strEncodedEnc): tc/Jb7E9w+HpU2Yvn5dA7ILGmyNTQM0h
--->

As you can see, this was not returning the string we were hoping for. Seeking a solution, we ditched ColdFusion for this process and attempted to reproduce the token in PHP.

Now I'm aware that various languages implement encryption in different ways - for example in the past managing encryption between a C# application and PHP back-end, I've had to play about with padding in order to get the two to talk, but my experience has been that PHP generally behaves when it comes to encryption standards.

Anyway, on to the PHP source we tried, and the resulting string.

/* PHP Circus (here be Elephants) */
$theKey="123412341234123412341234";
$theString="username=test123";
$strEncodedEnc=base64_encode(mcrypt_ecb (MCRYPT_3DES, $theKey, $theString, MCRYPT_ENCRYPT));
/*
 resulting string(strEncodedEnc): sfiSu4mVggia8Ysw98x0uw==
*/

As you can plainly see, we've got another string that differs from both the string expected by the payment processor AND the one produced by ColdFusion. Cue head-against-wall integration techniques.

After many to-and-fro communications with the payment processor (lots and lots of reps stating 'we can't help with coding issues, you must be doing it incorrectly, read the manual') we were finally escalated to someone with more than a couple of brain-cells to rub together, who was able to step back and actually look at and diagnose the issue.

He agreed, our CF and PHP attempts were not resulting in the correct string. After a quick search, he also agreed that it was not neccesarily our source, but rather how the two languages implemented their vision of the TripleDES standard.

Coming into the office this morning, we were met by an email with a snippet of source code, in Perl. This is was the code they were directly using on their end to produce the expected token.

#!/usr/bin/perl
# Perl Crypt Calamity (here be...something)
use strict;
use CGI;
use MIME::Base64;
use Crypt::TripleDES;

my $cgi = CGI->new();
my $param = $cgi->Vars();

$param->{key} = "123412341234123412341234";
$param->{string} = "username=test123";
my $des = Crypt::TripleDES->new();

my $enc = $des->encrypt3($param->{string}, $param->{key});
$enc = encode_base64($enc);
$enc =~ s/\n//gs;

# resulting string (enc): AYOF+kRtg239Mnyc8QIarw==

So, there we have it. Three languages, three implementations of what they quote in the documentation as TripleDES Standard Encryption, and three totally different resulting strings.

My question is, from your experience of these three languages and their implementations of the TripleDES algorithm, have you been able to get any two of them to give the same response, and if so what tweaks to the code did you have to make in order to come to the result?

I understand this is a very drawn out question, but I wanted to give clear and precise setting for each stage of testing that we had to perform.

I'll also be performing some more investigatory work on this subject later, and will post any findings that I come up with to this question, so that others may avoid this headache.

like image 456
Seidr Avatar asked May 12 '10 09:05

Seidr


3 Answers

The Perl's TripleDES should never be used. It does so many weird things and you are going to have fun.

Your first problem is that the keys in Perl are hex and you need to convert them into binary. Try this in PHP,

$theKey="123412341234123412341234";
$key = pack('H*', str_pad($theKey, 16*3, '0'));
$strEncodedEnc=base64_encode(mcrypt_ecb (MCRYPT_3DES, $key, $theString, MCRYPT_ENCRYPT));
echo $strEncodedEnc, "\n";

The result is,

AYOF+kRtg239Mnyc8QIarw==

Then you have to pad it in a weird way. I forgot the details. You are lucky with this sample (it's 16 chars).

like image 152
ZZ Coder Avatar answered Nov 18 '22 23:11

ZZ Coder


The Coldfusion Answer:

The first problem is that your key length is not correct for Triple DES. ZZ Coder correctly deduced that it needs to be padded to the correct length with 0's.

The next step is that the key needs to be converted to hex. To do this in CF, we have:

<cfset theKey="123412341234123412341234000000000000000000000000">
<cfset encodedKey = ToBase64(BinaryDecode(theKey, "HEX"))>

The final step is that the result is not being padded either, so we need to specify this in the encryption algorithm in CF:

<cfset strEncodedEnc = Encrypt(theString, encodedKey, "DESEDE/ECB/NoPadding", "Base64")>

The resulting complete code:

<cfset theKey="123412341234123412341234000000000000000000000000">
<cfset encodedKey = ToBase64(BinaryDecode(theKey, "HEX"))>
<cfset theString = "username=test123">
<cfset strEncodedEnc = Encrypt(theString, encodedKey, "DESEDE/ECB/NoPadding", "Base64")>
<cfdump var="#strEncodedEnc#"><br>

results in:

AYOF+kRtg239Mnyc8QIarw==
like image 5
Edward M Smith Avatar answered Nov 18 '22 23:11

Edward M Smith


I'll include the code below for anyone that happens to be working on CCBill upgrade (which sounds like the company referred to in the original post). The PHP functions below will match the output from CCBill's 3DES/TripleDES internal encryption as described in the documentation here: http://www.ccbill.com/cs/manuals/CCBill_Subscription_Upgrade_Users_Guide.pdf

//Encrypt String using 3DES Key
function encrypt($str,$key){
    $hex_key = hexmod($key);
    $bin_hex_key = pack('H*', str_pad($hex_key, 16*3, '0'));
    //Pad string length to exact multiple of 8
    $str = $str. str_repeat(' ',8-(strlen($str)%8) );   
    $out = base64_encode( mcrypt_ecb(MCRYPT_3DES, $bin_hex_key, $str, MCRYPT_ENCRYPT) );
    //print_r('Key/Hex/Str: '.$key.' -> '.$hex_key.' -> '.$str.' -> '.$out,1);
    return $out;
}

//Hex Modulus: Converts G-Z/g-z to 0-f (See @Jinyo's Post)
//Necessary to match CCBill's Encryption
function hexmod($str){
    //Convert G-Z & g-z to 0-f
    $ascii_in  = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
    $ascii_out = '0123456789ABCDEF0123456789ABCDEF0123abcdef0123456789abcdef0123';
    $hex_out = str_replace(str_split($ascii_in),str_split($ascii_out),$str);
    return $hex_out;
}

$triple_des_key = 'ABCDEFGHIJKLMNOPQRSTUVWX'; // <!-- 24char 3DES Key
$username_string = 'username=<username here>'; // Encrypt this string
$encrypted_username = encrypt($username_string,$triple_des_key); // <-- Output
like image 4
Eric Kigathi Avatar answered Nov 18 '22 23:11

Eric Kigathi