Шифрование строки в PHP и расшифровка в Node.js

Я отправляю данные через небезопасное соединение между серверами Apache и Node.js. Мне нужно зашифровать данные в PHP и расшифровать в Node.js. Я потратил 2 дня, пытаясь заставить его работать, однако мне удалось получить подпись сообщения для работы, без шифрования. Я пробовал передавать AES128-CBC, AES256-CBC, DES, AES128, AES256 в качестве алгоритмов, однако ничего не получилось.

Я пробовал это в PHP:

$data = json_encode(Array('mk' => $_SESSION['key'], 'algorithm' => 'SHA1', 'username' => $_SESSION['userid'], 'expires' => $expires)); $payload = openssl_encrypt($data, 'des', '716c26ef'); return base64_encode($payload); 

И в Node.js:

 var enc_json = new Buffer(response[1], 'base64'); var decipher = crypto.createDecipher('des', '716c26ef'); var json = decipher.update(enc_json).toString('ascii'); json += decipher.final('ascii'); 

И, кроме неправильных дешифрованных данных, я получаю такие ошибки:

 TypeError: error:0606508A:digital envelope routines:EVP_DecryptFinal_ex:data not multiple of block length TypeError: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length 

Мне нужно простое шифрование, поскольку данные не слишком чувствительны (нет пароля или пользовательских данных), однако данные должны считываться только получателем. Длина ключа может быть любой, но процедура шифрования / дешифрования должна быть как можно более простой, пожалуйста, нет.

Когда речь идет о симметричном шифровании, как это, первый шаг понимает, что это, вероятно, будет огромная боль в тылу – я никогда, никогда не работал сразу, даже когда я копировал свой собственный код. Это связано главным образом с тем, что методы шифрования и дешифрования по своей сути совершенно неумолимы и редко дают полезные сообщения об ошибках. Единственный нулевой символ, возврат каретки, линейный канал или динамически преобразованный тип могут бесшумно раздувать весь процесс.

Зная это, прогресс поэтапно. Я предлагаю следующее:

Во-первых, работайте с PHP. Передайте образец текста, зашифруйте его, немедленно расшифруйте его и сравните с строгим равенством с исходной текстовой переменной. Они совершенно одинаковы? Вывод обоих, также – являются ли они одного и того же типа и кажутся совершенно незатронутыми? Следите за непечатаемыми символами – проверьте длину и кодировку символов тоже!

Теперь сделайте это выше с еще одним примером текста, который имеет 1 символ больше или меньше предыдущего. Этот вопрос об отладке блоков / нулях – это важно.

Если это работает – и это редко делает сразу, потому что трудно предсказать причины, продолжайте Node.js.

В Node.js сделайте то же самое, что и в PHP, даже если это похоже на потраченное впустую действие – по дополнительным причинам, которые будут очевидны в одно мгновение. Шифруйте и расшифруйте все вместе в Node.js. Работает ли он во всех тех же условиях, которые указаны выше?

Как только это будет сделано, здесь идет «забавная» часть: используя одни и те же методы шифрования независимо в Node.js и PHP, пусть они оба выдадут вам «окончательный» готовый к передаче cryptext, который был создан.

Если все хорошо, они должны быть совершенно одинаковыми. Если это не так, у вас возникла проблема с реализацией и методами шифрования, которые несовместимы между системами. Некоторые настройки являются неправильными или противоречивыми (возможно, с нулевым дополнением или множеством других возможностей, или IV и т. Д.), Или вам нужно попробовать другую реализацию.

Если бы мне пришлось угадать слепо, я бы сказал, что есть проблема с кодировкой и декодированием base64 (чаще всего это происходит не так). Вещи, как правило, выполняются дважды, потому что сложно отлаживать двоичные типы данных в веб-приложениях (через браузер). Иногда вещи кодируются дважды, но только декодируются один раз, или одна реализация «помогает» автоматически кодировать / декодировать что-то автоматически, не зная, что это делает и т. Д.

Также возможно, что проблема с нулевым дополнением между Node и PHP, как предлагается здесь: AES encrypt в Node.js Расшифровать в PHP. Потерпеть неудачу.

Эти последние два вопроса настоятельно рекомендуются вашими кодами ошибок. Методы шифрования предсказывают размеры блоков точной длины, а если они выключены, то это сигнализирует повреждение данных, передаваемых этим функциям, что происходит, если один дополнительный символ проскользнул или если кодирование обрабатывается по-разному и т. Д.

Если вы пройдете через каждый из вышеперечисленных за один раз, уверяя себя, что вы не можете спешить и должны проверить каждый кропотливый маленький шаг процесса, должно быть гораздо более ясно, где именно происходит что-то неправильно, а затем это может быть troubleshooted.

