Проверка подписи RSA на основе OpenPGP с помощью WinCrypt / CryptoAPI

У меня есть код, который анализирует пакеты OpenPGP, и у меня есть n, e пакета открытого ключа, а также s пакета подписи в виде байтовых массивов.

Для проверки подписи я сначала инициализирую CryptAcquireContext (Я тоже пробовал с PROV_RSA_FULL вместо PROV_RSA_AES)

HCRYPTPROV hCryptProv;
CryptAcquireContext(&hCryptProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);

затем создайте хеш

HCRYPTHASH hHash;
CryptCreateHash(hCryptProv, CALG_SHA1, 0, 0, &hHash); // as the digest algorithm of the signature was 2 => SHA1

и заполнить его, используя CryptHashData, Это работает так же хорошо, как анализ и импорт открытого ключа с использованием CryptImportKey,

typedef struct _RSAKEY
{
BLOBHEADER blobheader;
RSAPUBKEY rsapubkey;
BYTE n[4096 / 8];
} RSAKEY;

static int verify_signature_rsa(HCRYPTPROV hCryptProv, HCRYPTHASH hHash, public_key_t &p_pkey, signature_packet_t &p_sig)
{
int i_n_len = mpi_len(p_pkey.key.sig.rsa.n); // = 512; p_pkey.key.sig.rsa.n is of type uint8_t n[2 + 4096 / 8];
int i_s_len = mpi_len(p_sig.algo_specific.rsa.s); // = 256; p_sig.algo_specific.rsa.s is of type uint8_t s[2 + 4096 / 8]

HCRYPTKEY hPubKey;
RSAKEY rsakey;
rsakey.blobheader.bType = PUBLICKEYBLOB; // 0x06
rsakey.blobheader.bVersion = CUR_BLOB_VERSION; // 0x02
rsakey.blobheader.reserved = 0;
rsakey.blobheader.aiKeyAlg = CALG_RSA_KEYX;
rsakey.rsapubkey.magic = 0x31415352;// ASCII for RSA1
rsakey.rsapubkey.bitlen = i_n_len * 8; // = 4096
rsakey.rsapubkey.pubexp = 65537;

memcpy(rsakey.n, p_pkey.key.sig.rsa.n + 2, i_n_len); // skip first two byte which are MPI length
std::reverse(rsakey.n, rsakey.n + i_n_len); // need to convert to little endian for WinCrypt

CryptImportKey(hCryptProv, (BYTE*)&rsakey, sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + i_n_len, 0, 0, &hPubKey); // no error

std::unique_ptr<BYTE[]> pSig(new BYTE[i_s_len]);
memcpy(pSig.get(), p_sig.algo_specific.rsa.s + 2, i_s_len); // skip first two byte which are MPI length
std::reverse(p_sig.algo_specific.rsa.s, p_sig.algo_specific.rsa.s + i_s_len); // need to convert to little endian for WinCrypt

if (!CryptVerifySignature(hHash, pSig.get(), i_s_len, hPubKey, nullptr, 0))
{
DWORD err = GetLastError(); // err=2148073478 -> INVALID_SIGNATURE
CryptDestroyKey(hPubKey);
return -1;
}

CryptDestroyKey(hPubKey);
return 0;
}

CryptVerifySignature не удается с GetLastError() декодирование в INVALID_SIGNATURE,

На http://tools.ietf.org/html/rfc4880#section-5.2.2 Я читаю

With RSA signatures, the hash value is encoded using PKCS#1 encoding
type EMSA-PKCS1-v1_5 as described in Section 9.2 of RFC 3447.  This
requires inserting the hash value as an octet string into an ASN.1
structure.

Это нужно или это автоматически делается CryptVerifySignature? Если нет, то как это сделать?

2

Решение

Подбивка PKCS # 1 вряд ли будет проблемой. Намек на то, что он использует OID для алгоритма хеширования по умолчанию, указывает на сигнатуры типа PKCS # 1 v1.5, так что я думаю, вы можете быть уверены, что используется правильное заполнение.

Больше подтверждения можно найти в CryptSignHash документация:

По умолчанию поставщики Microsoft RSA используют метод заполнения PKCS # 1 для подписи. OID хеша в элементе подписи DigestInfo автоматически устанавливается на OID алгоритма, связанный с хеш-объектом. Использование флага CRYPT_NOHASHOID приведет к тому, что этот OID будет опущен в подписи.


Просматривая документацию по API, я заметил следующее:

В нативном API шифрования используется порядок байтов с прямым порядком байтов, а в API .NET Framework — порядок байтов с прямым порядком байтов. Если вы проверяете подпись, сгенерированную с помощью API .NET Framework, вы должны поменять порядок байтов подписи перед вызовом функции CryptVerifySignature, чтобы проверить подпись.

Это означает, что API не соответствует PKCS # 1 v1.5, поскольку в нем явно указан порядок байтов. Поэтому это, безусловно, что-то, о чем нужно знать, и может быть частью решения.

2

Другие решения

Ошибка была в этой строке

std::reverse(p_sig.algo_specific.rsa.s, p_sig.algo_specific.rsa.s + i_s_len); // need to convert to little endian for WinCrypt

который должен читать

std::reverse(pSig.get(), pSig.get() + i_s_len); // need to convert to little endian for WinCrypt

потому что преобразование источника байтов из большого в младший порядок не преобразует другой буфер после копирования.

1

По вопросам рекламы [email protected]