2014-09-12 9 views
2

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

Для того, чтобы проверить подпись я первым инициализирует 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? Если нет, как это сделать?

ответ

1

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

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 

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

2

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

Более подтверждение можно найти в CryptSignHash documentation:

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


Просматривая документацию API, следующий бросились в глазах:

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

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

+0

.NET немного лучше, но я не вижу, как компания может уйти с такой плохой документацией API. –

+0

Спасибо за ваш ответ и ваш блеск. Ошибка была вызвана опечаткой и местом, где вы предполагали это ... – MrTux

+0

Что вы имеете в виду под «где я это предполагал?», Было ли это связано с большим/маленьким эндиантом? –