最近出现了一个问题,涉及将 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 有两​​个问题(或没有):

  1. 事实上,Crypt::TripleDES 的密钥是十六进制(ZZ Coder 之前解释过)。您可以通过使用 unpack 或使用 ord/sprintf 或许多其他方法来十六进制您的密钥:

    • $pass = unpack("H*", "您的密码");#打包/解压版本

    • $pass = join('', 地图 { sprintf("%x",$)} 地图 { 订单($) } split(//, "你的通行证"));

    Crypt::TripleDES 用空格填充密码短语(这对我来说没问题)

  2. 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 );

希望这可以帮助

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top