На этой неделе я боролся с той же проблемой, но наоборот (PHP-шифрование -> NodeJS-расшифровки), и ему удалось получить этот фрагмент:

aes256cbc.js

 var crypto = require('crypto'); var encrypt = function (plain_text, encryptionMethod, secret, iv) { var encryptor = crypto.createCipheriv(encryptionMethod, secret, iv); return encryptor.update(plain_text, 'utf8', 'base64') + encryptor.final('base64'); }; var decrypt = function (encryptedMessage, encryptionMethod, secret, iv) { var decryptor = crypto.createDecipheriv(encryptionMethod, secret, iv); return decryptor.update(encryptedMessage, 'base64', 'utf8') + decryptor.final('utf8'); }; var textToEncrypt = new Date().toISOString().substr(0,19) + '|My super secret information.'; var encryptionMethod = 'AES-256-CBC'; var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length var iv = secret.substr(0,16); var encryptedMessage = encrypt(textToEncrypt, encryptionMethod, secret, iv); var decryptedMessage = decrypt(encryptedMessage, encryptionMethod, secret, iv); console.log(encryptedMessage); console.log(decryptedMessage); 

aes256cbc.php

 <?php date_default_timezone_set('UTC'); $textToEncrypt = substr(date('c'),0,19) . "|My super secret information."; $encryptionMethod = "AES-256-CBC"; $secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length $iv = substr($secret, 0, 16); $encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secret,0,$iv); $decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secret,0,$iv); echo "$encryptedMessage\n"; echo "$decryptedMessage\n"; ?> 

Секрет здесь, чтобы избежать проблем с ключом / iv размером / расшифровкой, состоит в том, чтобы иметь секрет ровно 32 символов и 16 для IV. Кроме того, очень важно использовать «base64» и «utf8» в NodeJS, поскольку они являются значениями по умолчанию в PHP.

Вот несколько примеров:

 $ node aes256cbc.js && php aes256cbc.php zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw== 2015-01-27T18:29:12|My super secret information. zra3FX4iyCc7qPc1dZs+G3ZQ40f5bSw8P9n5OtWl1t86nV5Qfh4zNRPFbsciyyHyU3Qi4Ga1oTiTwzrPIZQXLw== 2015-01-27T18:29:12|My super secret information. $ node aes256cbc.js && php aes256cbc.php zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA== 2015-01-27T18:29:15|My super secret information. zra3FX4iyCc7qPc1dZs+G6B6+8aavHNc/Ymv9L6Omod8Di3tMbvOa2B7O2Yiyoutm9fy9l0G+P5VJT9z2qNESA== 2015-01-27T18:29:15|My super secret information. $ node aes256cbc.js && php aes256cbc.php zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw== 2015-01-27T18:29:29|My super secret information. zra3FX4iyCc7qPc1dZs+G4oD1Fr5yLByON6QDE56UOqP6kkfGJzpyH6TbwZYX2oGlh2JGv+aHYUMh0qQnAj/uw== 2015-01-27T18:29:29|My super secret information. $ node aes256cbc.js && php aes256cbc.php zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA== 2015-01-27T18:29:31|My super secret information. zra3FX4iyCc7qPc1dZs+G5OVCbCaUy8a0LLF+Bn8UT4X3nYbtynO0Zt2mvXnnli9dRxrxMw43uWnkh8MIwVHXA== 2015-01-27T18:29:31|My super secret information. $ node aes256cbc.js && php aes256cbc.php fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA== 2015-01-27T18:30:08|My super secret information. fdsqSyHBJjlwD0jYfOUZM2FrONG6Fk5d7FOItYEdbnaZIhhmg/apa8/jPwKFkDXD9eNqWC3w0JzY5wjtZADiBA== 2015-01-27T18:30:08|My super secret information. $ node aes256cbc.js && php aes256cbc.php fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw== 2015-01-27T18:30:45|My super secret information. fdsqSyHBJjlwD0jYfOUZM4SRfi6jG5EoDFEF6d9xCIyluXSiMaKlhd89ovpeOz/YyEIlPbYR4ly00gf6hWfKHw== 2015-01-27T18:30:45|My super secret information. 

ЗАМЕТКА:

Я использую формат « timestamp | message », чтобы избегать людей в средних атаках . Например, если зашифрованное сообщение содержит идентификатор, подлежащий аутентификации, MitM может захватывать сообщение и повторно отправлять его каждый раз, когда он хочет повторно аутентифицироваться.

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

РЕДАКТИРОВАТЬ:

Здесь я неправильно использовал Инициализационный вектор (IV). Как @ArtjomB. пояснил, что IV должна быть первой частью зашифрованного сообщения, а также должна быть случайной величиной. Также рекомендуется использовать значение hmac в заголовке HTTP ( x-hmac: *value* ), чтобы проверить, что сообщение было создано из действительного источника (но это не относится к проблеме с сообщением «повторная передача», описанной ранее ).

