| 
	Статус: Участник
 Группы: Участники
Зарегистрирован: 10.12.2012(UTC)
 Сообщений: 17
 
 Поблагодарили: 3 раз в 2 постах
 | 
            
		      
                В итоге я реализовал шифрование XML для сервисов ФСС через низкоуровневые функции Crypto API, поделюсь своим опытом, потому что структурированной информации в интернете по этой теме очень мало. Далее описываю схему шифрования XML именно для сервисов ФСС (для других, возможно, будет отличаться).Важные моменты: - Для шифрования в XML необходимо использовать режим шифрования CBC (высокоуровневые функции CryptEncryptMessage и CryptDecryptMessage этот режим не поддерживают) - При шифровании необходимо использовать паддинг исходных данных в режиме ISO10126Зашифрованный XML файл содержит 3 важных сегмента (примеры смотри выше в переписке):1. Зашифрованные данные  (EncryptedData -> KeyInfo -> EncryptedKey -> CipherData)1.1  "Сырые" зашифрованные данные, закодированные в Base641.2  Первые 8 байт - вектор инициализации (IV)2. Зашифрованный сессионный ключ и параметры шифрования  (EncryptedData -> KeyInfo -> EncryptedKey -> CipherData)2.1  Значение представляет собой ASN.1 структуру GostR3410-KeyTransport, закодированную в Base642.2  Внутреннюю структуру можно посмотреть с помощью любого ASN.1 просмотровщика (например, на сайте https://lapo.it/asn1js) - OCTET STRING (32 byte) - зашифрованный сессионный ключ - OCTET STRING (4 byte) - MAC сессионного ключа - OCTET STRING (64 byte) - открытый ключ отправителя - OCTET STRING (8 byte) - синхропосылка (UKM)3. Сертификат, которым будет зашифровано ответное сообщение  (EncryptedData -> KeyInfo -> EncryptedKey -> KeyInfo)3.1  Сертификат в формате X.509, закодированный в Base64Принцип шифрования XML: - Берем исходный XML файл и сертификат получателя, которым будут зашифрованы данные (в моем случае это сертификат ФСС) - Экспортируем открытый ключ получателя из сертификата ФСС в PUBLICKEYBLOB  - Генерируем эфемерную ключевую пару (закрытый и открытый ключ отправителя) - Экспортируем открытый ключ отправителя в PUBLICKEYBLOB и сохраняем последние 64 байта (потребуются для записи в структуру GostR3410-KeyTransport) - Получаем ключ согласования импортом открытого ключа получателя на эфемерном ключе - Создаем случайный сессионный ключ CALG_G28147 - Экспортируем созданный сессионный ключ в _CRYPT_SIMPLEBLOB на ключе согласования  - Получаем из структуры _CRYPT_SIMPLEBLOB значение зашифрованного сессионного ключа, MAC и синхропосылку (SV) - Получаем вектор инициализации сессионного ключа - Устанавливаем режим шифрования CBC и паддинг ISO10126 - Шифруем данные исходного XML файла блоками по 8 байт - Добавляем к зашифрованным данным в начало вектор инициализации - Собираем структуру GostR3410-KeyTransport из зашифрованного сессионного ключа, MAC, синхропосылки и открытого ключа отправителя - Создаем новый XML файл со структурой EncryptedData (см. примеры выше в переписке)Принцип расшифровки XML: - Берем зашифрованный XML файл и парсим из него параметры структуры EncryptedData  - Получаем свой закрытый ключ AT_KEYEXCHANGE для расшифровки - Собираем PUBLICKEYBLOB из статической части и открытого ключа - Получаем ключ согласования импортом открытого ключа отправителя на своем закрытом ключе - Собираем структуру _CRYPT_SIMPLEBLOB из статической части, сессионного ключа, MAC и синхропосылки - Получаем сессионный ключ импортом зашифрованного сессионного ключа на ключе согласования - Устанавливаем вектор инициализации - Устанавливаем режим шифрования CBC и паддинг ISO10126 - Расшифровываем данные блоками по 8 байт и получаем исходный XML файлПример шифрования буфера низкоуровневыми функциями на Delphi  (предполагается, что дальнейшую упаковку параметров в структуру GostR3410-KeyTransport и создание зашифрованного XML читатель сможет выполнить самостоятельно): Код:Пример расшифровки буфера низкоуровневыми функциями на Delphi
function TCryptoApiController.EncryptData(
      const ACertificate: AnsiString; // сертификат для шифрования в формате DER
      const ASourceData: AnsiString; // исходные данные
      out APublicKey: AnsiString; // открытый ключ отправителя
      out ASessionKey: AnsiString; // зашифрованный сессионный ключ
      out ASessionSV: AnsiString; // синхропосылка (UKM)
      out ASessionMAC: AnsiString; // MAС сессионного ключа
      out AInitVector: AnsiString; // вектор инициализации
      const ACipherMode: TCryptoCipherMode = ccm_CBC; // режим шифрования
      const APaddingMode: TCryptoPaddingMode = cpm_ISO10126 // режим паддинга
    ): AnsiString;
const
  ENCRYPT_BLOCK_LENGTH: DWORD = 8;
var
  FCryptProv: HCRYPTPROV;
  FCertContext: PCCERT_CONTEXT;
  FPublicKey: HCRYPTKEY;
  FSessionKeyBlob: AnsiString;
  FSenderPublicKeyBlob: AnsiString;
  FRecipientPublicKeyBlob: AnsiString;
  FEphemeralKey: HCRYPTKEY;
  FAgreeKey: HCRYPTKEY;
  FKeyLenNeed: DWORD;
  FSessionKey: HCRYPTKEY;
  FSourcePos: DWORD;
  FSourceDataLen: DWORD;
  FEncryptBuffer: AnsiString;
  FEncryptDataLen: DWORD;
  FEncryptBufferLen: DWORD;
  FEncryptFinal: LongBool;
begin
  CryptoInitialize;
  try
    CryptoLogMessage(CLM_ENCRYPT_DATA);
    AcquireContext(@FCryptProv, nil, CRYPT_VERIFYCONTEXT or CRYPT_SILENT, '');
    try
      // получение сертификата
      FCertContext := FCertCreateCertificateContext(PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, @ACertificate[1], Length(ACertificate));
      if FCertContext = nil then
        RaiseLastCryptoApiError;
      try
        // импорт информации по открытому ключу
        if FCryptImportPublicKeyInfoEx(FCryptProv, PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, @FCertContext.pCertInfo.SubjectPublicKeyInfo, 0, 0, nil, @FPublicKey) then
        try
          // экспорт открытого ключа получателя в BLOB
          if FCryptExportKey(FPublicKey, 0, PUBLICKEYBLOB, 0, nil, @FKeyLenNeed) then
          begin
            SetLength(FRecipientPublicKeyBlob, FKeyLenNeed);
            if not FCryptExportKey(FPublicKey, 0, PUBLICKEYBLOB, 0, @FRecipientPublicKeyBlob[1], @FKeyLenNeed) then
              RaiseLastCryptoApiError;
          end;
        finally
          if not FCryptDestroyKey(FPublicKey) then
            RaiseLastCryptoApiError;
        end
        else
          RaiseLastCryptoApiError;
      finally
        if FCertContext <> nil then
          FCertFreeCertificateContext(FCertContext);
      end;
      // генерация эфемерной ключевой пары
      if FCryptGenKey(FCryptProv, CALG_DH_EL_EPHEM, CRYPT_EXPORTABLE, @FEphemeralKey) then
      try
        // экспорт открытого ключа отправителя в BLOB
        if FCryptExportKey(FEphemeralKey, 0, PUBLICKEYBLOB, 0, nil, @FKeyLenNeed) then
        begin
          SetLength(FSenderPublicKeyBlob, FKeyLenNeed);
          if not FCryptExportKey(FEphemeralKey, 0, PUBLICKEYBLOB, 0, @FSenderPublicKeyBlob[1], @FKeyLenNeed) then
            RaiseLastCryptoApiError;
          // получаем значение открытого ключа отправителя из PUBLICKEYBLOB
          APublicKey := Copy(FSenderPublicKeyBlob, Length(FSenderPublicKeyBlob) - 64 + 1, 64);
        end;
        // получение ключа согласования импортом открытого ключа получателя
        // на эфемерном ключе
        if FCryptImportKey(FCryptProv, @FRecipientPublicKeyBlob[1], Length(FRecipientPublicKeyBlob), FEphemeralKey, 0, @FAgreeKey) then
        try
          // установление PRO_EXPORT алгоритма ключа согласования
          if not FCryptSetKeyParam(FAgreeKey, KP_ALGID, @CALG_PRO_EXPORT, 0) then
            RaiseLastCryptoApiError;
          // создание случайного сессионного ключа
          if FCryptGenKey(FCryptProv, CALG_G28147, CRYPT_EXPORTABLE, @FSessionKey) then
          try
            // экспорт сессионного ключа в BLOB
            if FCryptExportKey(FSessionKey, FAgreeKey, SIMPLEBLOB, 0, nil, @FKeyLenNeed) then
            begin
              SetLength(FSessionKeyBlob, FKeyLenNeed);
              // экспорт сессионного ключа на ключе согласования
              if not FCryptExportKey(FSessionKey, FAgreeKey, SIMPLEBLOB, 0, @FSessionKeyBlob[1], @FKeyLenNeed) then
                RaiseLastCryptoApiError;
              (*
              FSessionKeyBlob = CRYPT_SIMPLEBLOB
                bType               : offset 1,  size 1
                bVersion            : offset 2,  size 1
                reserved            : offset 3,  size 2
                aiKeyAlg            : offset 5,  size 4
                Magic               : offset 9,  size 4
                EncryptKeyAlgId     : offset 13, size 4
                bSV (bUKM)          : offset 17, size 8
                bEncryptedKey       : offset 25, size 32
                bMacKey             : offset 57, size 4
                bEncryptionParamSet : offset 61
              end;
              *)
              ASessionSV  := Copy(FSessionKeyBlob, 17, 8);
              ASessionKey := Copy(FSessionKeyBlob, 25, 32);
              ASessionMAC := Copy(FSessionKeyBlob, 57, 4);
            end
            else
              RaiseLastCryptoApiError;
            // получение вектора инициализации
            if FCryptGetKeyParam(FSessionKey, KP_IV, nil, @FKeyLenNeed, 0) then
            begin
              SetLength(AInitVector, FKeyLenNeed);
              if not FCryptGetKeyParam(FSessionKey, KP_IV, @AInitVector[1], @FKeyLenNeed, 0) then
                RaiseLastCryptoApiError;
            end else
              RaiseLastCryptoApiError;
            // зашифрованные данные
            Result := '';
            // режим шифрования CBC
            if not FCryptSetKeyParam(FSessionKey, KP_MODE, @CryptoCipherModeConst[ACipherMode], 0) then
              RaiseLastCryptoApiError;
            // режим паддинга
            if not FCryptSetKeyParam(FSessionKey, KP_PADDING, @CryptoPaddingModeConst[APaddingMode], 0) then
              RaiseLastCryptoApiError;
            FSourceDataLen := Length(ASourceData);
            FSourcePos := 1;
            // шифрование блоками по 8 байт
            while FSourcePos <= FSourceDataLen do
            begin
              // временный буфер с исходными данными для шифрования
              FEncryptBuffer := Copy(ASourceData, FSourcePos, ENCRYPT_BLOCK_LENGTH);
              FEncryptBufferLen := Length(FEncryptBuffer);
              FEncryptDataLen := FEncryptBufferLen;
              // последний блок данных
              FEncryptFinal := (FSourcePos + ENCRYPT_BLOCK_LENGTH) > FSourceDataLen;
              // получение размера буфера с учетом возможного паддинга
              if not FCryptEncrypt(FSessionKey, 0, FEncryptFinal, 0, nil, FEncryptBufferLen, 0) then
                RaiseLastCryptoApiError;
              // установка размера буфера
              SetLength(FEncryptBuffer, FEncryptBufferLen);
              // шифрование буфера данных
              if not FCryptEncrypt(FSessionKey, 0, FEncryptFinal, 0, @FEncryptBuffer[1], FEncryptDataLen, FEncryptBufferLen) then
                RaiseLastCryptoApiError;
              // накопление зашифрованных данных
              Result := Result + FEncryptBuffer;
              // переход к следующему блоку
              FSourcePos := FSourcePos + ENCRYPT_BLOCK_LENGTH;
            end
          finally
            if not FCryptDestroyKey(FSessionKey) then
              RaiseLastCryptoApiError;
          end
          else
            RaiseLastCryptoApiError;
        finally
          if not FCryptDestroyKey(FAgreeKey) then
            RaiseLastCryptoApiError;
        end
        else
          RaiseLastCryptoApiError;
      finally
        if not FCryptDestroyKey(FEphemeralKey) then
          RaiseLastCryptoApiError;
      end
      else
        RaiseLastCryptoApiError;
    finally
      ReleaseContext(FCryptProv);
    end
  finally
    CryptoFinalize;
  end;
end;
  (предполагается, что предварительный парсинг зашифрованного XML и распаковку параметров из структуры GostR3410-KeyTransport читатель сможет выполнить самостоятельно): Код:
function TCryptoApiController.DecryptData(
      const AKeyID: AnsiString; // идентификатор контейнера
      const AUserPIN: AnsiString;  // PIN код
      const AEncryptedData: AnsiString; // зашифрованные данные
      const APublicKey: AnsiString; // открытый ключ отправителя
      const ASessionKey: AnsiString; // зашифрованный сессионный ключ
      const ASessionSV: AnsiString; // синхропосылка (UKM)
      const ASessionMAC: AnsiString; // MAС сессионного ключа
      const AInitVector: AnsiString; // вектор инициализации
      const ACipherMode: TCryptoCipherMode = ccm_CBC; // режим шифрования
      const APaddingMode: TCryptoPaddingMode = cpm_ISO10126 // режим паддинга
    ): AnsiString;
const
  DECRYPT_BLOCK_LENGTH: DWORD = 8;
var
  FCryptProv: HCRYPTPROV;
  FPublicKeyBlob: AnsiString;
  FSessionKeyBlob: AnsiString;
  FPrivateKey: HCRYPTKEY;
  FAgreeKey: HCRYPTKEY;
  FSessionKey: HCRYPTKEY;
  FSourcePos: DWORD;
  FSourceDataLen: DWORD;
  FDecryptBuffer: AnsiString;
  FDecryptBufferLen: DWORD;
  FDecryptFinal: LongBool;
begin
  if AKeyID = '' then
    RaiseCryptoException(CLE_CKA_ID_IS_EMPTY);
  CryptoInitialize;
  try
    CryptoLogMessage(CLM_DECRYPT_DATA);
    AcquireContext(@FCryptProv, PWideChar(WideString(AKeyID)), CRYPT_SILENT, AUserPIN);
    try
      // получение закрытого ключа
      if FCryptGetUserKey(FCryptProv, AT_KEYEXCHANGE, @FPrivateKey) then
      try
        // сборка PublicKey BLOB из статической части и открытого ключа
        FPublicKeyBlob :=
          #$06#$20#$00#$00#$23#$2E#$00#$00#$4D#$41#$47#$31#$00#$02#$00#$00#$30#$12 +
          #$06#$07#$2A#$85#$03#$02#$02#$24#$00#$06#$07#$2A#$85#$03#$02#$02#$1E#$01 +
          APublicKey;
        // получение ключа согласования импортом открытого ключа отправителя
        // на закрытом ключе
        if FCryptImportKey(FCryptProv, @FPublicKeyBlob[1], Length(FPublicKeyBlob), FPrivateKey, 0, @FAgreeKey) then
        try
          // установление PRO_EXPORT алгоритма ключа согласования
          if not FCryptSetKeyParam(FAgreeKey, KP_ALGID, @CALG_PRO_EXPORT, 0) then
            RaiseLastCryptoApiError;
          (*
          FSessionKeyBlob = CRYPT_SIMPLEBLOB
            bType               : offset 1,  size 1
            bVersion            : offset 2,  size 1
            reserved            : offset 3,  size 2
            aiKeyAlg            : offset 5,  size 4
            Magic               : offset 9,  size 4
            EncryptKeyAlgId     : offset 13, size 4
            bSV (bUKM)          : offset 17, size 8
            bEncryptedKey       : offset 25, size 32
            bMacKey             : offset 57, size 4
            bEncryptionParamSet : offset 61
          end;
          *)
          // сборка SessionKey BLOB из статической части и параметров сессионного ключа
          FSessionKeyBlob :=
            #$01#$20#$00#$00#$1E#$66#$00#$00#$FD#$51#$4A#$37#$1E#$66#$00#$00 +
            ASessionSV + ASessionKey + ASessionMAC +
            #$30#$09#$06#$07 + // ASN.1 Sequence + OID Header
            #$2A#$85#$03#$02#$02#$1F#$01; // OID_GOST_R28147_89_CryptoPro_A_ParamSet 1.2.643.2.2.31.1
          // получение сессионного ключа импортом зашифрованного сессионного ключа
          // на ключе согласования
          if FCryptImportKey(FCryptProv, @FSessionKeyBlob[1], Length(FSessionKeyBlob), FAgreeKey, 0, @FSessionKey) then
          try
            // расшифрованные данные
            Result := '';
            // установка вектора инициализации
            if not FCryptSetKeyParam(FSessionKey, KP_IV, @AInitVector[1], 0) then
              RaiseLastCryptoApiError;
            // режим шифрования CBC
            if not FCryptSetKeyParam(FSessionKey, KP_MODE, @CryptoCipherModeConst[ACipherMode], 0) then
              RaiseLastCryptoApiError;
            // режим паддинга
            if not FCryptSetKeyParam(FSessionKey, KP_PADDING, @CryptoPaddingModeConst[APaddingMode], 0) then
              RaiseLastCryptoApiError;
            FSourceDataLen := Length(AEncryptedData);
            FSourcePos := 1;
            // расщифровка блоками по 8 байт
            while FSourcePos <= FSourceDataLen do
            begin
              // временный буфер с исходными данными для расшифровки
              FDecryptBuffer := Copy(AEncryptedData, FSourcePos, DECRYPT_BLOCK_LENGTH);
              FDecryptBufferLen := Length(FDecryptBuffer);
              // последний блок данных
              FDecryptFinal := (FSourcePos + DECRYPT_BLOCK_LENGTH) > FSourceDataLen;
              // расшифровка буфера данных
              if not FCryptDecrypt(FSessionKey, 0, FDecryptFinal, 0, @FDecryptBuffer[1], FDecryptBufferLen) then
                RaiseLastCryptoApiError;
              // обновление размера буфера на реальный размер расшифрованных данных
              SetLength(FDecryptBuffer, FDecryptBufferLen);
              // накопление рашифрованных данных
              Result := Result + FDecryptBuffer;
              // переход к следующему блоку
              FSourcePos := FSourcePos + DECRYPT_BLOCK_LENGTH;
            end
          finally
            if not FCryptDestroyKey(FSessionKey) then
              RaiseLastCryptoApiError;
          end
          else
            RaiseLastCryptoApiError;
        finally
          if not FCryptDestroyKey(FAgreeKey) then
            RaiseLastCryptoApiError;
        end
        else
          RaiseLastCryptoApiError;
      finally
        if not FCryptDestroyKey(FPrivateKey) then
          RaiseLastCryptoApiError;
      end
      else
        RaiseLastCryptoApiError;
    finally
      ReleaseContext(FCryptProv);
    end
  finally
    CryptoFinalize;
  end;
end;
 |