Perl/PHP/ColdFusion 中的 TripleDES
-
26-09-2019 - |
题
最近出现了一个问题,涉及将 API 与支付处理器连接,该支付处理器请求使用 TripleDES 标准对字符串进行加密以用作令牌。我们的应用程序使用 ColdFusion 运行,它有一个加密标签 - 支持 TripleDES - 但是我们得到的结果并不是支付处理器所期望的。
首先,这是支付处理器期望的结果令牌。
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 后端之间的加密时,我必须使用填充才能让两者进行对话,但是我的经验是,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 尝试没有得到正确的字符串。经过快速搜索后,他也同意这不一定是我们的来源,而是这两种语言如何实现 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==
因此,我们有它。三种语言,他们在文档中引用的 TripleDES 标准加密的三种实现,以及三种完全不同的结果字符串。
我的问题是,根据您对这三种语言及其 TripleDES 算法实现的经验,您是否能够让其中任何两种语言给出相同的响应,如果是的话,您必须对代码进行哪些调整才能得出结果吗?
我知道这是一个非常冗长的问题,但我想为我们必须执行的每个测试阶段提供清晰而精确的设置。
稍后我还将对这个主题进行更多的调查工作,并将发布我对这个问题的任何发现,以便其他人可以避免这个头痛。
解决方案
不应该被使用的Perl的TripleDes的。它这样做很多奇怪的事情,你将有乐趣。
您的第一个问题是,在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 / TripleDes的如这里所述文档中所述内部加密的输出: 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用户或开发的高科技-密码@ Mozilla浏览器,除非有人有用显示在这里。
ZZ Coder 就快到了。对于 Perl 和 PHP 代码返回不同加密的原因,还有一些注意事项。
首先,每当出现无效的十六进制字母(F后面的字母)时,请按照以下规则进行替换:
- G->0
- H->1
- 我->2
- J->3
- ...
- P->9
- 问->答
- 右->B
- ...
- V→F
- W->0
- ...
- Z->3
使用这种方法,AZ98AZ98AZ98AZ98AZ98AZ98的密钥是A398A398A398A398A398A398000000000000000000000000(用零填充后)。
其次,要加密的文本应该用空格填充,使字符数可以被8整除。在此示例中,username=test123 可以被 8 整除,因此不需要填充。但是,如果 username=test12,则末尾需要一个空格。
以下 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
千万要小心,虽然。我不太清楚是什么+和/转换到结尾。我猜在4和5,但我不能告诉你,如果这是真的。
帽尖到 HTTP:// opensourcetester。 co.uk/2012/11/29/zeros-padding-3des-ruby-openssl/ 中的加密代码和评注。
在ColdFusion的答案缺少修改CCBill的重点工作(如在Eric的回答)... 我已经修改了埃里克的回答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 有两个问题(或没有):
事实上,Crypt::TripleDES 的密钥是十六进制(ZZ Coder 之前解释过)。您可以通过使用 unpack 或使用 ord/sprintf 或许多其他方法来十六进制您的密钥:
$pass = unpack("H*", "您的密码");#打包/解压版本
$pass = join('', 地图 { sprintf("%x",$)} 地图 { 订单($) } split(//, "你的通行证"));
Crypt::TripleDES 用空格填充密码短语(这对我来说没问题)
Crypt::TripleDES 仅对纯文本进行空白填充。Java 或 PHP mcrypt_crypt 使用了多种填充方法:
- (IE。PKCS5、PKCS7、CMS) - 用相同值的字节填充,指示填充的字节数,例如:“安德烈”-> 十六进制:61 6e 64 72 65 69 -> 填充:61 6e 64 72 65 69 02 02
- 用空字符填充例如: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 或者算法问题。
所以我在 Perl 中做了什么,以便能够匹配 Java 中的密文(使用空字符填充):
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 );
希望这可以帮助