Microsoft Crypto API отключает использование алгоритма передачи ключей RSAES-OAEP

Я использую CryptEncryptMessage генерировать PKCS#7 сообщение в конверте я использую szOID_NIST_AES256_CBC в качестве алгоритма шифрования.

Сгенерированное сообщение кажется действительным, но является RSAES-OAEP для ключевого транспортного алгоритма, который имеет ограниченную поддержку в дикой природе (Thunderbird, OpenSSL SMIME Module среди многих других не поддерживают его).

Я хотел бы, чтобы CAPI вернулся к старому RSAencryption для транспортировки ключей.

Есть ли какой-нибудь возможный способ сделать это, я мог бы вернуться к функциям обмена сообщениями низкого уровня, если есть способ, а не использовать CryptEncryptMessage но я не могу найти способ сделать это, даже используя функции низкого уровня.

Код:

CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);

EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;

EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;

EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;

BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;

BOOL retval =  CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);

1 ответ

Алгоритм переноса ключей немного сложен в обращении, и он может не служить своей цели (я вижу, вы заметили, что вы хотите, чтобы CAPI поддерживал RSAencryption; поверь мне, я бы тоже) Похоже, что вы уже обнаружили большую часть вашей проблемы - сгенерированное сообщение кажется действительным, но ваш метод делает необходимым использование CryptEncryptMessage , который не будет работать хорошо / вообще в долгосрочной перспективе.

Шаг 1 - Изучите код

CRYPT_ENCRYPT_MESSAGE_PARA EncryptMessageParams;
EncryptMessageParams.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO);

EncryptMessageParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING;

EncryptMessageParams.ContentEncryptionAlgorithm.pszObjId = szOID_NIST_AES256_CBC;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.cbData = 0;
EncryptMessageParams.ContentEncryptionAlgorithm.Parameters.pbData = 0;

EncryptMessageParams.hCryptProv = NULL;
EncryptMessageParams.pvEncryptionAuxInfo = NULL;
EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;

BYTE pbEncryptedBlob[640000];
DWORD pcbEncryptedBlob = 640000;

BOOL retval =  CryptEncryptMessage(&EncryptMessageParams, cRecipientCert, pRecipCertContextArray, pbMsgText, dwMsgTextSize, pbEncryptedBlob, &pcbEncryptedBlob);

Довольно простой, не правда ли? Несмотря на эффективность, на самом деле проблема не решается. Если вы посмотрите на это:

EncryptMessageParams.dwFlags = 0;
EncryptMessageParams.dwInnerContentType = 0;

вы увидите, что оно предопределено, но используется только в определении retval, Тем не менее, я могу определенно увидеть это как микрооптимизацию, и она не очень полезна, если мы собираемся переписать код. Тем не менее, я обрисовал основные шаги, которые нужно предпринять, чтобы интегрировать их без полного пересмотра кода (чтобы вы могли продолжать использовать те же параметры):

Шаг 2 - Редактирование параметров

Как отметил @owlstead в своих комментариях, Crypto API не очень удобен для пользователя. Тем не менее, вы проделали большую работу с ограниченными ресурсами. То, что вы хотите добавить, это провайдер криптографического перечисления, чтобы помочь сузить ключи. Убедитесь, что у вас есть Microsoft Base Cryptographic Provider версии 1.0 или Microsoft Enhanced Cryptographic Provider версии 1.0 для их эффективного использования. В противном случае вам нужно добавить в функцию так:

DWORD cbName;
DWORD dwType;
DWORD dwIndex;
CHAR *pszName = NULL;
(regular crypt calls here)

Это в основном используется для предотвращения NTE_BAD_FLAGS ошибка, хотя технически вы могли бы избежать этого с помощью более низкого уровня декларации. Если вы хотите, вы также можете создать совершенно новый хеш (хотя это необходимо только в том случае, если приведенная выше реализация не масштабируется до необходимого фактора времени / скорости):

DWORD dwBufferLen = strlen((char *)pbBuffer)+1*(0+5);
HCRYPTHASH hHash;
HCRYPTKEY hKey;
HCRYPTKEY hPubKey;
BYTE *pbKeyBlob;
BYTE *pbSignature;
DWORD dwSigLen;
DWORD dwBlobLen;
(use hash as normal w/ crypt calls and the pbKeyBlobs/Signatures)

Обязательно обновите этот фрагмент, прежде чем двигаться дальше. Вы можете сделать это так легко:

if(CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
     printf("CSP context acquired.\n");
}

