Эквивалент RSA_public_decrypt и MS Crypto API

Я пытаюсь разработать решение для проверки лицензии. Лицензии кодируются на сервере с помощью функции OpenSSL RSA_private_encrypt.

Для Mac OX X я использую RSA_public_decrypt, и он прекрасно работает. В Windows я должен использовать очень маленький фрагмент кода, поэтому я не могу связываться с OpenSSL или другой библиотекой, И я должен использовать MS Crypto API.

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

Я пробовал все, включая CryptVerifyMessageSignatureWithKey и CryptDecodeObject, чтобы загрузить большой двоичный объект с разными параметрами, но все равно не повезло.

Он всегда заканчивается на GetLastError() == CRYPT_E_ASN1_BADTAG, что, как я полагаю, означает, что BLOB не отформатирован ASN1... Google ничего не говорит о формате вывода RSA_private_encrypt... так что я здесь совершенно потерян.

Вот код OS X на основе OpenSSL:

void cr_license_init(const char* lic) {
    __cr_license_ = lic;
    unsigned char lic_encoded[CR_LIC_LEN];

    BIO* b64 = BIO_new(BIO_f_base64());
    BIO* licIn = BIO_new_mem_buf((void*)lic, -1);
    licIn = BIO_push(b64, licIn);

    if(BIO_read(licIn, lic_encoded, CR_LIC_LEN) == CR_LIC_LEN) {

        const unsigned char* key_data = license_pub_der;
        RSA* r = d2i_RSA_PUBKEY(NULL, &key_data, sizeof(license_pub_der));

        if(r != NULL) {
            if(__cr_license_data_ != NULL) {
                free((void*)__cr_license_data_);
            }
            __cr_license_data_ = malloc(CR_LIC_LEN);
            if(RSA_public_decrypt(CR_LIC_LEN, lic_encoded,
    (unsigned char*)__cr_license_data_, r, RSA_PKCS1_PADDING) &lt= 0) {
                free((void*)__cr_license_data_);
                __cr_license_data_ = NULL;
            }
            RSA_free(r);
        }
    }
    BIO_free_all(licIn);
}

Эта часть кода в Windows работает хорошо, поэтому я предполагаю, что открытый ключ не является проблемой.

__cr_license_ = lic;
unsigned char lic_encoded[CR_LIC_LEN];

