Импорт ключа

Добрый день. Я реализую шифрование сообщения при помощи открытого ключа получателя и закрытого отправителя.
Отправитель - сертификат с закрытым ключом сформированный через КриптоПро CSP
Получатель - сертификат с закрытым ключом. Закрытый ключ расположен на токене РутокенЭЦП

Алгоритм моих действий следующий:
1. Получаем из сертификата получателя BLOB открытого ключа следующим образом:

    if(dwError==ERROR_SUCCESS)
    {
        //Импортируем открытый ключ
        if(m_pcryptoAPI->CryptImportPublicKeyInfoEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            &(pRecipientCert->pCertInfo->SubjectPublicKeyInfo),
            0,
            0,
            NULL,
            &hRecipientPublicKey))
        {
            //Экспортируем открытый ключ в BLOB
            if(m_pcryptoAPI->CryptExportKey(hRecipientPublicKey,
                NULL,PUBLICKEYBLOB,NULL,pbtPublicKeyBlob,&nPublicKeyBlobSize))
            {
                pbtPublicKeyBlob=new BYTE[nPublicKeyBlobSize];
                if(pbtPublicKeyBlob==NULL)
                    dwError=E_OUTOFMEMORY;
                else
                {
                    if(!m_pcryptoAPI->CryptExportKey(hRecipientPublicKey,
                        NULL,PUBLICKEYBLOB,NULL,pbtPublicKeyBlob,&nPublicKeyBlobSize))
                        dwError=::GetLastError();

                    //Уничтожаем объект открытого ключа, т.к. в дальнейшем будем использовать только его BLOB
                    m_pcryptoAPI->CryptDestroyKey(hRecipientPublicKey);
                    hRecipientPublicKey=NULL;
                }
            }
            else
                dwError=::GetLastError();
        }
        else
            dwError=::GetLastError();

    }

2. Получаем закрытый ключ отправителя так:

    if(dwError==ERROR_SUCCESS)
    {
        //Получаем закрытый ключ отправителя
        if(!m_pcryptoAPI->CryptGetUserKey(AT_KEYEXCHANGE,&hSenderPrivateKey))
            dwError=::GetLastError();
    }

3. Импортируем открытый ключ получателя на закрытый отправителя

        if(dwError==ERROR_SUCCESS)
    {
        //получаем ключ согласования импортом открытого ключа получателя на закрытый отправителя 
        if(m_pcryptoAPI->CryptImportKey(pbtPublicKeyBlob,nPublicKeyBlobSize,hSenderPrivateKey,NULL,&hAgreeKey))
        {
                }
                else    
                    dwError=::GetLastError();
     }

На шаге №3 получаем ошибку 0x80090019 (-2146893799) набор ключей не определен
При этом, если в качестве получателя используется сертификат, закрытый ключ от которого расположен в реестре или на обычной флэшке (сформированный через КриптоПро), то все превосходно отрабатывает.
Вопрос в том, что не так.

Re: Импорт ключа

Добрый день.
Вопрос скорее на форум крипто-про, но думаю вам стоит сначала открыть контекст (CryptAquireContext) с именем контейнера.

Сначала делаете так: http://www.cryptopro.ru/forum2/default. … #post12010
А потом как то так:
CryptAcquireContext( &phProv,"\\\\.\\Aktiv Rutoken ECP 0\\test_key",""Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider"",75,0)

(2015-10-29 18:13:20 отредактировано SvetovidovDM)

Re: Импорт ключа

Кирилл Мещеряков пишет:

Добрый день.
Вопрос скорее на форум крипто-про, но думаю вам стоит сначала открыть контекст (CryptAquireContext) с именем контейнера.

Сначала делаете так: http://www.cryptopro.ru/forum2/default. … #post12010
А потом как то так:
CryptAcquireContext( &phProv,"\\\\.\\Aktiv Rutoken ECP 0\\test_key",""Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider"",75,0)

Стоп, не очень понял.
Контекст с закрытым ключом отправителя уже открыт ранее, в сообщении я не приводил этот момент. Однако проблем с получением закрытого ключа нет, проблема возникает при импорте на него открытого ключа получателя.

На машине нет закрытого ключа получателя в принципе. Есть только сертификат .cer.

Опять таки повторюсь, что если сертификат получателя сформирован при помощи КриптоПро то проблем не возникает, и тот же самый код прекрасно работает и шифрует данные, что наводит меня на мысль что существует некая разница в ключах. Но как понять что это за разница, и как с ней бороться я не знаю.
Аналогичный вопрос на форуме КриптоПро я задал.
Но к сожалению не очень точно сформулировал проблему, в результате чего ответ получил не тот который я ожидал...

