代码之家  ›  专栏  ›  技术社区  ›  Seidr

Perl/PHP/ColdFusion中的三倍

  •  12
  • Seidr  · 技术社区  · 15 年前

    最近出现了一个问题,即如何将一个API与一个付款处理器连接起来,后者请求使用Tripledes标准对一个字符串进行加密以用作令牌。我们的应用程序使用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后端之间的加密时,为了让这两种语言交谈,我不得不使用填充,但我的经验是,当涉及到加密标准时,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算法的实现,您是否能够让它们中的任何两种给出相同的响应,如果是这样,为了得到结果,您必须对代码进行哪些调整?

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

    稍后,我还会对这一主题进行更多的调查,并将我提出的任何调查结果发布到这个问题上,以便其他人避免这种头痛。

    8 回复  |  直到 9 年前
        1
  •  8
  •   ZZ Coder    15 年前

    不应该使用Perl的三元组。它做了那么多奇怪的事情,你会很开心的。

    第一个问题是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个字符)。

        2
  •  5
  •   Edward M Smith    15 年前

    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==
    
        3
  •  4
  •   Eric Kigathi    13 年前

    我将为任何一个恰巧在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
    
        4
  •  2
  •   daxim Fayland Lam    15 年前

    哦,这很有趣!

    > 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 tech crypto@mozilla,除非有有用的人出现在这里。

        5
  •  2
  •   jinyo    14 年前

    ZZ编码员就在附近。对于Perl和PHP代码返回不同加密的原因,还有一些警告。

    首先,如果有无效的十六进制字母(F后面的字母),请按照以下规则进行替换:

    • g& gt;0
    • H & GT;1
    • I & GT;2
    • J & GT;3
    • p & gt;9
    • q & gt;a
    • R & GT;B
    • V & gt;f
    • w & gt;0
    • Z & GT;3

    使用此方法,az98az98az98az98az98az98的键是a398a398a398a398a398a398000000000000000000(用零填充后)。

    其次,要加密的文本应该用空格填充,这样字符数就可以被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 />";
    
        6
  •  2
  •   kayakyakr    12 年前

    花了我一个晚上的时间,但这就是@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/ 密码和注释。

        7
  •  1
  •   Ben    9 年前

    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);
    }
    
        8
  •  0
  •   andrei    10 年前

    crypt::tripledes有两个问题(或没有):

    1. crypt::tripledes的键是十六进制的事实(前面由zz coder解释)。您可以使用unpack或使用ord/sprintf或一系列其他方法来十六进制键:

      • $pass=unpack(“h*”,“your passphrase”);pack/unpack版本

      • $pass=join(“”,映射sprintf(“%x”),$ }映射{ORD($) )}拆分(//,“您的通行证”);

      crypt::tripledes用空格填充密码短语(对我来说没问题)

    2. tripledes只对纯文本进行空白填充。在Java或PHP McLogTyCyrPyt上使用了许多填充方法:

      • (即pkcs5、pkcs7、cms)-用表示填充字节数的相同值的字节填充,例如:“anderi”->hex:61 6e 64 72 65 69->填充:61 6e 64 72 65 69 02
      • 带空字符的pad例如: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 );
    

    希望这有帮助