Проверка подписи 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 я читаю

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 ответа

Решение

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

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

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

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

Другие вопросы по тегам