12

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.

4

8 に答える 8

8

Perl の TripleDES は決して使用しないでください。それは非常に多くの奇妙なことを行い、あなたは楽しむつもりです.

最初の問題は、Perl のキーが 16 進数であり、それらをバイナリに変換する必要があることです。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";

結果は、

AYOF+kRtg239Mnyc8QIarw==

次に、奇妙な方法でパディングする必要があります。詳細は忘れました。このサンプル (16 文字) は幸運です。

于 2010-05-12T10:25:13.437 に答える
5

Coldfusion Answer:

最初の問題は、キーの長さがTripleDESに対して正しくないことです。ZZ Coderは、0で正しい長さにパディングする必要があると正しく推測しました。

次のステップは、キーを16進数に変換する必要があることです。CFでこれを行うには、次のようにします。

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

最後のステップは、結果もパディングされないことです。そのため、CFの暗号化アルゴリズムでこれを指定する必要があります。

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

結果の完全なコード:

<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>

結果:

AYOF+kRtg239Mnyc8QIarw==
于 2010-05-12T13:28:05.490 に答える
4

CCBillのアップグレードに取り組んでいる人のために、以下のコードを含めます(元の投稿で言及されている会社のようです)。以下の PHP 関数は、http: //www.ccbill.com/cs/manuals/CCBill_Subscription_Upgrade_Users_Guide.pdfのドキュメントで説明されているように、CCBill の 3DES/TripleDES 内部暗号化からの出力と一致します 。

//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
于 2012-08-12T19:52:39.490 に答える
2

ZZ Coder はすぐそこにありました。Perl と PHP のコードが異なる暗号化を返した理由について、いくつか注意点があります。

まず、無効な 16 進文字 (F の後の文字) がある場合は常に、次の規則に従ってそれらを置き換えます。

  • G->0
  • H->1
  • I->2
  • J->3
  • ...
  • P->9
  • Q->A
  • R->B
  • ...
  • V->F
  • W->0
  • ...
  • Z->3

このメソッドを使用すると、AZ98AZ98AZ98AZ98AZ98AZ98 のキーは A398A398A398A398A398A39800000000000000000000000 になります (ゼロで埋めた後)。

第 2 に、暗号化するテキストは、文字数が 8 で割り切れるように空白を埋め込む必要があります。この例では、username=test123 は 8 で割り切れるので、埋め込む必要はありません。ただし、username=test12 の場合は、末尾に 1 つの空白が必要です。

次の PHP コードは、perl 暗号化に一致する暗号化を返します。

$theKey="A398A398A398A398A398A398000000000000000000000000";
 $key = pack("H*", $theKey);
$input = "username=test123";

$strEncodedEnc=mcrypt_ecb (MCRYPT_3DES, $key, $input, MCRYPT_ENCRYPT);
$strEncodedEnc64=base64_encode($strEncodedEnc);
echo $strEncodedEnc . "<br />";
echo $strEncodedEnc64 . "<br />";
于 2012-01-18T06:59:52.523 に答える
2

おお、これは楽しい!

> hex clear_text
0000  75 73 65 72 6e 61 6d 65  3d 74 65 73 74 31 32 33  username =test123

> openssl des3 -in clear_text -out crypt_text
enter des-ede3-cbc encryption password: 123412341234123412341234
Verifying - enter des-ede3-cbc encryption password: 123412341234123412341234

> hex crypt_text
0000  53 61 6c 74 65 64 5f 5f  d7 1b 37 a6 e0 c4 99 d1  Salted__ ..7.....
0010  ce 39 7f 87 5e 8b e8 8a  27 ca 39 41 58 01 38 16  .9..^... '.9AX.8.
0020  a5 2b c8 14 ed da b7 d5                           .+......

> base64 crypt_text
U2FsdGVkX1/XGzem4MSZ0c45f4dei+iKJ8o5QVgBOBalK8gU7dq31Q==

> openssl version
OpenSSL 0.9.8k 25 Mar 2009

> base64 --version | head -n 1
base64 (GNU coreutils) 7.1

有能な人がここに現れない限り、メーリングリスト openssl-users または dev-tech-crypto@mozilla を試してみてください。

于 2010-05-12T09:56:45.893 に答える
2

ほとんどの夜を過ごしましたが、これは@Eric Kigathiのソリューションがルビーでどのように見えるかです

def encoding(key, val)
  require "openssl"
  des = OpenSSL::Cipher::Cipher.new('des-ede3')
  des.encrypt
  des.key = convert_key_to_hex_bin key

  #ENCRYPTION
  des.padding = 0 #Tell Openssl not to pad
  val += " " until val.bytesize % 8 == 0 #Pad with zeros
  edata = des.update(val) + des.final 
  b64data = Base64.encode64(edata).gsub(/\n/,'')