Вот улучшенная версия, включая hmac для php и node и IV как часть зашифрованного сообщения:

aes256cbc.js (v2)

 var crypto = require('crypto'); var encrypt = function (message, method, secret, hmac) { //var iv = crypto.randomBytes(16).toString('hex').substr(0,16); //use this in production var iv = secret.substr(0,16); //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors) var encryptor = crypto.createCipheriv(method, secret, iv); var encrypted = new Buffer(iv).toString('base64') + encryptor.update(message, 'utf8', 'base64') + encryptor.final('base64'); hmac.value = crypto.createHmac('md5', secret).update(encrypted).digest('hex'); return encrypted; }; var decrypt = function (encrypted, method, secret, hmac) { if (crypto.createHmac('md5', secret).update(encrypted).digest('hex') == hmac.value) { var iv = new Buffer(encrypted.substr(0, 24), 'base64').toString(); var decryptor = crypto.createDecipheriv(method, secret, iv); return decryptor.update(encrypted.substr(24), 'base64', 'utf8') + decryptor.final('utf8'); } }; var encryptWithTSValidation = function (message, method, secret, hmac) { var messageTS = new Date().toISOString().substr(0,19) + message; return encrypt(messageTS, method, secret, hmac); } var decryptWithTSValidation = function (encrypted, method, secret, hmac, intervalThreshold) { var decrypted = decrypt(encrypted, method, secret, hmac); var now = new Date(); var year = parseInt(decrypted.substr(0,4)), month = parseInt(decrypted.substr(5,2)) - 1, day = parseInt(decrypted.substr(8,2)), hour = parseInt(decrypted.substr(11,2)), minute = parseInt(decrypted.substr(14,2)), second = parseInt(decrypted.substr(17,2)); var msgDate = new Date(Date.UTC(year, month, day, hour, minute, second)) if (Math.round((now - msgDate) / 1000) <= intervalThreshold) { return decrypted.substr(19); } } var message = 'My super secret information.'; var method = 'AES-256-CBC'; var secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length var hmac = {}; //var encrypted = encrypt(message, method, secret, hmac); //var decrypted = decrypt(encrypted, method, secret, hmac); var encrypted = encryptWithTSValidation(message, method, secret, hmac); var decrypted = decryptWithTSValidation(encrypted, method, secret, hmac, 60*60*12); //60*60m*12=12h console.log("Use HTTP header 'x-hmac: " + hmac.value + "' for validating against MitM-attacks."); console.log("Encrypted: " + encrypted); console.log("Decrypted: " + decrypted); 

Обратите внимание, что crypto.createHmac(...).digest('hex') переваривается hex . Это значение по умолчанию для PHP для hmac .

aes256cbc.php (v2)

 <?php function encrypt ($message, $method, $secret, &$hmac) { //$iv = substr(bin2hex(openssl_random_pseudo_bytes(16)),0,16); //use this in production $iv = substr($secret, 0, 16); //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors) $encrypted = base64_encode($iv) . openssl_encrypt($message, $method, $secret, 0, $iv); $hmac = hash_hmac('md5', $encrypted, $secret); return $encrypted; } function decrypt ($encrypted, $method, $secret, $hmac) { if (hash_hmac('md5', $encrypted, $secret) == $hmac) { $iv = base64_decode(substr($encrypted, 0, 24)); return openssl_decrypt(substr($encrypted, 24), $method, $secret, 0, $iv); } } function encryptWithTSValidation ($message, $method, $secret, &$hmac) { date_default_timezone_set('UTC'); $message = substr(date('c'),0,19) . "$message"; return encrypt($message, $method, $secret, $hmac); } function decryptWithTSValidation ($encrypted, $method, $secret, $hmac, $intervalThreshold) { $decrypted = decrypt($encrypted, $method, $secret, $hmac); $now = new DateTime(); $msgDate = new DateTime(str_replace("T"," ",substr($decrypted,0,19))); if (($now->getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) { return substr($decrypted,19); } } $message = "My super secret information."; $method = "AES-256-CBC"; $secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length //$encrypted = encrypt($message, $method, $secret, $hmac); //$decrypted = decrypt($encrypted, $method, $secret, $hmac); $encrypted = encryptWithTSValidation($message, $method, $secret, $hmac); $decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.\n"; echo "Encrypted: $encrypted\n"; echo "Decrypted: $decrypted\n"; ?> 

Вот несколько примеров:

 $ node aes256cbc.js && php aes256cbc.php Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN Decrypted: My super secret information. Use HTTP header 'x-hmac: 6862972ef0f463bf48523fc9e334bb42' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==I6cAKeoxeSP5TGgtK59PotB/iG2BUSU8Y6NhAhVabN9UB+ZCTn7q2in4JyLwQiGN Decrypted: My super secret information. $ node aes256cbc.js && php aes256cbc.php Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW Decrypted: My super secret information. Use HTTP header 'x-hmac: b2e63f216acde938a82142220652cf59' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CSgYBBR8dkZytORm8xwEDmD9WB1mpqC3XnSrB+wR3/KW Decrypted: My super secret information. $ node aes256cbc.js && php aes256cbc.php Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ Decrypted: My super secret information. Use HTTP header 'x-hmac: 73181744453d55eb6f81896ffd284cd8' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CTGik4Lv9PnWuEg5SiADJcdKX1to0LrNKmuCiYIweBAZ Decrypted: My super secret information. $ node aes256cbc.js && php aes256cbc.php Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8 Decrypted: My super secret information. Use HTTP header 'x-hmac: 5372ecca442d65f582866cf3b24cb2b6' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==YsFRdKzCLuCk7Yg+U+S1CYEITF6aozBNp7bA54qY0Ugg9v6ktwoH6nqRyatkFqy8 Decrypted: My super secret information. 

И последнее, но не менее важное: если у вас нет установленного в php mcrypt openssl mod, вы можете использовать mcrypt вместо rijndael128 и pkcs7 padding ( source ) следующим образом:

aes256cbc-mcrypt.php (v2)

 <?php function pkcs7pad($message) { $padding = 16 - (strlen($message) % 16); return $message . str_repeat(chr($padding), $padding); } function pkcs7unpad($message) { $padding = ord(substr($message, -1)); //get last char and transform it to Int return substr($message, 0, -$padding); //remove the last 'padding' string } function encrypt ($message, $method, $secret, &$hmac) { //$iv = substr(bin2hex(mcrypt_create_iv(mcrypt_get_iv_size($method, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM)),0,16); //use this in production $iv = substr($secret, 0, 16); //using this for testing purposes (to have the same encryption IV in PHP and Node encryptors) $message = pkcs7pad($message); $encrypted = base64_encode($iv) . base64_encode(mcrypt_encrypt($method, $secret, $message, MCRYPT_MODE_CBC, $iv)); $hmac = hash_hmac('md5', $encrypted, $secret); return $encrypted; } function decrypt ($encrypted, $method, $secret, $hmac) { if (hash_hmac('md5', $encrypted, $secret) == $hmac) { $iv = base64_decode(substr($encrypted, 0, 24)); return pkcs7unpad(mcrypt_decrypt($method, $secret , base64_decode(substr($encrypted, 24)) , MCRYPT_MODE_CBC, $iv)); } } function encryptWithTSValidation ($message, $method, $secret, &$hmac) { date_default_timezone_set('UTC'); $message = substr(date('c'),0,19) . "$message"; return encrypt($message, $method, $secret, $hmac); } function decryptWithTSValidation ($encrypted, $method, $secret, $hmac, $intervalThreshold) { $decrypted = decrypt($encrypted, $method, $secret, $hmac); $now = new DateTime(); //echo "Decrypted: $decrypted\n"; $msgDate = new DateTime(str_replace("T"," ",substr($decrypted,0,19))); if (($now->getTimestamp() - $msgDate->getTimestamp()) <= $intervalThreshold) { return substr($decrypted,19); } } $message = "My super secret information."; $method = MCRYPT_RIJNDAEL_128; $secret = "My32charPasswordAndInitVectorStr"; //must be 32 char length //$encrypted = encrypt($message, $method, $secret, $hmac); //$decrypted = decrypt($encrypted, $method, $secret, $hmac); $encrypted = encryptWithTSValidation($message, $method, $secret, $hmac); $decrypted = decryptWithTSValidation($encrypted, $method, $secret, $hmac, 60*60*12); //60*60m*12=12h echo "Use HTTP header 'x-hmac: $hmac' for validating against MitM-attacks.\n"; echo "Encrypted: $encrypted\n"; echo "Decrypted: $decrypted\n"; ?> 

Конечно, некоторые тесты:

 $ php aes256cbc-mcrypt.php && node aes256cbc.js Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU Decrypted: My super secret information. Use HTTP header 'x-hmac: 801282a9ed6b2d5bd2254140d7a17582' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v9IyatyGeNT2yebrpJZ5xH73H5fFcV1zhqhRGwM0ToGU Decrypted: My super secret information. $ php aes256cbc-mcrypt.php && node aes256cbc.js Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM Decrypted: My super secret information. Use HTTP header 'x-hmac: 0ab2bc83108e1e250f6ecd483cd65329' for validating against MitM-attacks. Encrypted: YjE0ZzNyMHNwVm50MGswbQ==ipQ+Yah8xoF0C6yjCJr8v79P+j4YUl8ln8eu7FDqEdbxMe1Z7BvW8iVUN1qFCiHM Decrypted: My super secret information.