Re: Импорт ключа

Как на рутокене то у вас оказывается ключ?

(2015-10-29 18:29:31 отредактировано SvetovidovDM)

Re: Импорт ключа

Кирилл Мещеряков пишет:

Как на рутокене то у вас оказывается ключ?

Генерируется средствами PKCS11 библиотеки, от вашего SDK.
Впрочем разницы нет, потому как при реальной эксплуатации будет так:
В дистрибутиве приложения будет лежать сертификат. Когда необходимо зашифровать данные, приложение будет брать этот сертификат, читать его, получать из него блоб открытого, получать ключ согласования этого блоба и выбранного пользователем сертификата отправителя, генерировать сессионный ключ, шифровать им сообщение, после чего шифровать сам сессионный ключ на ключе согласования и отправлять зашифрованные данные на сервер вместе с открытым ключем отправителя и зашифрованным сессионным ключом. Ну а на сервере процедура будет происходить в обратном порядке.

Re: Импорт ключа

А ну понятно, а то вы не написали что через PKCS11, я то подумал что тоже через КриптоПро CSP.

Попробуйте "перевернуть" открытый ключ (изменить порядок байт).

Re: Импорт ключа

Кирилл Мещеряков пишет:

А ну понятно, а то вы не написали что через PKCS11, я то подумал что тоже через КриптоПро CSP.

Попробуйте "перевернуть" открытый ключ (изменить порядок байт).

В таком случае возникает вопрос - как мне ориентироваться, надо ли его переворачивать?:) Есть ли некий флаг или поле в сертификате, говорящее о необходимости "перевернуть" ключ?

Re: Импорт ключа

Неа, нету.
Так как речь идет просто о числе, то зависит от спецификации, как оно будет представлено, в виде littleendian или bigendian.
Насколько я знаю, в PKCS11 и CryptoAPI получилось вразнобой.

Re: Импорт ключа

Кирилл Мещеряков пишет:

А ну понятно, а то вы не написали что через PKCS11, я то подумал что тоже через КриптоПро CSP.

Попробуйте "перевернуть" открытый ключ (изменить порядок байт).

