TripleDES в Perl / PHP / ColdFusion

В последнее время возникла проблема с подключением 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, вы могли заставить всех двух из них дать тот же ответ, и если да, то какие настройки кода вам нужно сделать в порядке прийти к результату?

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

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

Related of "TripleDES в Perl / PHP / ColdFusion"

Нельзя использовать TripleDES от 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 символов).

Ответ Coldfusion:

Первая проблема заключается в том, что длина ключа неверна для 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== 

Я приведу код ниже для тех, кто, возможно, работает над обновлением CCBill (что звучит как компания, упомянутая в исходном сообщении). Функции PHP, приведенные ниже, будут соответствовать выходному данным внутреннего шифрования CCES 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 GZ/gz to 0-f (See @Jinyo's Post) //Necessary to match CCBill's Encryption function hexmod($str){ //Convert GZ & gz 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-users или dev-tech-crypto @ mozilla, если только здесь не появится полезная информация.

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

Во-первых, всякий раз, когда есть недопустимые шестнадцатеричные буквы (буквы после F), замените их в соответствии со следующим правилом:

  • G-> 0
  • H-> 1
  • I-> 2
  • J-> 3
  • P-> 9
  • Q-> а
  • R-> B
  • V-> F
  • W-> 0
  • Z-> 3

Используя этот метод, ключом для AZ98AZ98AZ98AZ98AZ98AZ98 является A398A398A398A398A398A398000000000000000000000000 (после заполнения нулями).

Во-вторых, текст, который нужно зашифровать, должен быть дополнен пробелами, чтобы количество символов делилось на 8. В этом примере имя пользователя = 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 Answer отсутствует модификация ключа ccbill для работы (как в ответе Эрика) … Я изменил ответ Эрика на Lucee Code. Это не должно занять много времени, чтобы вернуть его в 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 являются HEX (объясняется ранее ZZ Coder). Вы можете использовать свой ключ, используя распаковку или используя команду ord / sprintf или кучу других методов:

    • $ pass = unpack ("H *", "YOUR PASSPHRASE"); # pack / unpack version

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

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

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

    • (т. е. PKCS5, PKCS7, CMS) – колодка с байтами того же значения, указывающая количество заполненных байтов, например: «andrei» -> hex: 61 6e 64 72 65 69 -> padded: 61 6e 64 72 65 69 02 02
    • например, 61 6e 64 72 65 69 00 00
    • pad с пробелами (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 ); 

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