end

def convert_key_to_hex_bin(str)
  decoder_ring = Hash['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'.split(//).zip('0123456789ABCDEF0123456789ABCDEF0123ABCDEF0123456789ABCDEF012345'.split(//))]
  str.gsub!(/./, decoder_ring)
  [str.ljust(16*3, '0')].pack("H*")
end

ただし、注意してください。+ と / が最後に何に変換されるのかよくわかりません。私は 4 と 5 だと推測しましたが、それが本当かどうかはわかりません。

http://opensourcetester.co.uk/2012/11/29/zeros-padding-3des-ruby-openssl/暗号化コードと解説をご覧ください。

于 2013-06-02T06:29:07.993 に答える
1

ColdFusion の回答には、動作するように ccbill キーを変更する機能がありません (Eric の回答のように)。Eric の回答を Lucee Code に変更しました。それを ACF 互換のコードに戻す (ReplaceNoCase 内の構造を個々の構造に変更する) のに、多くの作業は必要ありません。

public function ccbillupgrade(string key = "XXXXXXXXXXXXXXXXXXXXXXXX", string username){

    var remote_user = padUserName("username=#arguments.username#");
    var padded_key = 
        Ucase(
            Replace(
                LJustify(
                    hexmod(arguments.key)
                , 48), // Pad key to 48 bytes (hex) 
                " ", '0', 'all'
            )
        );

    var encodedKey = ToBase64(BinaryDecode(padded_key, "HEX"));

    return Encrypt(remote_user, encodedKey, "DESEDE/ECB/NoPadding", "Base64");
}

private string function hexmod(string input) {
    return ReplaceNoCase( arguments.input,
        {
            'G' = '0', 'H' = '1',
            'I' = '2', 'J' = '3',
            'K' = '4', 'L' = '5',
            'M' = '6', 'N' = '7',
            'O' = '8', 'P' = '9',
            'Q' = 'A', 'R' = 'B',
            'S' = 'C', 'T' = 'D',
            'U' = 'E', 'V' = 'F',
            'W' = '0', 'X' = '1',
            'Y' = '2', 'Z' = '3'

        }
    );
}
private string function padUserName(string username) {
    var neededLength = Len(arguments.username) + ( 8 - Len(username) % 8 );
    return LJustify(arguments.username, neededLength);
}
于 2016-06-16T12:23:30.010 に答える
0

Crypt::TripleDES には 2 つの問題 (またはそうでない) があります。

  1. Crypt::TripleDES のキーが HEX であるという事実 (以前に ZZ Coder によって説明されました)。unpack を使用するか、 ord/sprintf または他の多くの方法を使用して、キーを 16 進数にできます。

    • $pass = unpack("H*", "あなたのパスフレーズ"); #パック/アンパック バージョン

    • $pass = join('', map { sprintf("%x",$ )} map { ord($ ) } split(//, "YOUR PASS"));

    Crypt::TripleDES はパスフレーズをスペースで埋めます (これは私にとっては問題ありませんでした)

  2. Crypt::TripleDES は、プレーン テキストのみに空白のパディングを行います。Java または PHP mcrypt_encrypt で使用される多数のパディング メソッドがあります。

    • (つまり、PKCS5、PKCS7、CMS) - パディングされたバイト数を示す同じ値のバイトでパディングします。例: "andrei" -> 16 進数: 61 6e 64 72 65 69 -> パディング: 61 6e 64 72 65 69 02 02
    • null 文字で埋める 例: 61 6e 64 72 65 69 00 00
    • スペースで埋める (Crypt::TripleDES はすでにこれを行っている)
    • パディングされたバイト数となる最後のバイトを除いて、ゼロ (ヌル文字) でパディングします。例: 61 6e 64 72 65 69 00 02
    • 0x80 の後にヌル文字を追加します。例: 61 6e 64 72 65 69 80 00

暗号文に注意してください。ある時点までは一致するが、末尾が異なる場合は、プレーンテキストのパディングの問題があります。そうしないと、パスフレーズの問題、暗号ブロック モードの問題 (EBC、CBC、..) http://www.tools4noobs.com/online_tools/encrypt/help_modes.php、またはアルゴリズムの問​​題が発生する可能性があります。

したがって、Java の暗号テキスト (ヌル文字のパディングを使用) と一致させるために Perl で行ったこと:

my $pass = unpack("H*", "MY PASS");
my $text = "bla bla bla";
my $pad = 8 - (length $text % 8);
$pad = 0 if ( $pad > 7 );
$text .= chr(00) x $pad;

my $des = new Crypt::TripleDES;
my $cipher = $des->encrypt3( $text, $pass );

お役に立てれば

于 2012-04-26T16:45:12.457 に答える