Переделал код так:

                //получаем ключ согласования импортом открытого ключа получателя на закрытый отправителя
        if(!m_pcryptoAPI->CryptImportKey(pbtPublicKeyBlob,nPublicKeyBlobSize,hSenderPrivateKey,NULL,&hAgreeKey))
        {
            dwError=GetLastError();
            if(dwError==0x80090019)
            {
                DWORD dwKeyLen=((CRYPT_PUBLICKEYBLOB*)pbtPublicKeyBlob)->stPublicKeyParam.KeyParam.BitLen/8;
                BYTE *pbtKey=&((CRYPT_PUBLICKEYBLOB*)pbtPublicKeyBlob)->bPublicKey[0];
                DWORD dwIdxBeg=0;
                DWORD dwIdxEnd=dwKeyLen-1;
                BYTE bTemp;
                while(dwIdxBeg<=dwIdxEnd)
                {
                    bTemp=pbtKey[dwIdxBeg];
                    pbtKey[dwIdxBeg]=pbtKey[dwIdxEnd];
                    pbtKey[dwIdxEnd]=bTemp;
                    dwIdxBeg++;
                    dwIdxEnd--;
                }
                if(!m_pcryptoAPI->CryptImportKey(pbtPublicKeyBlob,nPublicKeyBlobSize,hSenderPrivateKey,NULL,&hAgreeKey))
                    dwError=GetLastError();
                else
                    dwError=ERROR_SUCCESS;
            }

Эффект тот же самый, ошибка та же...

Re: Импорт ключа

Криптография непростая штука.
Нужно соблюдать кучу условий, чтобы сложные вычисления приводили к ожидаемому результату.
Например, мы не знаем, что у вас за ключи на токене, как вы их получили, какие парамсеты у эллиптических кривых?
Без этого сложно что-то советовать.

(2015-10-30 13:26:51 отредактировано SvetovidovDM)

Re: Импорт ключа

Кирилл Мещеряков пишет:

Криптография непростая штука.
Нужно соблюдать кучу условий, чтобы сложные вычисления приводили к ожидаемому результату.
Например, мы не знаем, что у вас за ключи на токене, как вы их получили, какие парамсеты у эллиптических кривых?
Без этого сложно что-то советовать.

Ну сертификат я могу прислать. Если интересуют параметры указанные при генерации ключей - тоже могу прислать.
В общем скажите что нужно - отправлю.
Получал через тестовый контур Электронной Москвы (smvmsk)

Re: Импорт ключа

Нужен код, создающий ключи на Рутокене ЭЦП и извлекающий открытый ключ. Параметры КриптоПро CSP не нужны.

(2015-10-30 13:41:11 отредактировано SvetovidovDM)

Re: Импорт ключа

Кирилл Мещеряков пишет:

Нужен код, создающий ключи на Рутокене ЭЦП и извлекающий открытый ключ. Параметры КриптоПро CSP не нужны.

Ну весь код выкладывать не буду, он слишком большой. Параметры ключей - легко.

    CK_BYTE         btTrue = 0x01;
    CK_BYTE         btFalse = 0x00;
    BYTE            abtParamSetGostR3410[] = { 0x06, 0x07, 0x2A, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01 };
    BYTE            abtParamSetGostR3411[] = { 0x06, 0x07, 0x2A, 0x85, 0x03, 0x02, 0x02, 0x1E, 0x01 };
    CK_MECHANISM    stMechanizm = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL, 0 };
    CK_KEY_TYPE        lKeyType = CKK_GOSTR3410;
    CK_OBJECT_CLASS    lPublicKeyClass = CKO_PUBLIC_KEY;
    CK_OBJECT_CLASS    lPrivateKeyClass = CKO_PRIVATE_KEY;
    CK_ATTRIBUTE    stAttrPublicKey[] = 
    {
        { CKA_CLASS, &lPublicKeyClass, sizeof( lPublicKeyClass ) },
        { CKA_KEY_TYPE, &lKeyType, sizeof( lKeyType ) },
        { CKA_TOKEN, &btTrue, sizeof( btTrue ) },
        { CKA_PRIVATE, &btFalse, sizeof( btFalse ) },
        { CKA_ID, ( CK_CHAR* )(( const char* )astrID ), ( CK_ULONG )astrID.GetLength() },
        { CKA_LABEL, ( CK_CHAR* )(( const char* )astrKeyLabel ), ( CK_ULONG )astrKeyLabel.GetLength() },
        { CKA_SUBJECT, ( CK_CHAR* )(( const char* )astrSubject ), ( CK_ULONG )astrSubject.GetLength() },
        { CKA_GOSTR3410_PARAMS, &abtParamSetGostR3410[0], sizeof( abtParamSetGostR3410 ) / sizeof( abtParamSetGostR3410[0] ) },
        { CKA_GOSTR3411_PARAMS, &abtParamSetGostR3411[0], sizeof( abtParamSetGostR3411 ) / sizeof( abtParamSetGostR3411[0] ) },
        { CKA_ENCRYPT, &btTrue, sizeof(btTrue)},  //*   
        { CKA_DERIVE, &btTrue, sizeof(btTrue)},      //*
        { CKA_VERIFY, &btTrue, sizeof( btTrue ) },
    };

    CK_ATTRIBUTE    stAttrPrivateKey[] = 
    {
        { CKA_CLASS, &lPrivateKeyClass, sizeof( lPrivateKeyClass ) },
        { CKA_KEY_TYPE, &lKeyType, sizeof( lKeyType ) },
        { CKA_TOKEN, &btTrue, sizeof( btTrue ) },
        { CKA_PRIVATE, &btTrue, sizeof( btTrue ) },
        { CKA_ID, ( CK_CHAR* )(( const char* )astrID ), ( CK_ULONG )astrID.GetLength() },
        { CKA_LABEL, ( CK_CHAR* )(( const char* )astrKeyLabel ), ( CK_ULONG )astrKeyLabel.GetLength() },
        { CKA_SUBJECT, ( CK_CHAR* )(( const char* )astrSubject ), ( CK_ULONG )astrSubject.GetLength() },
        { CKA_GOSTR3410_PARAMS, &abtParamSetGostR3410[0], sizeof( abtParamSetGostR3410 ) / sizeof( abtParamSetGostR3410[0] ) },
        { CKA_GOSTR3411_PARAMS, &abtParamSetGostR3411[0], sizeof( abtParamSetGostR3411 ) / sizeof( abtParamSetGostR3411[0] ) },
        { CKA_DECRYPT, &btTrue,sizeof(btTrue) },    //*
        { CKA_DERIVE, &btTrue,sizeof(btTrue)  },    //*
        { CKA_SIGN, &btTrue, sizeof( btTrue ) },
    };

    //Параметры помеченные * внесены для тестирования плагина ИС НР. Возможно они будут добавлены и в основную реализацию
    CK_OBJECT_HANDLE    hPublicKey = ( CK_OBJECT_HANDLE )NULL;
    CK_OBJECT_HANDLE    hPrivateKey = ( CK_OBJECT_HANDLE )NULL;
    CK_RV    lRetCode = pstFL->C_GenerateKeyPair( m_hSession,
        &stMechanizm,
        &stAttrPublicKey[0],
        sizeof( stAttrPublicKey ) / sizeof( stAttrPublicKey[0] ),
        &stAttrPrivateKey[0],
        sizeof( stAttrPrivateKey ) / sizeof( stAttrPrivateKey[0] ),
        &hPublicKey,
        &hPrivateKey );

