TripleDES в Perl / PHP / ColdFusion - PullRequest
       88

TripleDES в Perl / PHP / ColdFusion

12 голосов
/ 12 мая 2010

Недавно возникла проблема, связанная с подключением API к процессору платежей, который запрашивал шифрование строки для использования в качестве токена с использованием стандарта TripleDES. Наши приложения запускаются с использованием ColdFusion, который имеет тег Encrypt, который поддерживает 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, вы смогли заставить любые два из них дать одинаковый ответ, и если да, то какие настройки для кода были вам необходимы? сделать для того, чтобы прийти к результату?

Я понимаю, что это очень длительный вопрос, но я хотел дать четкую и точную настройку для каждого этапа тестирования, который мы должны были выполнить.

Позже я также выполню еще несколько следственных работ по этому вопросу и опубликую любые выводы, которые я приду к этому вопросу, чтобы другие могли избежать этой головной боли.

Ответы [ 8 ]

8 голосов
/ 12 мая 2010

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 символов).

5 голосов
/ 12 мая 2010

Coldfusion Ответ:

Первая проблема заключается в том, что длина вашего ключа неверна для Triple DES. Кодер ZZ правильно сделал вывод, что его нужно дополнить до правильной длины нулями.

Следующим шагом является преобразование ключа в шестнадцатеричный код. Для этого в 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==
4 голосов
/ 12 августа 2012

Я включу приведенный ниже код для всех, кто работает над обновлением CCBill (похоже на компанию, указанную в исходном посте). Функции PHP, приведенные ниже, будут соответствовать выходным данным внутреннего шифрования 3DES / TripleDES CCBill, как описано в документации здесь: 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
2 голосов
/ 02 июня 2013

Я провел большую часть вечера, но так выглядит решение @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/ код шифрования и комментарий.

2 голосов
/ 18 января 2012

ZZ кодер был почти там. Еще несколько предостережений о том, почему коды Perl и PHP возвращают разные шифры.

Во-первых, при наличии недопустимых шестнадцатеричных букв (букв после 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, то в конце ему нужен был бы один пробел.

Следующий код 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 />";
2 голосов
/ 12 мая 2010

О, это весело!

> 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, если здесь не появится кто-то полезный.

1 голос
/ 16 июня 2016

В ответе ColdFusion отсутствует изменение действующей клавиши ccbill (как в ответе Эрика) ... Я изменил ответ Эрика на код Люси. Не нужно много работать, чтобы вернуть его в 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);
}
0 голосов
/ 26 апреля 2012

Есть две проблемы (или нет) с Crypt :: TripleDES:

  1. Тот факт, что ключи для Crypt :: TripleDES являются HEX (объяснено ранее ZZ Coder). Вы можете зашифровать свой ключ, используя unpack или ord / sprintf, или несколько других методов:

    • $ pass = unpack ("H *", "YOUR PASSPHRASE"); # версия упаковки / распаковки

    • $ pass = join ('', map {sprintf ("% x", $ )} map {ord ($ )} split (//, "YOUR PASS")) ;

    Crypt :: TripleDES дополняет парольную фразу пробелами (что было хорошо для меня)

  2. Crypt :: TripleDES выполняет заполнение пробелами только простого текста. Существует множество методов заполнения, которые используются в Java или PHP. Mcrypt_encrypt:

    • . 69 02 02
    • пэд с нулевыми символами, например: 61 6e 64 72 65 69 00 00
    • блокнот с пробелами (Crypt :: TripleDES уже делает это)
    • pad с нулями (нулевыми символами) за исключением последнего байта, который будет количеством дополняемых байтов, например: 61 6e 64 72 65 69 00 02
    • pad с 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 );

Надеюсь, это поможет

...