12

最近出现了一个关于使用 TripleDES 标准将 API 与请求加密字符串以用作令牌的支付处理器连接的问题。我们的应用程序使用 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 算法的实现的经验,您是否能够让它们中的任何两种给出相同的响应,如果是的话,您必须对代码进行哪些调整得出结果?

我知道这是一个非常冗长的问题,但我想为我们必须执行的每个测试阶段提供清晰准确的设置。

稍后我还将对此主题进行更多调查工作,并将发布我对这个问题提出的任何发现,以便其他人可以避免这种头痛。

4

8 回答 8

8

永远不要使用 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 个字符)。

于 2010-05-12T10:25:13.437 回答
5

冷融合答案:

第一个问题是您的密钥长度对于 Triple DES 不正确。ZZ Coder 正确推断出它需要用 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==
于 2010-05-12T13:28:05.490 回答
4

我将为碰巧正在从事 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
于 2012-08-12T19:52:39.490 回答
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

ZZ Coder 快到了。对于 Perl 和 PHP 代码返回不同加密的原因,还有一些注意事项。

首先,每当有无效的十六进制字母(F后面的字母)时,按照以下规则替换它们:

  • G->0
  • H->1
  • 我->2
  • J->3
  • ...
  • P->9
  • 问->答
  • R->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 />";
于 2012-01-18T06:59:52.523 回答
2

花了我一个晚上的大部分时间,但这就是@Eric Kigathi 的解决方案在 ruby​​ 中的样子

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 代码的答案。将其恢复为 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 有两​​个(或没有)问题:

  1. Crypt::TripleDES 的密钥是 HEX 的事实(前面由 ZZ Coder 解释过)。您可以使用 unpack 或使用 ord/sprintf 或一堆其他方法来十六进制您的密钥:

    • $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”-> 十六进制: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 );

希望这可以帮助

于 2012-04-26T16:45:12.457 回答