шифрование ГОСТ на php и электронные больничные ФСС

Возникла задача - добавить в нашу МИС интеграцию с сервисом ФСС по обработке электронных больничных - здесь описание.
МИСка наша крутится на сервере с линуксом. Реализовать взаимодействие с сервисом оказалось в итоге не просто.
Опытом своим хочу поделиться
Возникла задача - добавить в нашу МИС интеграцию с сервисом ФСС по обработке электронных больничных - здесь описание.
МИСка наша крутится на сервере с линуксом. Реализовать взаимодействие с сервисом оказалось в итоге не просто.
Опытом своим хочу поделиться
Статья пока незакончена, в процессе....

У меня сервер на CENTOS достаточно старенький конечно - ну что поделать, так исторически сложилось©, работать то надо.

1 шаг. Установка Крипто-Про
Скачиваем и устанавливаем Крипто-Про
У меня лицензия на 4-ю версию, поэтому берем ее - https://www.cryptopro.ru/products/csp/downloads#latest_csp40r4_linux
Выбор пал на КриптоПро CSP 4.0 для Linux (x64, rpm) https://www.cryptopro.ru/sites/default/files/private/csp/40/9963/linux-amd64.tgz
Также нам понадобится КриптоПро ЭЦП SDK 2.0 (нам нужна поддержка ГОСТ-2012 - в более ранних версиях ее нет) https://www.cryptopro.ru/products/cades/downloads
выбор опять же пал на - Linux 64 бита https://www.cryptopro.ru/sites/default/files/products/cades/current_release_2_0/cades_linux_amd64.tar.gz

Также на всякий случай лучше запастись stunnel-msspi https://www.cryptopro.ru/products/other/stunnel-msspi, нам он понадобился в следующем проекте с МДЛП о котором я расскажу вот здесь (типа ссылка тут будет)
Закидываем скаченные архивы на сервер, распаковываем - там куча rpm-ок
[root@bars srv]# tar -xf linux-amd64.tgz
[root@bars srv]# cd linux-amd64
[root@bars linux-amd64]# ls -la
total 21360
drwxr-xr-x  2 nobody nobody    4096 Nov 23  2018 .
drwxr-xr-x. 4 root   root      4096 Jun  5 11:23 ..
-rw-r--r--  1 nobody nobody    2203 Nov 23  2018 cprocsp-compat-altlinux-64-1.0.0-1.noarch.rpm
-rw-r--r--  1 nobody nobody 3202690 Nov 23  2018 cprocsp-cpopenssl-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   12244 Nov 23  2018 cprocsp-cpopenssl-base-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody  323564 Nov 23  2018 cprocsp-cpopenssl-devel-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody   39404 Nov 23  2018 cprocsp-cpopenssl-gost-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody  417875 Nov 23  2018 cprocsp-curl-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody 2089134 Nov 23  2018 cprocsp-drv-64-4.0.9963-5.src.rpm
-rw-r--r--  1 nobody nobody 2132178 Nov 23  2018 cprocsp-drv-devel-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody   84568 Nov 23  2018 cprocsp-ipsec-devel-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody  170341 Nov 23  2018 cprocsp-ipsec-esp-64-4.0.9963-5.src.rpm
-rw-r--r--  1 nobody nobody   33792 Nov 23  2018 cprocsp-ipsec-genpsk-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   89975 Nov 23  2018 cprocsp-ipsec-ike-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   18558 Nov 23  2018 cprocsp-rdr-emv-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   58823 Nov 23  2018 cprocsp-rdr-esmart-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   30293 Nov 23  2018 cprocsp-rdr-gui-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   49544 Nov 23  2018 cprocsp-rdr-gui-gtk-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   15854 Nov 23  2018 cprocsp-rdr-infocrypt-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   33521 Nov 23  2018 cprocsp-rdr-inpaspot-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody  466398 Nov 23  2018 cprocsp-rdr-jacarta-64-3.6.408.695-4.x86_64.rpm
-rw-r--r--  1 nobody nobody   12460 Nov 23  2018 cprocsp-rdr-mskey-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   12706 Nov 23  2018 cprocsp-rdr-novacard-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   45655 Nov 23  2018 cprocsp-rdr-pcsc-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   13911 Nov 23  2018 cprocsp-rdr-rosan-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   47162 Nov 23  2018 cprocsp-rdr-rutoken-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody  144178 Nov 23  2018 cprocsp-rsa-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody  134583 Nov 23  2018 cprocsp-stunnel-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   49816 Nov 23  2018 cprocsp-xer2print-4.0.9963-5.noarch.rpm
-rwxr-xr-x  1 nobody nobody  301229 Nov 23  2018 cpverify
-rw-r--r--  1 nobody nobody   63820 Nov 23  2018 ifd-rutokens-1.0.1-1.x86_64.rpm
-rwxr-xr-x  1 nobody nobody     211 Nov 23  2018 install.desktop
-rwxr-xr-x  1 nobody nobody   20617 Nov 23  2018 install_gui.sh
-rwxr-xr-x  1 nobody nobody   10548 Nov 23  2018 install.sh
-rwxr-xr-x  1 nobody nobody    6718 Nov 23  2018 integrity.sh
-rw-r--r--  1 nobody nobody      88 Nov 23  2018 linux-amd64.ini
-rw-r--r--  1 nobody nobody  186031 Nov 23  2018 lsb-cprocsp-base-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody   11667 Nov 23  2018 lsb-cprocsp-ca-certs-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody 6755056 Nov 23  2018 lsb-cprocsp-capilite-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody 1197101 Nov 23  2018 lsb-cprocsp-devel-4.0.9963-5.noarch.rpm
-rw-r--r--  1 nobody nobody  720274 Nov 23  2018 lsb-cprocsp-kc1-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody 1015425 Nov 23  2018 lsb-cprocsp-kc2-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody  192800 Nov 23  2018 lsb-cprocsp-pkcs11-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody 1478332 Nov 23  2018 lsb-cprocsp-rdr-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody    8682 Nov 23  2018 lsb-cprocsp-rdr-accord-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   10722 Nov 23  2018 lsb-cprocsp-rdr-ancud-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   13518 Nov 23  2018 lsb-cprocsp-rdr-crypton-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody    8297 Nov 23  2018 lsb-cprocsp-rdr-maxim-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody    8719 Nov 23  2018 lsb-cprocsp-rdr-sobol-64-4.0.9963-5.x86_64.rpm
-rw-r--r--  1 nobody nobody   10814 Nov 23  2018 sobol-1-7.src.rpm
-rwxr-xr-x  1 nobody nobody    3574 Nov 23  2018 uninstall.sh
ну что, шашки наголо и в атаку!
[root@bars linux-amd64]# ./install.sh
Uninstalling CSP packages...
CSP packages have been successfully uninstalled
Installing lsb-cprocsp-base-4.0.9963-5.noarch.rpm ...
error: Failed dependencies:
        lsb-core-noarch >= 3.0 is needed by lsb-cprocsp-base-4.0.9963-5.noarch
Error: installation failed. LSB package may not be installed.
Install LSB package and reinstall CryptoPro CSP. If it does not help, please
read installation documentation or contact the manufacturer: support@cryptopro.ru.
Ха, не так все просто (а кто вообще обещал что будет легко?)
Ну давай, потанцуем
[root@bars linux-amd64]# yum install lsb-core-noarch
... тут куча всякого, в итоге
Installed:
  redhat-lsb-core.x86_64 0:4.0-7.el6.centos

Dependency Installed:
  at.x86_64 0:3.1.10-49.el6                      bc.x86_64 0:1.06.95-1.el6            cvs.x86_64 0:1.11.23-16.el6
  ed.x86_64 0:1.1-3.3.el6                        gettext.x86_64 0:0.17-18.el6         mailx.x86_64 0:12.4-10.el6_10
  patch.x86_64 0:2.6-8.el6_9                     pax.x86_64 0:3.4-10.1.el6            perl-CGI.x86_64 0:3.51-144.el6
  perl-Test-Simple.x86_64 0:0.92-144.el6         time.x86_64 0:1.7-38.el6

Dependency Updated:
  perl.x86_64 4:5.10.1-144.el6                                   perl-CPAN.x86_64 0:1.9402-144.el6
  perl-Digest-SHA.x86_64 1:5.47-144.el6                          perl-ExtUtils-MakeMaker.x86_64 0:6.55-144.el6
  perl-ExtUtils-ParseXS.x86_64 1:2.2003.0-144.el6                perl-Module-Pluggable.x86_64 1:3.90-144.el6
  perl-Pod-Escapes.x86_64 1:1.04-144.el6                         perl-Pod-Simple.x86_64 1:3.13-144.el6
  perl-Test-Harness.x86_64 0:3.17-144.el6                        perl-devel.x86_64 4:5.10.1-144.el6
  perl-libs.x86_64 4:5.10.1-144.el6                              perl-version.x86_64 3:0.77-144.el6

Complete!
[root@bars linux-amd64]#
Ну будем надеятся что ничего не поломается - вон сколько всего понаобновлял... :)
Второй заход...
[root@bars linux-amd64]# ./install.sh
Uninstalling CSP packages...
CSP packages have been successfully uninstalled
Installing lsb-cprocsp-base-4.0.9963-5.noarch.rpm ...
Installing lsb-cprocsp-rdr-64-4.0.9963-5.x86_64.rpm ...

