Статус: Участник
Группы: Участники
Зарегистрирован: 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 "Сырые" зашифрованные данные, закодированные в Base64 1.2 Первые 8 байт - вектор инициализации (IV) 2. Зашифрованный сессионный ключ и параметры шифрования (EncryptedData -> KeyInfo -> EncryptedKey -> CipherData) 2.1 Значение представляет собой ASN.1 структуру GostR3410-KeyTransport, закодированную в Base64 2.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 читатель сможет выполнить самостоятельно): Код:
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;
Пример расшифровки буфера низкоуровневыми функциями на Delphi (предполагается, что предварительный парсинг зашифрованного 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;
|