DWORD dwSize;
if(CryptStringToBinaryA(__cr_license_, 0/*autocalculate*/, CRYPT_STRING_BASE64, lic_encoded, &dwSize, NULL, NULL) && dwSize == CR_LIC_LEN) {
HCRYPTPROV hProv;
if(CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
    PCERT_PUBLIC_KEY_INFO pki = NULL;
    DWORD dwKeySize;
    if(CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, license_pub_der, sizeof(license_pub_der), CRYPT_ENCODE_ALLOC_FLAG, NULL, &pki, &dwKeySize)) {
        HCRYPTKEY hKey = 0;
        if(CryptImportPublicKeyInfo( hProv, X509_ASN_ENCODING, pki, &hKey)) {

Но после этого все, что я пытаюсь сделать с сообщением, приводит к CRYPT_E_ASN1_BADTAG. Пробовал CryptMsgOpenToDecode с CryptMsgUpdate, CryptDecodeObject, CryptVerifyMessageSignatureWithKey - ничего не работает.

В основном я думаю, что проблема в несовместимости pkcs1 и pkcs7, как упоминал owlstead. Есть ли у кого-нибудь опыт работы с форматом pkcs1, импортирующим/преобразующим/и т. д. с помощью MS CAPI?

Любая помощь или даже подсказка очень ценятся! Заранее спасибо!


person strannik    schedule 25.01.2013    source источник
comment
Возможно, вы захотите включить некоторый код в вопрос.   -  person Daniel Roethlisberger    schedule 25.01.2013
comment
также показывать заголовок ключей и закодированных файлов. Эта ошибка связана с недопустимым заголовком тега ASN.   -  person Vahid Farahmand    schedule 26.01.2013
comment
Это помогает? stackoverflow.com/q/7573754/645583   -  person pcunite    schedule 26.01.2013
comment
Да, я видел эту ветку. Проблема в данном случае не в ключе. Я могу успешно импортировать ключ с помощью CryptImportPublicKeyInfo, поэтому я предполагаю, что это не так. Похоже, проблема в закодированном BLOB. Я считаю, что BLOB несовместим с ASN1 и должен быть каким-то образом преобразован.   -  person strannik    schedule 26.01.2013
comment
Вы читали комментарий под этой статьей? msdn.microsoft.com/en -нас/библиотека/окна/рабочий стол/   -  person Maarten Bodewes    schedule 26.01.2013
comment
Спасибо @owlstead, я, конечно, видел этот комментарий. Я сомневаюсь, что это так, потому что я могу успешно импортировать открытый ключ в контекст с помощью CryptImportPublicKeyInfo.   -  person strannik    schedule 26.01.2013
comment
@Daniel Я надеялся на код Windows, который выдал ошибку CRYPT_E_ASN1_BADTAG, на которую вы ссылаетесь.   -  person Daniel Roethlisberger    schedule 26.01.2013
comment
@DanielRoethlisberger, извините, у меня дома нет кода для Windows, но я обязательно добавлю его завтра в офисе.   -  person strannik    schedule 26.01.2013
comment
Даниэль, ты знаешь механизм заполнения, используемый CryptVerifyMessageSignatureWithKey? По какой-то причине это не указано API (не то чтобы я удивлен, Microsoft регулярно занижает)   -  person Maarten Bodewes    schedule 26.01.2013
comment
@owlstead, Microsoft говорит, что вы можете выбрать один: X509_ASN_ENCODING | PKCS_7_ASN_ENCODING. Таким образом, BLOB, безусловно, должен быть отформатирован ASN1 и, похоже, работает с дополнением PKCS.   -  person strannik    schedule 26.01.2013
comment
PKCS#7 или CMS — это синтаксис криптографических сообщений высокого уровня. Приведенный выше код OpenSSL, по-видимому, использует низкоуровневую подпись PKCS#1. Этот формат подписи может быть частью структуры PKCS#7, но это определенно не то же самое. PKCS означает стандарты криптографии с открытым ключом и требует, чтобы число, такое как #1 #7, указывало на конкретный стандарт (и, надеюсь, за ним должна следовать версия и имя конкретного синтаксиса или алгоритма сообщения). PKCS сам по себе ничего не определяет. Это может ответить на ваш вопрос. Обратите внимание, что CMS указывается с использованием нотации ASN.1 BER....   -  person Maarten Bodewes    schedule 26.01.2013
comment
Кстати, X509_ASN_ENCODING предназначен для сертификатов, они также указаны в нотации ASN.1 BER. Сертификаты подписываются почти так же, как и CMS, в основном они представляют собой формат контейнера, который подписывается, опять же, сама подпись, скорее всего, будет PKCS#1 (с использованием формата подписи v1.5).   -  person Maarten Bodewes    schedule 26.01.2013
comment
@owlstead, хорошо, понял, спасибо за такое подробное объяснение! Любые предложения, как можно преобразовать PKCS1 в PKCS7?   -  person strannik    schedule 26.01.2013
comment
Преобразование может быть невозможным. PKCS#7 может использовать формат подписи PKCS#1, но обычно он не вычисляется непосредственно по самим данным. Либо позвольте серверу создать PKCS7 (это функция OpenSSL), либо попробуйте найти функцию, которая будет проверять подписи PKCS#1.   -  person Maarten Bodewes    schedule 26.01.2013
comment
@DanielRoethlisberger, я опубликовал часть кода Windows.   -  person strannik    schedule 26.01.2013
comment
@Daniel: Два вопроса. Я видел, что вы спрашивали об этом 3 месяца назад и только что создали награду. Любой прогресс в этом. Не могли бы вы опубликовать открытый, закрытый ключ и зашифрованную лицензию (для тестирования). Итак, я могу взглянуть на него и попытаться написать код, который расшифрует его в Windows.   -  person Victor Ronin    schedule 22.04.2013
comment
@Daniel: Еще один вопрос. Откуда взялось это требование крошечного кода? Это для драйвера или что-то в этом роде?   -  person Victor Ronin    schedule 22.04.2013
comment
Также см. OpenSSL и MS CryptoAPI: разные цифровые подписи.   -  person jww    schedule 09.06.2015


Ответы (3)


Вы смешиваете форматы подписи более высокого и более низкого уровня. OpenSSL по умолчанию использует подписи PKCS#1 v1.5, которые содержат только данные подписи. Windows, похоже, использует контейнеры PKCS # 7. Они могут содержать PKCS#1 v1.5, но эти и другие данные упакованы с использованием формата тега/длины ASN.1 BER. Если Microsoft API попытается декодировать это, он предположит, что необработанная подпись является форматом контейнера, и декодирование завершится ошибкой.

person Maarten Bodewes    schedule 25.01.2013
comment
Можно ли добавить в подпись кодировку более высокого уровня (PKCS7)? Я имею в виду, может быть что-то вроде CryptEncodeObject? - person strannik; 26.01.2013
comment
Смотрите мой последний комментарий к вашему вопросу - person Maarten Bodewes; 26.01.2013
comment
большое спасибо за разъяснение! Будет продолжать исследования. Кто-нибудь знает о возможном решении? - person strannik; 26.01.2013

Если это не настолько очевидно, что вы попытались, но пропустили его перечисление, или я иначе неправильно понял ваш вопрос, я думаю, вам следует использовать CryptDecrypt для расшифровки лицензии, а не функций, которые вы упоминаете в вопросе. Обратите внимание: поскольку вы, похоже, используете OpenSSL с дополнением PKCS#1 v1.5, а CryptoAPI, похоже, не поддерживает это (не тестировалось, но в спецификациях указан только PKCS#1 v2 OAEP), вам, вероятно, придется использовать CRYPT_DECRYPT_RSA_NO_PADDING_CHECK и проверьте и удалите заполнение PKCS#1 v1.5 вручную после расшифровки.

person Daniel Roethlisberger    schedule 26.01.2013
comment
Разве CryptDecrypt не предназначен для использования с закрытым ключом? - person strannik; 26.01.2013
comment
Как я и думал, он возвращает NTE_NO_KEY при использовании с открытым ключом. - person strannik; 26.01.2013
comment
Вы пробовали или только думаете? Честно говоря, я не знаю, но все остальные функции работают с криптографическими контейнерами более высокого уровня, а не с необработанными блоками RSA. Если это не сработает, вы можете либо обмануть его, заставив поверить, что открытый ключ является закрытым ключом, перевернув некоторый бит (не знаю, как открытый ключ помечен как таковой), либо обмануть его, используя CryptEncrypt без каких-либо дополнений (не с указанием CRYPT_OAEP), поскольку, если он не выполняет никаких дополнений, шифрование и дешифрование являются эквивалентной операцией. - person Daniel Roethlisberger; 26.01.2013
comment
Да, я только что попробовал. Это не работает. Я не уверен, возможно ли (и как) обмануть его, чтобы он поверил, что это закрытый ключ... В основном я думаю, что это может быть невозможно, потому что шифрование с закрытым ключом (также известное как подпись) должно сильно отличаться от шифрования открытым ключом. - person strannik; 26.01.2013
comment
Операции шифрования и дешифрования отличаются только добавлением/проверкой и удалением заполнения. На необработанном уровне RSA операции идентичны, только с другим ключом. Кроме того, необработанные операции подписи/проверки отличаются от шифрования/дешифрования только другим дополнением, фактические операции RSA на битовом уровне идентичны. - person Daniel Roethlisberger; 26.01.2013

OpenSSL экспортирует ключи с дополнительным заголовком, которого не ожидает CryptoAPI.

Заголовок закрытого ключа (в нотации ASN.1):

Offset| Len  |LenByte|
======+======+=======+======================================================================
     0|   630|      3| SEQUENCE : 
     4|     1|      1|    INTEGER : 0
     7|    13|      1|    SEQUENCE : 
     9|     9|      1|       OBJECT IDENTIFIER : rsaEncryption [1.2.840.113549.1.1.1]
    20|     0|      1|       NULL : 
    22|   608|      3|    OCTET STRING : 
                             ... actual key data go here ...

Заголовок открытого ключа (в нотации ASN.1):

Offset| Len  |LenByte|
======+======+=======+======================================================================
     0|   159|      2| SEQUENCE : 
     3|    13|      1|    SEQUENCE : 
     5|     9|      1|       OBJECT IDENTIFIER : rsaEncryption [1.2.840.113549.1.1.1]
    16|     0|      1|       NULL : 
    18|   141|      2|    BIT STRING UnusedBits:0 : 
                              ... actual key data go here ...

Именно эти заголовки заставляют CryptDecodeObjectEx задыхаться. Он ожидает данные ключа RAW без какого-либо заголовка.

Итак, в основном вам нужно:

  1. (Необязательно) Преобразуйте .PEM в .DER с помощью CryptStringToBinary.
  2. Проверьте, запускается ли DER с указанными выше заголовками. Для этого вам нужно прочитать данные в кодировке ASN.1.
  3. (Необязательно) Пропустите вышеупомянутый заголовок и перейдите непосредственно к данным ключа (начинается с SEQUENCE, которая включает 2 INTEGER для открытого ключа или 9 INTEGER для закрытого ключа).
  4. Передайте результат в CryptDecodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB/PKCS_RSA_PRIVATE_KEY).
  5. Импортируйте ключи с помощью CryptImportKey.
person Alex    schedule 04.12.2015