Adding new reader:
Nick name: FLASH
Name device: FLASH
Succeeded, code:0x0
Installing lsb-cprocsp-kc1-64-4.0.9963-5.x86_64.rpm ...
Installing lsb-cprocsp-capilite-64-4.0.9963-5.x86_64.rpm ...
Installing cprocsp-curl-64-4.0.9963-5.x86_64.rpm lsb-cprocsp-ca-certs-4.0.9963-5.noarch.rpm ...

CSP packages have been successfully installed
Основные каталоги в которых понадобится полазить
[root@bars linux-amd64]# ls -la /etc/opt/cprocsp/
total 40
drwxr-xr-x  2 root root  4096 Jun  5 11:33 .
drwxr-xr-x. 3 root root  4096 Jun  5 11:33 ..
-rw-r--r--  1 root root 17703 Jun  5 11:33 config64.ini
-rw-r--r--  1 root root   255 Jun  5 11:33 license.ini
-rw-r--r--  1 root root    33 Nov 22  2018 release
-rwxr-xr-x  1 root root  1206 Nov 22  2018 stunnel.conf
[root@bars linux-amd64]# ls -la /var/opt/cprocsp/
total 32
drwxr-xr-x   8 root root 4096 Jun  5 11:33 .
drwxr-xr-x.  3 root root 4096 Jun  5 11:33 ..
drwxr-xr-x   4 root root 4096 Jun  5 11:33 dsrf
drwxrwxrwt   3 root root 4096 Jun  5 11:33 keys
drwx--x--x  10 root root 4096 Jun  5 11:33 mnt
drwxrwxrwt   2 root root 4096 Jun  5 11:33 tmp
drwxr-xr-x   4 root root 4096 Jun  5 11:33 tmpcerts
drwxrwxrwt   4 root root 4096 Jun  5 11:33 users
[root@bars linux-amd64]# ls -la /opt/cprocsp/
total 24
drwxr-xr-x  6 root root 4096 Jun  5 11:33 .
drwxr-xr-x. 8 root root 4096 Jun  5 11:33 ..
drwxr-xr-x  3 root root 4096 Jun  5 11:33 bin
drwxr-xr-x  4 root root 4096 Jun  5 11:33 lib
drwxr-xr-x  3 root root 4096 Jun  5 11:33 sbin
drwxr-xr-x  4 root root 4096 Jun  5 11:33 share
2 шаг. Устанавливаем SDK
[root@bars srv]# tar -xf cades_linux_amd64.tar.gz
[root@bars srv]# cd cades_linux_amd64
[root@bars cades_linux_amd64]# ls -la
total 31880
drwxr-xr-x  2 meksik games     4096 Sep 19  2019 .
drwxr-xr-x. 5 root   root      4096 Jun  5 11:53 ..
-rw-------  1 meksik games 13744817 Sep 17  2019 cprocsp-pki-2.0.0-amd64-cades.rpm
-rw-------  1 meksik games   307637 Sep 17  2019 cprocsp-pki-2.0.0-amd64-phpcades.rpm
-rw-------  1 meksik games   812649 Sep 17  2019 cprocsp-pki-2.0.0-amd64-plugin.rpm
-rw-------  1 meksik games 13727006 Sep 17  2019 cprocsp-pki-cades_2.0.0-1_amd64.deb
-rw-------  1 meksik games   302540 Sep 17  2019 cprocsp-pki-phpcades_2.0.0-1_amd64.deb
-rw-------  1 meksik games   804876 Sep 17  2019 cprocsp-pki-plugin_2.0.0-1_amd64.deb
-rw-r--r--  1 meksik games      212 Sep 19  2019 ._.DS_Store
-rw-r--r--  1 meksik games     6148 Sep 19  2019 .DS_Store
-rw-------  1 meksik games      212 Aug 13  2019 ._lsb-cprocsp-devel_5.0.11535-4_all.deb
-rw-------  1 meksik games  1442962 Aug 13  2019 lsb-cprocsp-devel_5.0.11535-4_all.deb
-rw-------  1 meksik games      212 Aug 13  2019 ._lsb-cprocsp-devel-5.0.11535-4.noarch.rpm
-rw-------  1 meksik games  1455146 Aug 13  2019 lsb-cprocsp-devel-5.0.11535-4.noarch.rpm
[root@bars cades_linux_amd64]#
нам нужно установить cprocsp-pki-2.0.0-amd64-cades.rpm, cprocsp-pki-2.0.0-amd64-phpcades.rpm и lsb-cprocsp-devel-5.0.11535-4.noarch.rpm
[root@bars cades_linux_amd64]# rpm -i cprocsp-pki-2.0.0-amd64-cades.rpm
License 0A202-U0030-00ECW-RRLMF-UU2WK is set
[ErrorCode: 0x00000000]
License TA200-G0030-00ECW-RRLNE-BTDVV is set
[ReturnCode: 0x00000000]
[root@bars cades_linux_amd64]# rpm -i cprocsp-pki-2.0.0-amd64-phpcades.rpm
[root@bars cades_linux_amd64]#
Если устанавливать rpm-ку от Крипто-Про 4, lsb-cprocsp-devel-4.0.9963-5.noarch.rpm, то она не даст собрать расширение, надо устанавливать именно ту которая во втором архиве, но она просит зависимость - КриптоПро 5, потому указываем игнорировать зависимость
[root@bars cades_linux_amd64]# rpm -i lsb-cprocsp-devel-5.0.11535-4.noarch.rpm
error: Failed dependencies:
        lsb-cprocsp-base >= 5.0 is needed by lsb-cprocsp-devel-5.0.11535-4.noarch
[root@bars cades_linux_amd64]# rpm -i --nodeps lsb-cprocsp-devel-5.0.11535-4.noarch.rpm
3 шаг Сборка PHP расширения
смотрим какая версия php установлена, и смотрим установлен ли пакет devel чтобы мы могли собрать расширение phpcades
[root@bars cades_linux_amd64]# yum list installed |grep php
cprocsp-pki-phpcades.x86_64                2.0.0-1                   installed
php56w.x86_64                              5.6.40-1.w6               @webtatic
php56w-cli.x86_64                          5.6.40-1.w6               @webtatic
php56w-common.x86_64                       5.6.40-1.w6               @webtatic
php56w-devel.x86_64                        5.6.40-1.w6               @webtatic
php56w-gd.x86_64                           5.6.40-1.w6               @webtatic
php56w-imap.x86_64                         5.6.40-1.w6               @webtatic
php56w-ldap.x86_64                         5.6.40-1.w6               @webtatic
php56w-mbstring.x86_64                     5.6.40-1.w6               @webtatic
php56w-mysql.x86_64                        5.6.38-1.w6               @webtatic
php56w-pdo.x86_64                          5.6.40-1.w6               @webtatic
php56w-pear.noarch                         1:1.10.4-1.w6             @webtatic
php56w-pgsql.x86_64                        5.6.40-1.w6               @webtatic
php56w-process.x86_64                      5.6.40-1.w6               @webtatic
php56w-soap.x86_64                         5.6.40-1.w6               @webtatic
php56w-xml.x86_64                          5.6.40-1.w6               @webtatic

[root@bars cades_linux_amd64]# cd /opt/cprocsp/src/phpcades/
если нет devel - устанавливаем или скачиваем исходники текущей версии php читаем /opt/cprocsp/src/phpcades/README.txt, приступаем в файле /opt/cprocsp/src/phpcades/Makefile.unix в первой строке ставим PHPDIR=/usr/include/php далее, сказано поставить libboost-dev - надо, значит надо...
[root@bars phpcades]# yum install boost-devel
...
Installed:
  boost-devel.x86_64 0:1.41.0-28.el6

Dependency Installed:
  boost.x86_64 0:1.41.0-28.el6            boost-date-time.x86_64 0:1.41.0-28.el6      boost-filesystem.x86_64 0:1.41.0-28.el6       boost-graph.x86_64 0:1.41.0-28.el6
  boost-iostreams.x86_64 0:1.41.0-28.el6  boost-math.x86_64 0:1.41.0-28.el6           boost-program-options.x86_64 0:1.41.0-28.el6  boost-python.x86_64 0:1.41.0-28.el6
  boost-regex.x86_64 0:1.41.0-28.el6      boost-serialization.x86_64 0:1.41.0-28.el6  boost-signals.x86_64 0:1.41.0-28.el6          boost-system.x86_64 0:1.41.0-28.el6
  boost-test.x86_64 0:1.41.0-28.el6       boost-thread.x86_64 0:1.41.0-28.el6         boost-wave.x86_64 0:1.41.0-28.el6             libicu.x86_64 0:4.2.1-15.el6_10