Извлечение ключа:

    // Берём значение открытого ключа
    CK_ATTRIBUTE    stAttrValue[] = { { CKA_VALUE, NULL, 0 } };
    if( lRetCode == CKR_OK )
        lRetCode = pstFL->C_GetAttributeValue( m_hSession, hPublicKey, &stAttrValue[0], sizeof( stAttrValue ) / sizeof( stAttrValue[0] ));
    if( lRetCode == CKR_OK )
    {
        stAttrValue[0].pValue = ( stAttrValue[0].ulValueLen != 0 ? new BYTE[stAttrValue[0].ulValueLen] : NULL );
        if( stAttrValue[0].pValue == NULL )
            lRetCode = CKR_HOST_MEMORY;
        else
        {
            lRetCode = pstFL->C_GetAttributeValue( m_hSession, hPublicKey, &stAttrValue[0], sizeof( stAttrValue ) / sizeof( stAttrValue[0] ));
            if( lRetCode == CKR_OK )
            {
                CKeyInfo*    pkeyInfo = new CKeyInfo( astrID, astrID.GetLength(), astrKeyLabel, astrKeyLabel.GetLength(),
                    ( BYTE* )stAttrValue[0].pValue, stAttrValue[0].ulValueLen );
                if( pkeyInfo != NULL )
                    m_posKeyInfo = m_listKeyInfos.AddTail( pkeyInfo );
            }

            delete [] ( BYTE* )stAttrValue[0].pValue;
        }
    }

    DWORD    dwErrorCode = ( lRetCode != CKR_OK ? ( 0x11000000 | lRetCode ) : ERROR_SUCCESS );
    return( dwErrorCode );

Re: Импорт ключа

Добрый день !

Создайте два контейнера КриптоПро CSP. 
Соберите и запустите пример по выработке общего ключа DH, который есть в примерах КриптоПро CSP SDK (пример называется ExportingSessionKey ) и попробуйте создать общей сессионный ключ для этих двух контейнеров.

Если пример сработает, то проведите такой эксперимент: скопируйте значение открытого ключа из PKCS#11 (64 байта)
не переворачивая в один из блобов перед импортом в криптопро по смещению 36 байт от начала блоба.
Если импорт пройдет, то будем думать что делать дальше :)

С уважением,
Олег Тараскин

Re: Импорт ключа

Выяснил, что в работающем и неработающем ключе разные параметры кривых.
В рабочем (сгенерированный с помощью КриптоПро CSP

SEQUENCE(2 elem)
   SEQUENCE(2 elem)
      OBJECT IDENTIFIER1.2.643.2.2.19
   SEQUENCE(2 elem)
      OBJECT IDENTIFIER1.2.643.2.2.36.0
      OBJECT IDENTIFIER1.2.643.2.2.30.1
BIT STRING(1 elem)
   OCTET STRING(64 byte) DEF9414282AAC2CF4D7B822F0790C39404A3F804C078821A9957DEA4DC30EE466390E0…

В нерабочем (Сгереированный с помощью PKCS#11)

SEQUENCE(2 elem)
   SEQUENCE(2 elem)
      OBJECT IDENTIFIER1.2.643.2.2.19
   SEQUENCE(2 elem)
      OBJECT IDENTIFIER1.2.643.2.2.35.1
      OBJECT IDENTIFIER1.2.643.2.2.30.1
BIT STRING(1 elem)
   OCTET STRING(64 byte) 295550871C46A9C208E519463D60D68F664A496BACCBB50A1C0112F12081BA255A5320…

Вопрос - можно лии их согласовать каким то образом?