Perl/PHP/ColdFusion の TripleDES
-
26-09-2019 - |
質問
最近、TripleDES 標準を使用して、トークンとして使用する文字列の暗号化を要求する決済処理業者と API を接続する際に問題が発生しました。私たちのアプリケーションは、TripleDES をサポートする Encrypt タグを持つ ColdFusion を使用して実行されますが、返された結果は支払い処理業者が期待したものではありませんでした。
まず、支払い処理業者が期待していた結果のトークンがこれです。
AYOF+kRtg239Mnyc8QIarw==
以下は、使用していた ColdFusion のスニペットとその結果の文字列です。
<!--- 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
--->
ご覧のとおり、これは期待していた文字列を返しませんでした。解決策を求めて、このプロセスでは ColdFusion を使用せず、PHP でトークンを再現することを試みました。
今では、さまざまな言語がさまざまな方法で暗号化を実装していることに気づきました。たとえば、以前は C# アプリケーションと PHP バックエンド間の暗号化を管理していましたが、この 2 つを対話させるためにパディングをいじる必要がありましたが、私の経験では、PHP は暗号化標準に関しては一般的に動作します。
とにかく、試した PHP ソースとその結果の文字列を見てみましょう。
/* 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==
*/
ご覧のとおり、支払処理業者が予期する文字列と ColdFusion によって生成された文字列の両方とは異なる別の文字列が得られています。頭と壁の統合テクニックをキューに入れます。
支払い処理業者と何度もやり取りを行った後 (「コーディングの問題についてはサポートできません。やり方が間違っているはずです。マニュアルを読んでください」という担当者がたくさんいました)、最終的に私たちは以上のことを持っている担当者にエスカレーションされました。いくつかの脳細胞をこすり合わせることで、一歩下がって問題を実際に観察して診断することができました。
彼は、CF と PHP の試みでは正しい文字列が得られなかったことに同意しました。簡単に検索した後、彼は、それが必ずしも私たちのソースではなく、むしろ 2 つの言語が TripleDES 標準のビジョンをどのように実装したかであることに同意しました。
今朝オフィスに出社すると、Perl で書かれたソース コードのスニペットが記載された電子メールが届きました。これは、予想されるトークンを生成するために彼らが直接使用していたコードです。
#!/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==
というわけで、これで完成です。3 つの言語、ドキュメントで TripleDES 標準暗号化として引用されているものの 3 つの実装、および結果として得られる 3 つのまったく異なる文字列。
私の質問は、これら 3 つの言語とその TripleDES アルゴリズムの実装の経験から、そのうちの 2 つで同じ応答を返すことができましたか、また、できた場合、そのためにコードにどのような調整を行う必要があったのかということです。結果が出るまで?
非常にくどい質問であることは承知していますが、実行する必要があるテストの各段階について、明確かつ正確な設定を提供したいと考えました。
また、後でこのテーマについてさらに調査作業を行う予定であり、他の人がこの問題を回避できるように、この質問に関して得られた結果をすべて投稿する予定です。
解決
PerlのトリプルDESを使用すべきではありません。それは非常に多くの奇妙なことを行い、あなたが楽しみを持ってしようとしている。
あなたの最初の問題は、Perlのキーは六角であり、あなたはバイナリに変換する必要があるということです。
、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の文字です)このサンプルでラッキーです。
他のヒント
、ColdFusionの回答:
最初の問題は、あなたの鍵の長さは、トリプルDESのために正しくないということです。 ZZコーダー正しくそれが0ので正しい長さにパディングする必要があることを推測ます。
は次のステップは、キーがヘクスに変換する必要があることです。 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==
私は(会社のような音は、元の記事で言及さ)下記のCCBillのアップグレードに取り組んでたまたま誰のために以下のコードが含まれます。ここではマニュアルに記載されている以下のPHPの関数は、下記のCCBillの3DES /トリプルDES暗号化内部からの出力と一致します。 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
ああ、これは楽しいです!
> 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-ユーザーまたはDEV-ハイテク暗号@モジラ、ここで誰かに有用ショーアップしない限ります。
を試してみてください、暗号の専門家に相談してくださいZZコーダーはもうすぐそこだった。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 のキーは A398A398A398A398A398A398000000000000000000000000 (ゼロ埋め後) になります。
次に、暗号化するテキストには、文字数が 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 />";
ほとんどの夜のを連れて行ってくれたが、これはどのように@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
Doが、しかし、注意してください。私はかなり確実で何+および/変換終了時にいませんよ。私は4と5の推測が、それが本当なら、私はあなたを伝えることはできません。
のhttpへの帽子先端:// opensourcetester。 co.uk/2012/11/29/zeros-padding-3des-ruby-openssl/ の暗号化コードと解説ます。
、ColdFusionの回答(エリックの回答のように)仕事に下記のCCBillキーを変更不足しています... 私はLuceeコードにエリックの答えを変更しました。これは、バック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);
}
Crypt::TripleDES には (またはそうでない) 問題が 2 つあります。
Crypt::TripleDES のキーは 16 進数であるという事実 (ZZ Coder によって以前に説明されました)。unpack を使用するか、ord/sprintf またはその他のメソッドを使用して、キーを 16 進数化できます。
$pass = unpack("H*", "あなたのパスフレーズ");#pack/unpack バージョン
$pass = join('', マップ { sprintf("%x",$)} マップ { ord($) } split(//, "あなたのパス"));
Crypt::TripleDES はパスフレーズをスペースで埋めます (これは私にとっては問題ありませんでした)
Crypt::TripleDES はプレーンテキストのみの空白埋めを行います。Java または PHP の mcrypt_encrypt では、多数のパディング メソッドが使用されます。
- (すなわち。PKCS5、PKCS7、CMS) - パディングされるバイト数を示す同じ値のバイトでパディングします。例:"アンドレイ" -> 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 の後に null 文字を埋め込みます。例:61 6e 64 72 65 69 80 00
暗号文に注意してください。ある時点までは一致していても末尾が異なる場合は、平文のパディングの問題が発生しています。そうしないと、パスフレーズや暗号ブロック モード (EBC、CBC、...) の問題が発生する可能性があります。 http://www.tools4noobs.com/online_tools/encrypt/help_modes.php またはアルゴリズムの問題。
そこで、Java からの暗号文 (null 文字のパディングを使用) と一致させるために 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 );
お役に立てれば