#php #encryption #hash #rsa
#php #шифрование #хэш #rsa
Вопрос:
Я получаю XML в запросе от API веб-службы. Он содержит подпись, подписанную закрытым ключом запрашивающего, и я должен проверить ее с помощью открытого ключа запрашивающего. Затем я должен отправить ответ с подписью, подписанной моим закрытым ключом.
Этот процесс должен выполняться с помощью RSA и SHA-256 на PHP.
В настоящее время у меня есть следующий код:
$data_to_encrypt = "MsgBody..../MsgBody"; // xml
$msgbody = simplexml_load_string($data_to_encrypt);
$result = $msgbody->xpath('//MsgBody');
openssl_private_encrypt(json_encode($result), $encrypted, $private_key, OPENSSL_PKCS1_PADDING);
$signature = $encrypted;
$verify = openssl_verify($encrypt, $signature ,$publick_key, OPENSSL_ALGO_SHA256);
Результат $verify= 0. Почему это плохая проверка?
Комментарии:
1. на чем вы застряли? Скорее всего, используется openssl_sign , но, чтобы быть уверенным, вам следует запросить документы или более подробную информацию у указанного запрашивающего
2. Используйте
openssl_sign
вместо этого. Вероятно, при использовании отсутствует индикация хэш-функцииopenssl_private_encrypt
. Вся эта функция должна быть удалена.
Ответ №1:
Аналог openssl_private_encrypt
is openssl_public_decrypt
. Оба метода допускают низкоуровневую подпись / проверку с дополнением PKCS # 1 v1.5 (RSASSA-PKCS1-v1_5), где данные неявно не хэшируются, и идентификатор используемого дайджеста не добавляется к хэшу. То есть, чтобы результат соответствовал PKCS # 1 v1.5 заполнение, оба должны быть выполнены явно.
Напротив, хеширование и добавление идентификатора дайджеста неявно учитываются в openssl_sign
и openssl_verify
в контексте заполнения RSA и PKCS # 1 версии 5, поэтому подпись автоматически соответствует заполнению PKCS # 1 версии5.
Как правило, последний является более эффективным способом подписи / проверки. Однако openssl_private_encrypt
оно также имеет применение, а именно, когда для подписи доступны не сами подписываемые данные, а только уже хэшированные данные.
Проблема в коде заключается в сочетании openssl_private_encrypt
и openssl_verify
. Конечно, вы можете объединить оба, но тогда вам придется реализовать хеширование и добавление идентификатора дайджеста для openssl_private_encrypt
, который отсутствует в коде. В качестве альтернативы (как уже отмечалось в комментариях) openssl_sign
можно применить, что здесь более эффективно.
Другое несоответствие заключается в том, что подписание и проверка должны выполняться на одних и тех же данных. В коде json_encode($result)
используется для подписи и $encrypt
(из которого json_encode($result)
был получен) для проверки.
Следующий PHP-код демонстрирует комбинацию openssl_private_encrypt
и openssl_verify
(см. Тест 1):
function getRSAKeys(){
$keyPairResource = openssl_pkey_new(array("private_key_bits" => 2048, "private_key_type" => OPENSSL_KEYTYPE_RSA));
openssl_pkey_export($keyPairResource, $privateKey);
return [$privateKey, openssl_pkey_get_details($keyPairResource)["key"]];
}
// Create test key
$newKeyPair = getRSAKeys();
$privateKey = $newKeyPair[0];
$publicKey = $newKeyPair[1];
// Test 1: openssl_private_encrypt and openssl_verify
$dataToSign = 'Test 1: The data to sign'; // Could correspond to e.g. json_encode($result) in the code
$dataToSignHashed = hash('sha256', $dataToSign, true);
$dataToSignHashedWithID = hex2bin("3031300d060960864801650304020105000420") . $dataToSignHashed; // ID from https://www.rfc-editor.org/rfc/rfc8017#page-47
openssl_private_encrypt($dataToSignHashedWithID, $signature, $privateKey, OPENSSL_PKCS1_PADDING);
$verified = openssl_verify($dataToSign, $signature, $publicKey, OPENSSL_ALGO_SHA256);
print($verified) . PHP_EOL;
// Test 2: openssl_sign and openssl_verify
$dataToSign = 'Test 2: The data to sign'; // Could correspond to e.g. json_encode($result) in the code
openssl_sign($dataToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$verified = openssl_verify($dataToSign, $signature, $publicKey, OPENSSL_ALGO_SHA256);
print($verified) . PHP_EOL;
// Test 3: openssl_private_encrypt and openssl_public_decrypt (without hashing and adding the digest id)
$dataToSign = 'Test 3: The data to sign'; // Could correspond to e.g. json_encode($result) in the code
openssl_private_encrypt($dataToSign, $signature, $privateKey, OPENSSL_PKCS1_PADDING);
openssl_public_decrypt($signature, $decrypted, $publicKey, OPENSSL_PKCS1_PADDING);
print($dataToSign === $decrypted) . PHP_EOL;
Редактировать: последний пример следует понимать чисто технически и должен продемонстрировать, что шифрование с openssl_private_encrypt
помощью без хеширования и без добавления идентификатора может быть расшифровано с openssl_public_decrypt
помощью . На практике при подписании применяется хеширование, см. Комментарий келалаки и, например, здесь . Оба метода не предназначены для подписи / проверки сообщения напрямую, но, как уже упоминалось выше, позволяют пользователю подписывать / проверять уже хэшированное сообщение.
Комментарии:
1. Следует отметить, что хэширование перед подписанием имеет решающее значение для безопасности.