Complete!
[root@bars phpcades]#
[root@bars phpcades]#
[root@bars phpcades]# eval `/opt/cprocsp/src/doxygen/CSP/../setenv.sh --64`; make -f Makefile.unix
g++  -DLINUX  -DUNIX -DHAVE_LIMITS_H  -D_COMPACT -DHAVE_STDINT_H  -I/opt/cprocsp/include/ -I/opt/cprocsp/include/cpcsp -I/opt/cprocsp/include/pki -I/opt/cprocsp/include/pki/atl -I/opt/cprocsp/include/pk
i/cppcades -I/opt/cprocsp/include/pki/cplib -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/Zend -I/usr/include/php/TSRM  -DSIZEOF_VOID_P=8 -fPIC -DPIC -c -o PHPCadesCPSigners.o PHPCadesCP
Signers.cpp
.....
g++  -shared PHPCadesCPSigners.o PHPCadesCPSigner.o PHPCadesCPExtendedKeyUsage.o PHPCadesCPAttribute.o PHPCadesCPEKU.o PHPCadesCPEKUs.o PHPCadesCPBasicConstraints.o PHPCadesCPSignedData.o PHPCadesCPPubl
icKey.o PHPCadesCPPrivateKey.o PHPCadesCPOID.o PHPCadesCPEncodedData.o PHPCadesCPAttributes.o PHPCadesCPCertificateStatus.o PHPCadesCPEnvelopedData.o PHPCadesCPAlgorithm.o PHPCadesCPRecipients.o PHPCade
sCPKeyUsage.o PHPCadesAbout.o PHPCadesCPCardholderData.o PHPCadesCPCertificates.o PHPCadesCPSignedXML.o PHPCadesCPHashedData.o PHPCadesCPRawSignature.o PHPCadesCPCertificate.o PHPCadesCPStore.o dllmain.
o PHPCadesVersion.o PHPCadesSymmetricAlgorithm.o errormsg.o  -L/opt/cprocsp/lib/amd64 -lcppcades -lcapi10 -lcapi20 -lrdrsup -lcplib  -g -o libphpcades.so
[root@bars phpcades]#
Скопируем его ко всем остальным модулям и включим
[root@bars phpcades]# cp libphpcades.so /usr/lib64/php/modules
[root@bars phpcades]# echo "extension=libphpcades.so" > /etc/php.d/cryptopro.ini
Все. на этом установка расширения для PHP завершена. Но для php версии 7 так легко все не проходит, нужны дополнительные шаги
Download file EncryptFSS.c
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#   include <windows.h>
#   include <wincrypt.h>
#   include <tchar.h>
#else
#   include <stdlib.h>
#   include <CSP_WinDef.h>
#   include <CSP_WinCrypt.h>
#   include "reader/tchar.h"
#endif
#include <WinCryptEx.h>
 
#include <stdint.h>
 
//#define DEBUG_OUT 1
 
#define BLOCK_LENGTH 8
#define ASN1_LENGTH 172
 
#define ENCRKEY_OFFSET 7
#define ENCRKEY_LEN 32
 
#define MACKEY_OFFSET 41
#define MACKEY_LEN 4
 
#define PUBKEY_OFFSET 98
#define PUBKEY_LEN 64
 
#define SV_OFFSET 164
#define SV_LEN 8
 
static BYTE encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                                'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                                'w', 'x', 'y', 'z', '0', '1', '2', '3',
                                '4', '5', '6', '7', '8', '9', '+', '/'};
static BYTE *decoding_table = NULL;
static int mod_table[] = {0, 2, 1};
 
BYTE * base64_encode(const BYTE *data, DWORD input_length, DWORD *output_length);
static void HandleError(char *s);
static void CleanUp(void);
 
static HCRYPTPROV hProv = 0;		    // Дескриптор CSP 
static HCRYPTKEY hKey = 0;		        // Дескриптор закрытого ключа 
static HCRYPTKEY hSessionKey = 0;	    // Дескриптор сессионного ключа
static HCRYPTKEY hAgreeKey = 0;         // Дескриптор ключа согласования
static HCRYPTKEY hEphemeralKey = 0;	    // Дескриптор эфемерного ключа
 
static FILE *fhCert=NULL;		        // Файл, в котором хранится сертификат
static FILE *fhSource=NULL;       		// Исходный файл
static FILE *fhOutput=NULL;       		// Исходный файл
#ifdef DEBUG_OUT
static FILE *fhEncrypt=NULL;		    // Зашифрованный файл
static FILE *fhSession_SV=NULL;         // Файл для хранения сессионного ключа (вектор инициализации)
static FILE *fhSession_EncryptedKey=NULL; // Файл для хранения сессионного ключа (зашифрованный ключ)
static FILE *fhSession_PublicKey=NULL;  // Файл для хранения публичного эфемерного ключа
static FILE *fhSession_MacKey=NULL;	    // Файл для хранения сессионного ключа (имита)
static FILE *fhVector=NULL;		        // Файл для хранения вектора инициализации
static FILE *fhEncryptionParam;         // Файл для хранения неменяемой части блоба
#endif
 
static BYTE *pbKeyBlobSimple = NULL;    // Указатель на сессионный ключевой BLOB 
static BYTE *pbIV = NULL;		        // Вектор инициализации сессионного ключа
static BYTE *pbCipherValue = NULL;      // Указатель шифрованный контент
 
#define MAX_PUBLICKEYBLOB_SIZE 200
 