Если вы документируете или выпускаете, возможно, захотите добавить void MyHandleError(char *s) чтобы поймать ошибку, чтобы кто-то, кто редактирует, но не смог, может быстро ее обнаружить.

Кстати, при первом запуске вы должны будете создать новый набор, потому что по умолчанию его нет. Хороший однострочник, который можно вставить в if ниже:

CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)

Помните, что синхронизация ресурсов сервера не будет такой эффективной, как выполнение переделок, предложенных мной на первом шаге. Это то, что я объясню ниже:

Шаг 3 - перекодировать и перезапустить

Как программист, перекодирование может показаться пустой тратой времени, но оно определенно может помочь вам в долгосрочной перспективе. Помните, что вам все равно придется кодировать в пользовательских параметрах при кодировании / синхронизации; Я не собираюсь давать вам весь код, как ребенок. Этого должно быть достаточно, чтобы показать вам основные контуры.

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

Помните, мы собираемся обойти CryptEncryptMessage используя CryptReleaseContext, который непосредственно освобождает ручку, полученную CryptAcquireContext функция. Стандарт Microsoft на CAC ниже:

BOOL WINAPI CryptAcquireContext(
  _Out_  HCRYPTPROV *phProv,
  _In_   LPCTSTR pszContainer,
  _In_   LPCTSTR pszProvider,
  _In_   DWORD dwProvType,
  _In_   DWORD dwFlags
);

Обратите внимание, что Microsoft ругает вас, если вы используете пользовательский интерфейс:

Если CSP должен отображать пользовательский интерфейс для работы, вызов завершается неудачно, и в качестве последней ошибки устанавливается код ошибки NTE_SILENT_CONTEXT. Кроме того, если вызовы выполняются для CryptGenKey с флагом CRYPT_USER_PROTECTED с контекстом, который был получен с флагом CRYPT_SILENT, вызовы не выполняются, и CSP устанавливает NTE_SILENT_CONTEXT.

В основном это код сервера, а ERROR_BUSY будет определенно отображаться новым пользователям, когда есть несколько подключений, особенно с высокой задержкой. Свыше 300 мс просто вызовет NTE_BAD_KEYSET_PARAM или аналогичный, который должен быть вызван, из-за тайм-аута без получения даже надлежащей ошибки. (Проблемы с передачей, кто-нибудь со мной?)

Если вы не беспокоитесь о нескольких DLL (которые это не поддерживает из-за NTE_PROVIDER_DLL_FAIL ошибки), базовая настройка для захвата клиентских служб шифрования будет такой, как показано ниже (скопировано непосредственно из примеров Microsoft):

if (GetLastError() == NTE_BAD_KEYSET)
 {
   if(CryptAcquireContext(
      &hCryptProv, 
      UserName, 
      NULL, 
      PROV_RSA_FULL, 
      CRYPT_NEWKEYSET)) 
    {
      printf("A new key container has been created.\n");
    }
    else
    {
      printf("Could not create a new key container.\n");
      exit(1);
    }
  }
  else
  {
      printf("A cryptographic service handle could not be "
          "acquired.\n");
      exit(1);
   }

Как бы просто это ни казалось, вы определенно не хотите застрять, передавая это алгоритму обмена ключами (или что-то еще, что у вас есть). Если вы не используете симметричные сеансовые ключи (Diffie-Hellman/KEA), пара ключей обмена может использоваться для шифрования сеансовых ключей, чтобы их можно было безопасно хранить и обмениваться с другими пользователями.

Кто-то по имени Джон Ховард написал замечательную утилиту настройки удаленного управления Hyper-V (HVRemote), которая представляет собой обширную компиляцию методов, обсуждаемых здесь. В дополнение к использованию основных крипт и пар ключей они могут использоваться для ANONYMOUS LOGON дистанционный пульт DCOM доступ (cscript hvremote.wsf, чтобы быть конкретным). Вы можете увидеть многие функции и приемы в его последних криптах (вам придется сузить запрос) в его блоге:

http://blogs.technet.com/b/jhoward/

Если вам нужна дополнительная помощь с основами, просто оставьте комментарий или запросите личный чат.

Заключение

Хотя это довольно просто, когда вы поймете основные методы хеширования на стороне сервера и то, как клиент захватывает "крипты", вы будете сомневаться, почему вы даже пытались использовать шифрование во время передачи. Однако без шифрования на стороне клиента шифрование определенно было бы единственным безопасным способом передачи уже хешированного.

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

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