int main(int argc, char *argv[])
{
    BYTE  pbKeyBlob[MAX_PUBLICKEYBLOB_SIZE];	            // Указатель на ключевой BLOB 
    DWORD dwBlobLen = MAX_PUBLICKEYBLOB_SIZE;	            // Длина ключевого BLOBа
    DWORD dwBlobLenSimple;                                  // Длина сессионного ключевого BLOBа
 
    BYTE  pbSenderPublicKeyBlob[MAX_PUBLICKEYBLOB_SIZE];    // Указатель на ключевой BLOB публичный, отправителя
    DWORD dwSenderPublicBlobLen = MAX_PUBLICKEYBLOB_SIZE; 	// Длина ключевого BLOBа, публичного, отправителя
    DWORD keyParam = CRYPT_MODE_CBC;    
    DWORD paddingMode = ISO10126_PADDING; //cpm_ISO10126 
 
    BYTE * pbBufferB64;
    DWORD dwBufferB64Len;
    DWORD dwCipherValue;
 
    BYTE * pbPubCertData;
    DWORD dwPubCertData;
 
    BYTE * pbContent;                   // указатель на исходное содержимое
    DWORD cbContent = 0;	            // Длина содержимого
    DWORD dwIV = 0;		                // Длина вектора инициализации
    DWORD bufLen = sizeof(pbContent);   // Длина буфера
    ALG_ID ke_alg = CALG_PRO_EXPORT /*CALG_PRO12_EXPORT*/;          // алгоритм ключа согласования
    DWORD cbEncryptionParamSetStandart; // длина неизменяемой части блоба
 
    DWORD cbCert = 4000;                // максимальный размер файла сертификата
    BYTE  pbCert[4000];                 // указатель на сертификат
    PCCERT_CONTEXT pCertContext = NULL;
    HCRYPTKEY hPubKey;
 
    BYTE pbKeyCipherValue[ASN1_LENGTH] = {0x30, 0x81, 0xA9, 0x30, 0x28, 4, 0x20,
                                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // offs 7: len 32 = Session Encrypted Key
                                          4, 4, 
                                          0, 0, 0, 0,                                                                                      // offs 41 len 4 = Session Mac Key
                                          0xA0, 0x7D, 
                                          6, 9, 0x2A, 0x85, 3, 7, 1, 2, 5, 1, 1,     // OBJECT IDENTIFIER 1.2.643.7.1.2.5.1.1 tc26CipherZ (TC26 params Z for GOST 28147-89)
                                          0xA0, 0x66, 0x30, 0x1F,
                                          6, 8, 0x2A, 0x85, 3, 7, 1, 1, 1, 1,        // OBJECT IDENTIFIER 1.2.643.7.1.1.1.1 gost2012PublicKey256 (GOST R 34.10-2012 256 bit public key)
                                          0x30, 0x13, 
                                          6, 7, 0x2A, 0x85, 3, 2, 2, 0x24, 0,        // OBJECT IDENTIFIER 1.2.643.2.2.36.0 cryptoProSignXA (CryptoPro ell.curve XA for GOST R 34.10-2001)
                                          6, 8, 0x2A, 0x85, 3, 7, 1, 1, 2, 2, 3,     // OBJECT IDENTIFIER 1.2.643.7.1.1.2.2 gost2012Digest256 (GOST R 34.11-2012 256 bit digest)
                                          0x43, 0, 4, 0x40, 
                                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // offs 98 = : len 64 = Session Public Key
                                          0x4, 0x8,
                                          0, 0, 0, 0, 0, 0, 0, 0                                                                           // offs 164: len 8 = Session SV
                                          };   // Указатель на структуру ASN1 для ключа
    DWORD dwKeyCipherValue = ASN1_LENGTH;
 
    static BYTE xml_1[] = 
    "<?xml version=\"1.1\" encoding=\"UTF-8\"?>"
    "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
        "<SOAP-ENV:Header/>"
        "<SOAP-ENV:Body>"
            "<EncryptedData xmlns=\"http://www.w3.org/2001/04/xmlenc#\" Type=\"http://www.w3.org/2001/04/xmlenc#Element\">"
                "<EncryptionMethod Algorithm=\"urn:ietf:params:xml:ns:cpxmlsec:algorithms:gost28147\"/>"
                "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
                    "<EncryptedKey xmlns=\"http://www.w3.org/2001/04/xmlenc#\">"
                        "<EncryptionMethod Algorithm=\"urn:ietf:params:xml:ns:cpxmlsec:algorithms:transport-gost2001\"/>"
                        "<KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"
                            "<X509Data>"
                                "<X509Certificate>";
    static BYTE xml_2[] =       "</X509Certificate>"
                            "</X509Data>"
                        "</KeyInfo>"
                        "<CipherData>"
                            "<CipherValue>";
    static BYTE xml_3[] =   "</CipherValue>"
                        "</CipherData>"
                    "</EncryptedKey>"
                "</KeyInfo>"
                "<CipherData>"
                    "<CipherValue>";
    static BYTE xml_4[] = 
                    "</CipherValue>"
                "</CipherData>"
            "</EncryptedData>"
        "</SOAP-ENV:Body>"
    "</SOAP-ENV:Envelope>";
 
 
    if(argc < 5)
    {
        printf("Encryption tool for FSS ELN service.\nver 0.01, IbZ(c) Studio (tm)  :)\n" );
        printf("\n\tusing: %s <container_name> <certificate_filename> <source_file> <output.xml>\n\n", argv[0]);
        HandleError( "Not enough input parameters given" );
    } 
 
    // Открытие файла, который будет зашифрован.
    if(!(fhSource = fopen(argv[3], "rb"))) HandleError( "Problem opening the source file \n" );
    fseek(fhSource, 0, SEEK_END);
    cbContent = ftell(fhSource);
    rewind(fhSource);
#ifdef DEBUG_OUT
    printf( "01. The file '%s' was opened, length = %d bytes\n", argv[3],cbContent );
#endif
    pbContent = (BYTE *)malloc(cbContent);
    if(!pbContent) HandleError("Out of memory. \n");
 
    if(!(fhOutput = fopen(argv[4], "wb"))) HandleError( "Problem opening the file output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe file '%s' was opened\n", argv[4] );
    // Открытие файла, в который будет производится запись блока зашифрованного файла.
    if(!(fhEncrypt = fopen("encrypt.bin", "wb"))) HandleError( "Problem opening the file 'encrypt.bin'\n" );
    printf( "\tThe file 'encrypt.bin' was opened\n" );
 
    // Открытие файла, в который производится запись синхропосылки.
    if(!(fhSession_SV = fopen("session_SV.bin", "wb"))) HandleError( "Problem opening the file 'session_SV.bin'\n" );
    printf( "\tThe file 'session_SV.bin' was opened\n" );
 
    // Открытие файла, в который производится запись сессионного ключа.
    if(!(fhSession_EncryptedKey = fopen("session_EncryptedKey.bin", "wb"))) HandleError( "Problem opening the file 'session_EncryptedKey.bin'\n" );
    printf( "\tThe file 'session_EncryptedKey.bin' was opened\n" );
 
    if(!(fhSession_PublicKey = fopen("session_PublicKey.bin", "wb"))) HandleError( "Problem opening the file 'session_PublicKey.bin'\n" );
    printf( "\tThe file 'session_PublicKey.bin' was opened\n" );
 
    // Открытие файла, в который производится запись MAC сессионного ключа.
    if(!(fhSession_MacKey = fopen("session_MacKey.bin", "wb"))) HandleError( "Problem opening the file 'session_MacKey.bin'\n" );
    printf( "\tThe file 'session_MacKey.bin' was opened\n" );
 
    // Открытие файла, в который производится запись вектора инициализации.
    if(!(fhVector = fopen("vector.bin", "wb"))) HandleError( "Problem opening the file 'vector.bin'\n" );
    printf( "\tThe file 'vector.bin' was opened\n" );
 
    // Открытие файла, в который производится запись вектора инициализации.
    if (!(fhEncryptionParam = fopen("EncryptionParam.bin", "wb"))) HandleError("Problem opening the file 'EncryptionParam.bin'\n");
    printf( "\tThe file 'EncryptionParam.bin' was opened\n");
#endif
 
 
    // Получение дескриптора контейнера отправителя, находящегося в рамках провайдера. 
    // if(CryptAcquireContext( &hProv, _TEXT("HDIMAGE\\\\2019rnik.000\\026C"), NULL, PROV_GOST_2012_256, 0)) 
    if(!CryptAcquireContext( &hProv, argv[1], NULL, PROV_GOST_2012_256, 0)) HandleError("Error during CryptAcquireContext.");
#ifdef DEBUG_OUT
    printf("02. The key container \"%s\" has been acquired. \n", argv[1]);
#endif
    // Загрузка PUBLICKEYBLOB из сертификата, открытие файла, в котором содержится открытый ключ получателя.
    if((fhCert = fopen(argv[2], "rb")))
    {
#ifdef DEBUG_OUT
	    printf( "\tThe file '%s' was opened\n",  argv[2]);
#endif
	    cbCert = (DWORD)fread(pbCert, 1, cbCert, fhCert);
        if(!cbCert) HandleError( "Failed to read certificate\n" );
#ifdef DEBUG_OUT
        printf( "\tCertificate was read from the '%s'\n", argv[2] );
#endif
        pCertContext = CertCreateCertificateContext ( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pbCert, cbCert);
        if (!pCertContext) HandleError( "CertCreateCertificateContext" );
 
   	    // Импортируем открытый ключ
        if (!CryptImportPublicKeyInfoEx( hProv, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &(pCertContext->pCertInfo->SubjectPublicKeyInfo), 0, 0, NULL, &hPubKey)) 
        {
            CertFreeCertificateContext(pCertContext);
            HandleError( "CryptImportPublicKeyInfoEx" );
        }
#ifdef DEBUG_OUT
        printf("\tPublic key imported from cert file\n");
#endif
        CertFreeCertificateContext(pCertContext);
 
        // экспорт открытого ключа получателя в BLOB
        if (!CryptExportKey( hPubKey, 0, PUBLICKEYBLOB, 0, pbKeyBlob, &dwBlobLen)) HandleError( "CryptExportKey" );
#ifdef DEBUG_OUT
        printf("\tPublic key exported to blob\n");
#endif
    }
 
// begin hack
    // генерация эфемерной ключевой пары
    if(!CryptGenKey(hProv, /* CALG_DH_EL_EPHEM */ CALG_DH_GR3410_12_256_EPHEM, CRYPT_EXPORTABLE, &hEphemeralKey)) HandleError("Error during CryptGenKey Ephemeral key.");
#ifdef DEBUG_OUT
    printf("03. The Ephemeral key has been acquired. \n");
#endif
    // экспорт открытого ключа отправителя в BLOB
    if(!CryptExportKey(hEphemeralKey, 0, PUBLICKEYBLOB, 0, pbSenderPublicKeyBlob, &dwSenderPublicBlobLen)) HandleError("Error during CryptExportKey of Sender\'s public key.");
#ifdef DEBUG_OUT
    printf("04. The Sender public key has been acquired. size of Blob = %d bytes\n", dwSenderPublicBlobLen);
#endif
    // получаем значение открытого ключа отправителя из PUBLICKEYBLOB
    memcpy(&pbKeyCipherValue[PUBKEY_OFFSET], &pbSenderPublicKeyBlob[dwSenderPublicBlobLen-PUBKEY_LEN], PUBKEY_LEN);
#ifdef DEBUG_OUT
    if(!fwrite( pbSenderPublicKeyBlob, 1, dwSenderPublicBlobLen, fhSession_PublicKey)) HandleError( "The session key can not be written to the 'session_PublicKey.bin'\n" );
    printf("\tThe session key was written to the 'session_PublicKey.bin'\n" );
#endif
    // Получение дескриптора закрытого ключа отправителя. нам нужно для получения сертификата который вставим в запрос
    if(!CryptGetUserKey(	hProv, AT_KEYEXCHANGE, &hKey)) HandleError("Error during CryptGetUserKey private key.");
#ifdef DEBUG_OUT
    printf("05. The private key has been acquired. \n");
#endif
    // определяем размер блока памяти для получения нашего сертификата который мы пошлем в ФСС
    if(!CryptGetKeyParam(hKey, KP_CERTIFICATE, NULL, &dwPubCertData, 0)) HandleError("Error during CryptGetKeyParam for determinating size of certificate.");
#ifdef DEBUG_OUT
    printf("06. The size for Public Certificate has been acquired. \n");
#endif
    pbPubCertData = (BYTE*)malloc(dwPubCertData);
    if(!pbPubCertData) HandleError("Out of memory. \n");
 
    // получачем сам сертификата который мы пошлем в ФСС в подготовленную область памяти
    if(!CryptGetKeyParam(hKey, KP_CERTIFICATE, pbPubCertData, &dwPubCertData, 0)) HandleError("Error during CryptGetKeyParam when get certificate");
#ifdef DEBUG_OUT
    printf("07. The our Certificate has been acquired. \n");
#endif
    // Получение ключа согласования импортом открытого ключа получателя на эфемерном ключе,  ----не на закрытом ключе отправителя.
    if(!CryptImportKey(hProv, pbKeyBlob, dwBlobLen, hEphemeralKey, 0, &hAgreeKey)) HandleError("Error during CryptImportKey public key.");
#ifdef DEBUG_OUT
    printf("08. get AgreeKey by importing the responder public key. \n");
#endif
    // Установление ----PRO12_EXPORT (на саомм деле нет, PRO_EXPORT) алгоритма ключа согласования
    if(!CryptSetKeyParam( hAgreeKey, KP_ALGID, (LPBYTE)&ke_alg,  0)) HandleError("Error during CryptSetKeyParam agree key.");
    //printf("22. PRO12_EXPORT agree key algorithm has been set. \n");
#ifdef DEBUG_OUT
    printf("09. PRO_EXPORT agree key algorithm has been set. \n");
#endif
    // Генерация случайного сессионного ключа.
    if(!CryptGenKey( hProv, CALG_G28147, CRYPT_EXPORTABLE, &hSessionKey)) HandleError("ERROR -- CryptGenKey failed for random session key.");
#ifdef DEBUG_OUT
    printf("10. Original session key is created. \n");
#endif
    //--------------------------------------------------------------------
    // Зашифрование сессионного ключа.
    //--------------------------------------------------------------------
 
    // экспорт сессионного ключа в BLOB
 
    // Определение размера BLOBа сессионного ключа и распределение памяти.
    if(!CryptExportKey( hSessionKey, hAgreeKey, SIMPLEBLOB, 0, NULL, &dwBlobLenSimple)) HandleError("Error computing BLOB length.");
#ifdef DEBUG_OUT
    printf("11. Size of the BLOB for the sender session key determined. \n");
#endif
    pbKeyBlobSimple = (BYTE*)malloc(dwBlobLenSimple);
    if(!pbKeyBlobSimple) HandleError("Out of memory. \n");
 
    // Зашифрование сессионного ключа на ключе Agree. экспорт сессионного ключа на ключе согласования
    if(!CryptExportKey( hSessionKey, hAgreeKey, SIMPLEBLOB, 0, pbKeyBlobSimple, &dwBlobLenSimple)) HandleError("Error during CryptExportKey.");
#ifdef DEBUG_OUT
   	printf("12. Contents have been written to the BLOB. \n");
#endif       
    memcpy(&pbKeyCipherValue[SV_OFFSET], ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bSV, SV_LEN);
    memcpy(&pbKeyCipherValue[ENCRKEY_OFFSET], ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptedKey, ENCRKEY_LEN);
    memcpy(&pbKeyCipherValue[MACKEY_OFFSET], ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bMacKey, MACKEY_LEN);
 
    // режим шифрования CBC
    if(!CryptSetKeyParam(hSessionKey, KP_MODE, (LPBYTE)&keyParam, 0)) HandleError("Error during CryptSetKeyParam cipher mode.");
#ifdef DEBUG_OUT
    printf("\tSet crypt param CRYPT_MODE_CBC. \n");
#endif
    // режим паддинга
    if(!CryptSetKeyParam(hSessionKey, KP_PADDING, (LPBYTE)&paddingMode, 0)) HandleError("Error during CryptSetKeyParam padding mode.");
#ifdef DEBUG_OUT
    printf("\tSet crypt param ISO10126_PADDING. \n");
#endif    
//end hacking
 
    // Определение размера вектора инициализации сессионного ключа. 
    if(!CryptGetKeyParam( hSessionKey, KP_IV, NULL, &dwIV, 0)) HandleError("Error computing IV length.");
#ifdef DEBUG_OUT
    printf("13. Size of the IV for the session key determined. \n");
#endif
    pbIV = (BYTE*)malloc(dwIV);
    if (!pbIV) HandleError("Out of memory. \n");
 
    // Получение вектора инициализации сессионного ключа.
    if(!CryptGetKeyParam( hSessionKey, KP_IV, pbIV, &dwIV, 0)) HandleError("Error during CryptGetKeyParam.");
#ifdef DEBUG_OUT
    printf( "14. CryptGetKeyParam succeeded. \n");
 
    //--------------------------------------------------------------------
    // Запись вектора инициализации в файл.
    if(!fwrite( pbIV, 1,	dwIV, fhVector)) HandleError( "The IV can not be written to the 'vector.bin'\n" );
    printf( "\tThe IV was written to the 'vector.bin'\n" );
 
    //--------------------------------------------------------------------
    // Запись сессионного вектора в файл.
    if(!fwrite( ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bSV, 1, SEANCE_VECTOR_LEN, fhSession_SV)) HandleError( "The session vector can not be written to the 'session_SV.bin'\n" );
    printf( "\tThe session vector was written to the 'session_SV.bin'\n" );
 
    //--------------------------------------------------------------------
    // Запись сессионного зашифрованного ключа в файл.
    if(!fwrite( ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptedKey, 1, G28147_KEYLEN, fhSession_EncryptedKey)) HandleError( "The session key can not be written to the 'session_EncryptedKey.bin'\n" );
    printf( "\tThe session key was written to the 'session_EncryptedKey.bin'\n" );
 
    //--------------------------------------------------------------------
    // Запись сессионного MAC ключа в файл.
    if(!fwrite( ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bMacKey, 1, EXPORT_IMIT_SIZE, fhSession_MacKey)) HandleError( "The session key can not be written to the 'session_MacKey.bin'\n" );
    printf( "\tThe session key was written to the 'session_MacKey.bin'\n" );
#endif
 
    //--------------------------------------------------------------------
    // Запись неизменяемой части блоба в файл.
    if (((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptionParamSet[0] != 0x30) HandleError("The EncryptionParam can not be written to the 'EncryptionParam.bin' - first byte is not 0x30\n");
    cbEncryptionParamSetStandart = (DWORD)((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptionParamSet[1] + sizeof((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptionParamSet[0] + sizeof((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptionParamSet[1];
#ifdef DEBUG_OUT
    if (!fwrite(((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptionParamSet, 1, cbEncryptionParamSetStandart, fhEncryptionParam)) HandleError("The EncryptionParam can not be written to the 'EncryptionParam.bin'\n");
    printf("\tThe EncryptionParam was written to the 'EncryptionParam.bin'\n");
 
    //--------------------------------------------------------------------
    // Чтение  файла, и шифрование прочитанного блока и запись его в файл "encrypt.bin". Это будет 
    // 'SOAP-ENV:Envelope -> SOAP-ENV:Body -> EncryptedData ->  CipherData -> CipherValue
    //--------------------------------------------------------------------
    printf( "15. Begin Encryption:\n");
#endif
    // сначала копируем вектор инициализации
    dwCipherValue = dwIV;
    pbCipherValue = (BYTE*)malloc(dwIV);
    if (!pbCipherValue) HandleError("Out of memory. \n");
    memcpy(pbCipherValue,pbIV,dwIV);
 
    bufLen = cbContent;
    if( (DWORD)fread(pbContent, 1, cbContent, fhSource) != cbContent ) HandleError( "Problem reading the source xml file \n" );
 
    // сначала определяем необходимый размер блока памяти
    if(!CryptEncrypt(hSessionKey, 0, TRUE, 0, NULL, &bufLen, 0)) HandleError("get memory size for Encryption failed.");
#ifdef DEBUG_OUT
    printf( "\tget memory size %d bytes for encryption success....\n", bufLen);
#endif    		
    pbContent = (BYTE*)realloc(pbContent, bufLen);
    if (!pbContent) HandleError("Out of memory. \n");
 
    if(!CryptEncrypt(hSessionKey, 0, TRUE, 0, pbContent, &cbContent, bufLen)) HandleError("Encryption failed.");
 
    pbCipherValue = (BYTE*)realloc(pbCipherValue, dwCipherValue+cbContent);
    if (!pbCipherValue) HandleError("Out of memory. \n");
 
    memcpy(&pbCipherValue[dwCipherValue], pbContent, cbContent);
    dwCipherValue += cbContent;
#ifdef DEBUG_OUT
    if(!fwrite( pbContent, 1, cbContent, fhEncrypt)) HandleError( "The encrypted content can not be written \n" );
    printf( "\tencrypted %d bytes, to %d bytes....\n" , cbContent, bufLen);
#endif
    // -------------------------------------------------------
    //  начинаем запись результирующего зашифрованного XML
    // -------------------------------------------------------
    if(!fwrite(xml_1, 1,sizeof(xml_1)-1, fhOutput)) HandleError( "The 1st part of xml can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "-------- writing result file xml ----------\n\tThe 1st part of xml was written to the '%s'\n", argv[4] );
#endif
 
    pbBufferB64 = base64_encode(pbPubCertData, dwPubCertData, &dwBufferB64Len);
    if(!fwrite( pbBufferB64, 1, dwBufferB64Len, fhOutput)) HandleError( "The encrypted data can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe encrypted data was written to the '%s'\n", argv[4] );
#endif
 
    if(!fwrite(xml_2, 1,sizeof(xml_2)-1, fhOutput))HandleError( "The 2nd part of xml can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe 2nd part of xml was written to the '%s'\n", argv[4] );
#endif
 
    pbBufferB64 = base64_encode(pbKeyCipherValue, ASN1_LENGTH, &dwBufferB64Len);
    if(!fwrite( pbBufferB64, 1, dwBufferB64Len, fhOutput)) HandleError( "The session structure can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe session structure was written to the '%s'\n", argv[4] );
#endif
 
    if(!fwrite(xml_3, 1,sizeof(xml_3)-1, fhOutput)) HandleError( "The 3rd part of xml can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe 3rd part of xml was written to the '%s'\n", argv[4] );
#endif
 
    pbBufferB64 = base64_encode(pbCipherValue, dwCipherValue, &dwBufferB64Len);
    if(!fwrite( pbBufferB64, 1, dwBufferB64Len, fhOutput)) HandleError( "The encrypted data can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe encrypted data was written to the '%s'\n", argv[4] );
#endif
 
    if(!fwrite(xml_4, 1,sizeof(xml_4)-1, fhOutput)) HandleError( "The 4th part of xml can not be written to the output xml file\n" );
#ifdef DEBUG_OUT
    printf( "\tThe 4th part of xml was written to the '%s'\n---- writing result file xml complete ------\n", argv[4] );
#endif
 
    CleanUp();
 
#ifdef DEBUG_OUT
    printf("\nThe program ran to completion without error. \n");
#endif
    return 0;
}
 
 
void CleanUp(void)
{
    if(fhSource) fclose (fhSource);
    if(fhOutput) fclose (fhOutput);
    if(fhCert) fclose (fhCert);
#ifdef DEBUG_OUT
    if(fhEncrypt) fclose (fhEncrypt);
    if(fhSession_SV) fclose (fhSession_SV);
    if(fhSession_EncryptedKey) fclose (fhSession_EncryptedKey);
    if(fhSession_PublicKey) fclose (fhSession_PublicKey);
    if(fhSession_MacKey) fclose (fhSession_MacKey);
    if(fhVector) fclose (fhVector);
    if(fhEncryptionParam) fclose(fhEncryptionParam);
#endif
    if(hKey) CryptDestroyKey(hKey);
    if(hEphemeralKey) CryptDestroyKey(hEphemeralKey);
    if(hSessionKey) CryptDestroyKey(hSessionKey);
    if(hAgreeKey) CryptDestroyKey(hAgreeKey);
    if(hProv) CryptReleaseContext(hProv, 0);
    if(pbKeyBlobSimple)	free(pbKeyBlobSimple);
    if(pbIV) free(pbIV);
}
 
BYTE * base64_encode(const BYTE *data, DWORD input_length, DWORD *output_length)
{
    WORD i, j;
    DWORD octet_a, octet_b, octet_c, triple;
 
    *output_length = 4 * ((input_length + 2) / 3);
 
    BYTE * encoded_data = malloc(*output_length);
    if (encoded_data == NULL) return NULL;
 
    for (i = 0, j = 0; i < input_length;)
    {
        octet_a = i < input_length ? (BYTE)data[i++] : 0;
        octet_b = i < input_length ? (BYTE)data[i++] : 0;
        octet_c = i < input_length ? (BYTE)data[i++] : 0;
        triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
        encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
        encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
        encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
        encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
    }
    for (i = 0; i < mod_table[input_length % 3]; i++) encoded_data[*output_length - 1 - i] = '=';
    return encoded_data;
}
 
 
void HandleError(char *s)
{
    DWORD err = GetLastError();
    printf("Error number     : 0x%x\n", err);
    printf("Error description: %s\n", s);
    CleanUp();
    if(!err) err = 1;
    exit(err);
}
 
Download file DecryptFSS.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#   include <windows.h>
#   include <wincrypt.h>
#   include <tchar.h>
#else
#   include <stdlib.h>
#   include <CSP_WinDef.h>
#   include <CSP_WinCrypt.h>
#   include "reader/tchar.h"
#endif
#include <WinCryptEx.h>
 
//#define DEBUG_OUT 1
 
#define BLOCK_LENGTH 4096
 
#define ENCRKEY_OFFSET 7
#define ENCRKEY_LEN 32
 
#define MACKEY_OFFSET 41
#define MACKEY_LEN 4
 
#define PUBKEY_OFFSET 98
#define PUBKEY_LEN 64
 
#define SV_OFFSET 164
#define SV_LEN 8
 
#define IV_LEN 8
#define ENCPARAMSET_LEN 13
 
static BYTE encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                                'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
                                'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
                                'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
                                'w', 'x', 'y', 'z', '0', '1', '2', '3',
                                '4', '5', '6', '7', '8', '9', '+', '/'};
static BYTE *decoding_table = NULL;
static int mod_table[] = {0, 2, 1};
 
void build_decoding_table();
BYTE *base64_decode(const BYTE *data, DWORD input_length, DWORD *output_length);
static void CleanUp(void);
static void HandleError(char *s);
 
static HCRYPTPROV hProv = 0;		            // Дескриптор CSP
static HCRYPTKEY hKey = 0;		                // Дескриптор закрытого ключа 
static HCRYPTKEY hPubKey = 0;		            // Дескриптор открытого ключа ФСС
static HCRYPTKEY hSessionKey = 0;	            // Дескриптор сессионного ключа
static HCRYPTKEY hAgreeKey = 0;                 // Дескриптор ключа согласования
 
static FILE *fhEncrypt=NULL;		            // Зашифрованный файл
static FILE *fhOutput=NULL; 		            // Расшифрованный файл
#ifdef DEBUG_OUT
static FILE *fhCertB64=NULL;    	            // Сертификат ФСС из xml в base64
static FILE *fhCert=NULL;    		            // Сертификат ФСС из xml бинарный
static FILE *fhEncodingParametersB64;           // Параметры шифрования из xml в base64
static FILE *fhEncodingParameters;              // Параметры шифрования из xml в бинарном виде
static FILE *fhEncodedContentB64;               // Содержимое из xml в base64
static FILE *fhEncodedContent;                  // Содержимое из xml которое надо расшифровать в бинарном виде с вектором инициализации
static FILE *fhMacKey;                      
static FILE *fhKeyBlobSimple;
static FILE *fhKeyBlob;
static FILE *fhContent;                         // чистое содержимое которое надо расшифровать без вектора инициализации
#endif
 
char SearchStr1[] = "<ds:X509Data><ds:X509Certificate>";
char SearchStr2[] = "</ds:X509Certificate></ds:X509Data></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>";
char SearchStr3[] = "</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>";
char SearchStr4[] = "</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>";
 
#define MAX_PUBLICKEYBLOB_SIZE 200
 
int main(int argc, char *argv[])
{
    BYTE * pbEncrypt;	                        // Указатель на содержимое зашифрованного файла
    DWORD cbEncrypt = 0;	                    // Длина содержимого
 
    BYTE * pbClrContent;                        // Указатель на содержимое зашифрованного файла, после очистки от спецсимволов и пробелов
    DWORD cbClrContent = 0;                     // Длина содержимого после очистики
 
    BYTE * pbContent;	                        
    DWORD cbContent = 0;	                    
 
    BYTE * pbCert;	                            // Указатель на сертификат ФСС
    DWORD cbCert = 0;	                        // длина блока памяти
 
    BYTE  pbKeyBlob[MAX_PUBLICKEYBLOB_SIZE];	// Указатель на ключевой BLOB 
    DWORD dwBlobLen = MAX_PUBLICKEYBLOB_SIZE;	// Длина ключевого BLOBа
 
    BYTE pPublicKeyBlob[PUBKEY_LEN];
    DWORD cbPublicKeyBlob;
 
    BYTE *pbKeyBlobSimple = NULL;               // Указатель на сессионный ключевой BLOB 
    DWORD cbBlobLenSimple;                  	// Длина сессионного ключевого BLOBа
 
    BYTE pbIV[IV_LEN];		                    // Вектор инициализации сессионного ключа
    DWORD dwIV = IV_LEN;	                    // Длина вектора инициализации
 
    DWORD keyParam = CRYPT_MODE_CBC;    
    DWORD paddingMode = ISO10126_PADDING; //cpm_ISO10126 
 
    ALG_ID ke_alg = CALG_PRO_EXPORT /*CALG_PRO12_EXPORT*/;          // алгоритм ключа согласования
    CRYPT_SIMPLEBLOB_HEADER tSimpleBlobHeaderStandart;
                                                //неменяемая часть блоба
    BYTE pbEncryptionParamSetStandart[ENCPARAMSET_LEN] = {0x30, 0x0B, 6, 9, 0x2A, 0x85, 3, 7, 1, 2, 5, 1, 1 };    // OBJECT IDENTIFIER 1.2.643.7.1.2.5.1.1 tc26CipherZ (TC26 params Z for GOST 28147-89)
 
    DWORD cbEncryptionParamSetStandart = ENCPARAMSET_LEN;                //длина неменяемой части блоба
    PCCERT_CONTEXT pCertContext = NULL;
 
    DWORD dwBytesRead;
    DWORD i, pos_Cert1, pos_Cert2, pos_EncP1, pos_EncP2, pos_Enc1, pos_Enc2;
 
    BYTE * pbBufferB64;
    DWORD cbBufferB64;
 
    tSimpleBlobHeaderStandart.BlobHeader.aiKeyAlg = CALG_G28147; 
    tSimpleBlobHeaderStandart.BlobHeader.bType = SIMPLEBLOB;
    tSimpleBlobHeaderStandart.BlobHeader.bVersion = BLOB_VERSION;
    tSimpleBlobHeaderStandart.BlobHeader.reserved = 0;
    tSimpleBlobHeaderStandart.EncryptKeyAlgId = CALG_G28147;
    tSimpleBlobHeaderStandart.Magic = G28147_MAGIC;    
 
    if(argc < 4)
    {
        printf("Decryption tool for FSS ELN service.\nver 0.01, IbZ(c) Studio (tm)  :)\n" );
        printf("\n\tusing: %s <container_name> <encrypted_xml> <output.xml>\n\n", argv[0]);
        HandleError( "Not enough input parameters given" );
    } 
 
    // Открытие файла, в котором хранится зашифрованный ответ FSS.
    if(!(fhEncrypt = fopen(argv[2], "r+b" ))) HandleError( "Problem opening the file with encrypted xml\n" );
#ifdef DEBUG_OUT
    printf( "01. The file '%s' was opened\n", argv[2] );
#endif
 
    fseek(fhEncrypt, 0, SEEK_END);
    cbEncrypt = sizeof(char)*ftell(fhEncrypt);
    rewind(fhEncrypt);
    // Выделяем память для чтения файла целиком и читаем его в эту область
    pbEncrypt = (BYTE*)malloc(cbEncrypt);
    if (pbEncrypt == NULL) HandleError("Out of memory. \n");
    dwBytesRead = (DWORD)fread(pbEncrypt, 1, cbEncrypt, fhEncrypt);
 
    if (dwBytesRead != cbEncrypt) HandleError("The Encrypted file can not be reading from the 'EncryptionParam.bin'\n");
#ifdef DEBUG_OUT
    printf( "02. The file '%s' was readed, size = %d butes\n", argv[2], dwBytesRead );
#endif
 
    // Убираем разрывы строк, пробелы и другие спецсимволы - поля же файле е закодированы в base64, чтобы потом легчо было пропарсить
    pbClrContent = (BYTE*)malloc(cbEncrypt);
    if (pbClrContent == NULL) HandleError("Out of memory. \n");
    for(i=0; i<strlen(pbEncrypt); i++)
    {
//     if( ! ( (pbEncrypt[i] = 13) or (pbEncrypt[i] = 10) or (pbEncrypt[i] = 32) pbEncrypt[i] = 9) )
       if((pbEncrypt[i] >32)&&(pbEncrypt[i]<127)) pbClrContent[cbClrContent++] = pbEncrypt[i];
    }
#ifdef DEBUG_OUT
    printf( "\tClear size = %d bytes\n", cbClrContent );
#endif
 
    // Находим позиции сертификата FSS
    pbContent = (char *)strstr(pbClrContent, SearchStr1);
    if ( pbContent == NULL) HandleError("Not found Encoding parameters structure\n");
    pos_Cert1 = pbContent-pbClrContent+1+strlen(SearchStr1);
 
    pbContent = (char *)strstr(pbClrContent, SearchStr2);
    if ( pbContent == NULL) HandleError("Not found End of Certificate\n");
    pos_Cert2 = pbContent-pbClrContent+1;
#ifdef DEBUG_OUT
    printf ("03. Found Certificate at positions  %d - %d\n",pos_Cert1, pos_Cert2);
#endif
 
    // Копируем сертификат в pbCert
    pbCert = base64_decode(&pbClrContent[pos_Cert1-1], pos_Cert2-pos_Cert1, &cbCert);
 
 
#ifdef DEBUG_OUT
    // Для отладки пишем  сертификат в файл.
    if(!(fhCertB64 = fopen("certificate.txt", "w+b" ))) HandleError( "Problem opening the file 'certificate.txt'\n" );
    printf( "\tThe file 'certificate.txt' was opened\n" );
    if(!fwrite( &pbClrContent[pos_Cert1-1], 1, pos_Cert2-pos_Cert1, fhCertB64)) HandleError( "The  content can not be written to the 'certificate.txt'\n" );
    printf( "\tThe  content was written to the 'certificate.txt'\n" );
 
    if(!(fhCert = fopen("certificate.bin", "w+b" ))) HandleError( "Problem opening the file 'certificate.bin'\n" );
    printf( "\tThe file 'certificate.bin' was opened\n" );
    if(!fwrite( pbCert, 1, cbCert, fhCert)) HandleError( "The  content can not be written to the 'certificate.bin'\n" );
    printf( "\tThe  content was written to the 'certificate.bin'\n" );
#endif
 
    // Находим позиции параметров шифрования
    pbContent = (char *)strstr(pbClrContent, SearchStr2);
    if ( pbContent == NULL) HandleError("Not found Encoding parameters\n");
    pos_EncP1 = pbContent-pbClrContent+1+strlen(SearchStr2);
 
    pbContent = (char *)strstr(pbClrContent, SearchStr3);
    if ( pbContent == NULL) HandleError("Not found End of Encoding parameters structure\n");
    pos_EncP2 = pbContent-pbClrContent+1;
 
#ifdef DEBUG_OUT
    printf ("04. Found Encoding parameters at positions %d - %d\n",pos_EncP1, pos_EncP2);
#endif
    pbBufferB64 = base64_decode(&pbClrContent[pos_EncP1-1], pos_EncP2-pos_EncP1, &cbBufferB64);
 
    //-----------------------------------------------
    // Готовим блоб структуру
    //-----------------------------------------------
    cbBlobLenSimple = cbEncryptionParamSetStandart + sizeof(CRYPT_SIMPLEBLOB_HEADER) + SEANCE_VECTOR_LEN + G28147_KEYLEN + EXPORT_IMIT_SIZE;
    pbKeyBlobSimple = malloc(cbBlobLenSimple);
    if(!pbKeyBlobSimple) HandleError("Out of memory. \n");
 
    //копируем неменяемый хедер в блобе
    memcpy(&((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->tSimpleBlobHeader, &tSimpleBlobHeaderStandart, sizeof(CRYPT_SIMPLEBLOB_HEADER));
 
    //копируем неменяемую ASN структуру
    memcpy(&((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptionParamSet, pbEncryptionParamSetStandart, cbEncryptionParamSetStandart); 
 
    // копируем session_SV
    memcpy(&((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bSV, &pbBufferB64[SV_OFFSET], SEANCE_VECTOR_LEN);
 
    // копируем session_EncryptedKey
    memcpy(&((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bEncryptedKey, &pbBufferB64[ENCRKEY_OFFSET], G28147_KEYLEN);
 
    // копируем session_MacKey
    memcpy(&((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bMacKey, &pbBufferB64[MACKEY_OFFSET], EXPORT_IMIT_SIZE);
 
#ifdef DEBUG_OUT
    if(!(fhMacKey = fopen("bMacKey.txt", "w+b" ))) HandleError( "Problem opening the file 'bMacKey.txt'\n" );
    printf( "\tThe file 'bMacKey.txt' was opened\n" );
    if(!fwrite( ((CRYPT_SIMPLEBLOB*)pbKeyBlobSimple)->bMacKey, 1, EXPORT_IMIT_SIZE, fhMacKey)) HandleError( "The  content can not be written to the 'bMacKey.txt'\n" );
    printf( "\tThe content was written to the 'bMacKey.txt'\n");
 
    printf( "\t\t%d bytes of tSimpleBlobHeader \n"
            "\t\t%d bytes of bEncryptionParamSet\n"
            "\t\t%d bytes of bSV\n"
            "\t\t%d bytes of bEncryptedKey\n"
            "\t\t%d bytes of bMacKey\n"
            "\t\t%d bytes of total copied\n",  sizeof(CRYPT_SIMPLEBLOB_HEADER),cbEncryptionParamSetStandart,SEANCE_VECTOR_LEN,G28147_KEYLEN,EXPORT_IMIT_SIZE, cbBlobLenSimple );
 
    if(!(fhKeyBlobSimple = fopen("pbKeyBlobSimple.txt", "w+b" ))) HandleError( "Problem opening the file 'pbKeyBlobSimple.txt'\n" );
    printf( "\tThe file 'pbKeyBlobSimple.txt' was opened\n" );
    if(!fwrite( pbKeyBlobSimple, 1, cbBlobLenSimple, fhKeyBlobSimple)) HandleError( "The  content can not be written to the 'pbKeyBlobSimple.txt'\n" );
    printf( "\tThe content was written to the 'pbKeyBlobSimple.txt'\n" );
#endif
 
    memcpy(&pPublicKeyBlob, &pbBufferB64[PUBKEY_OFFSET], PUBKEY_LEN);
 
#ifdef DEBUG_OUT
    // для отладки пишем в файл.
    if(!(fhKeyBlob = fopen("pbKeyBlob.txt", "w+b" ))) HandleError( "Problem opening the file 'pbKeyBlob.txt'\n" );
    printf( "\tThe file 'pbKeyBlob.txt' was opened\n" );
    if(!fwrite( pPublicKeyBlob, 1, PUBKEY_LEN, fhKeyBlob)) HandleError( "The  content can not be written to the 'pbKeyBlob.txt'\n" );
    printf( "\tThe content was written to the 'pbKeyBlob.txt'\n");
 
    if(!(fhEncodingParametersB64 = fopen("encoding_parameters.txt", "w+b" ))) HandleError( "Problem opening the file 'encoding_parameters.txt'\n" );
    printf( "\tThe file 'encoding_parameters.txt' was opened\n" );
    if(!fwrite( &pbClrContent[pos_EncP1-1], 1, pos_EncP2-pos_EncP1, fhEncodingParametersB64)) HandleError( "The  content can not be written to the 'encoding_parameters.txt'\n" );
    printf( "\tThe content was written to the 'encoding_parameters.txt'\n" );
 
    if(!(fhEncodingParameters = fopen("encoding_parameters.bin", "w+b" ))) HandleError( "Problem opening the file 'encoding_parameters.bin'\n" );
    printf( "\tThe file 'encoding_parameters.bin' was opened\n" );
    if(!fwrite( pbBufferB64, 1, cbBufferB64, fhEncodingParameters)) HandleError( "The  content can not be written to the 'encoding_parameters.bin'\n" );
    printf( "\tThe content was written to the 'encoding_parameters.bin'\n" );
#endif
 
    // Находим основное зашифрованое содержимое
    pbContent = (char *)strstr(pbClrContent, SearchStr3);
    if ( pbContent == NULL ) HandleError("Not found Encoded content\n");
    pos_Enc1 = pbContent-pbClrContent+1+strlen(SearchStr3);
 
    pbContent = (char *)strstr(pbClrContent, SearchStr4);
    if ( pbContent == NULL ) HandleError("Not found End of encoded content\n");
    pos_Enc2 = pbContent-pbClrContent+1;
#ifdef DEBUG_OUT
    printf ("05. Found Encoded content at positions %d - %d\n",pos_Enc1, pos_Enc2);
#endif
 
    // В pbBufferB64 - первые 8 байт будут вектор инициализации, затем само зашифрованное содержимое
    pbBufferB64 = base64_decode(&pbClrContent[pos_Enc1-1], pos_Enc2-pos_Enc1, &cbBufferB64);
 
#ifdef DEBUG_OUT
    // для отладки пишем в файл.
    if(!(fhEncodedContentB64 = fopen("encoded_content.txt", "w+b" ))) HandleError( "Problem opening the file 'encoded_content.txt'\n" );
    printf( "\tThe file 'encoded_content.txt' was opened\n" );
    if(!fwrite( &pbClrContent[pos_Enc1-1], 1, pos_Enc2-pos_Enc1, fhEncodedContentB64)) HandleError( "The  content can not be written to the 'encoded_content.txt'\n" );
    printf( "\tThe content was written to the 'encoded_content.txt'\n" );
 
    if(!(fhEncodedContent = fopen("encoded_content.bin", "w+b" ))) HandleError( "Problem opening the file 'encoded_content.bin'\n" );
    printf( "\tThe file 'encoded_content.bin' was opened\n" );
    if(!fwrite( pbBufferB64, 1, cbBufferB64, fhEncodedContent)) HandleError( "The  content can not be written to the 'encoded_content.bin'\n" );
    printf( "\tThe content was written to the 'encoded_content.bin'\n" );
#endif
 
    // Чтение  вектора инициализации из файла.
    memcpy(pbIV, pbBufferB64, dwIV); 
    pbContent = &pbBufferB64[dwIV];
    cbContent = cbBufferB64-dwIV;
 
    // ------------------------------------------------------------------
    // Закончили чтение файла и подготовку структур - начинаем дешифровку
    // ------------------------------------------------------------------
 
    if(!CryptAcquireContext( &hProv, argv[1], NULL, PROV_GOST_2012_256, 0)) HandleError("Error during CryptAcquireContext.");
#ifdef DEBUG_OUT
    printf("06. The key container \"%s\" has been acquired. \n", argv[1]);
#endif
 
    // Загрузка PUBLICKEYBLOB из сертификата, открытие файла, в котором содержится открытый ключ получателя.
    pCertContext = CertCreateCertificateContext ( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pbCert, cbCert);
    if (!pCertContext) HandleError( "CertCreateCertificateContext" );
#ifdef DEBUG_OUT
    printf("07. Certificate context created\n");
#endif
 
	// Импортируем открытый ключ
    if (!CryptImportPublicKeyInfoEx( hProv, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &(pCertContext->pCertInfo->SubjectPublicKeyInfo), 0, 0, NULL, &hPubKey)) HandleError( "CryptImportPublicKeyInfoEx" );
#ifdef DEBUG_OUT
    printf("08. Public key imported from cert file\n");
#endif
 
    // экспорт открытого ключа получателя в BLOB
    if (!CryptExportKey( hPubKey, 0, PUBLICKEYBLOB, 0, pbKeyBlob, &dwBlobLen)) HandleError( "CryptExportKey" );
#ifdef DEBUG_OUT
    printf("09. Public key exported to blob\n");
#endif
 
    memcpy(&pbKeyBlob[dwBlobLen-PUBKEY_LEN], pPublicKeyBlob, PUBKEY_LEN);
 
    // получение закрытого ключа
    if(!CryptGetUserKey( hProv, AT_KEYEXCHANGE, &hKey))  HandleError("Error during CryptGetUserKey private key.");
#ifdef DEBUG_OUT
   	printf("10. The private key has been acquired. \n");
#endif
 
    //получение ключа согласования импортом открытого ключа отправителя на закрытом ключе
	if(!CryptImportKey(hProv, pbKeyBlob, dwBlobLen, hKey, 0, &hAgreeKey)) HandleError("Error during CryptImportKey public key.");
#ifdef DEBUG_OUT
    printf("11. The sender public key has been imported. \n");
#endif
 
    // установление PRO_EXPORT  алгоритма ключа согласования ---не PRO12_EXPORT !
    if(!CryptSetKeyParam( hAgreeKey, KP_ALGID, (LPBYTE)&ke_alg, 0)) HandleError("Error during CryptSetKeyParam agree key.");
#ifdef DEBUG_OUT
    printf("12. PRO_EXPORT agree key algorithm has been set. \n");
#endif
 
    // Получение сессионного ключа импортом зашифрованного сессионного ключа  на ключе согласования Agree.
    if(!CryptImportKey( hProv, pbKeyBlobSimple, cbBlobLenSimple, hAgreeKey, 0, &hSessionKey)) HandleError("Error during CryptImportKey session key.");
#ifdef DEBUG_OUT
    printf("13. The session key has been imported. \n");
#endif
 
    // Установка вектора инициализации - без него первые 8 байт расшифруются неправильно.
    if(!CryptSetKeyParam( hSessionKey, KP_IV, pbIV, 0))	HandleError("Error during CryptSetKeyParam.");
#ifdef DEBUG_OUT
    printf("14. CryptSetKeyParam setting IV succeeded. \n");
#endif
 
    // установка режима шифрования CBC
    if(!CryptSetKeyParam( hSessionKey, KP_MODE, (LPBYTE)&keyParam, 0))	HandleError("Error during CryptSetKeyParam.");
#ifdef DEBUG_OUT
    printf("15. CryptSetKeyParam setting CBC mode succeeded. \n");
#endif
 
    // установка режима паддинга
    if(!CryptSetKeyParam( hSessionKey, KP_PADDING, (LPBYTE)&paddingMode, 0)) HandleError("Error during CryptSetKeyParam.");
#ifdef DEBUG_OUT
    printf("16. CryptSetKeyParam setting ISO10126 padding succeeded. \n");
#endif
 
 
#ifdef DEBUG_OUT
    if(!(fhContent = fopen("content.bin", "w+b" ))) HandleError( "Problem opening the file 'content.bin'\n" );
    printf( "\tThe file 'content.bin' was opened\n" );
    if(!fwrite( pbContent, 1, cbContent, fhContent)) HandleError( "The  content can not be written to the 'content.bin'\n" );
    printf( "\tThe content was written to the 'content.bin', size = %d\n", cbContent );
#endif
 
    // Дешифрование зашифрованного базового SOAP-запроса, после которого в
    // decryptedData будет содержать дешифрованные данные,
    // а decryptedDataLen - длину расшифрованных данных
 
    if(!CryptDecrypt( hSessionKey, 0, TRUE, 0, pbContent, &cbContent)) HandleError("Decryption failed.");
#ifdef DEBUG_OUT
    printf("17. Decryption succeeded. \n");
#endif
 
    // Запись расшифрованного блока в файл.
    if(!(fhOutput = fopen(argv[3], "w+b" ))) HandleError( "Problem opening the file for encoded xml\n" );
#ifdef DEBUG_OUT
    printf( "\tThe file '%s' was opened\n", argv[3] );
#endif
    if(!fwrite( pbContent, 1, cbContent, fhOutput)) HandleError( "The decrypted content can not be written '\n" );
#ifdef DEBUG_OUT
    printf( "18. The decrypted content was written to the '%s'\n", argv[3] );
#endif
 
    CleanUp();
 
#ifdef DEBUG_OUT
    printf("The program ran to completion without error. \n");
#endif
 
    return 0;
}
 
 
 
void CleanUp(void)
{
    if(fhEncrypt) fclose (fhEncrypt);
    if(fhOutput) fclose (fhOutput);
#ifdef DEBUG_OUT
    if(fhCert) fclose (fhCert);
    if(fhCertB64) fclose (fhCertB64);
    if(fhMacKey) fclose (fhMacKey);
    if(fhKeyBlobSimple) fclose (fhKeyBlobSimple);
    if(fhKeyBlob) fclose (fhKeyBlob);
    if(fhEncodingParametersB64) fclose (fhEncodingParametersB64);
    if(fhEncodingParameters) fclose (fhEncodingParameters);
    if(fhEncodedContentB64) fclose (fhEncodedContentB64);
    if(fhEncodedContent) fclose (fhEncodedContent);
    if(fhContent) fclose (fhContent);
#endif
    if(hKey) CryptDestroyKey(hKey);                 // Уничтожение дескриптора закрытого ключа.
    if(hSessionKey) CryptDestroyKey(hSessionKey);   // Уничтожение дескриптора сессионного ключа.
    if(hAgreeKey) CryptDestroyKey(hAgreeKey);       // Уничтожение дескриптора ключа согласования.
    if(hProv) CryptReleaseContext(hProv, 0);        // Освобождение дескриптора провайдера.
}
 
BYTE *base64_decode(const BYTE *data, DWORD input_length, DWORD *output_length)
{
    int i, j;
    DWORD sextet_a, sextet_b, sextet_c, sextet_d, triple;
 
    if (decoding_table == NULL) build_decoding_table();
 
    if (input_length % 4 != 0) return NULL;
 
    *output_length = input_length / 4 * 3;
    if (data[input_length - 1] == '=') (*output_length)--;
    if (data[input_length - 2] == '=') (*output_length)--;
 
    BYTE *decoded_data = malloc(*output_length);
    if (decoded_data == NULL) return NULL;
 
    for (i = 0, j = 0; i < input_length;) {
 
        sextet_a = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
        sextet_b = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
        sextet_c = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
        sextet_d = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
 
        triple = (sextet_a << 3 * 6)
                + (sextet_b << 2 * 6)
                + (sextet_c << 1 * 6)
                + (sextet_d << 0 * 6);
 
        if (j < *output_length) decoded_data[j++] = (triple >> 2 * 8) & 0xFF;
        if (j < *output_length) decoded_data[j++] = (triple >> 1 * 8) & 0xFF;
        if (j < *output_length) decoded_data[j++] = (triple >> 0 * 8) & 0xFF;
    }
    return decoded_data;
}
 
void build_decoding_table()
{
    int i;
    decoding_table = malloc(256);
    for (i = 0; i < 64; i++) decoding_table[(BYTE) encoding_table[i]] = i;
}
 
//--------------------------------------------------------------------
//  В этом примере используется функция HandleError, функция обработки
//  простых ошибок, для печати сообщения и выхода из программы. 
//  В большинстве приложений эта функция заменяется другой функцией, 
//  которая выводит более полное сообщение об ошибке.
 
void HandleError(char *s)
{
    DWORD err = GetLastError();
    printf("Error number     : 0x%x\n", err);
    printf("Error description: %s\n", s);
    CleanUp();
    if(!err) err = 1;
    exit(err);
}