#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>

// Начало примера (не следует удалять данный комментарий, он используется 
// для автоматической сборки документации)
//--------------------------------------------------------------------
// В данном примере реализовано шифрование закрытого ключа AT_KEYEXCHANGE 
// на открытом ключе получателя.
//
// Следует сделить за тем, чтобы контейнеры "Sender" и "Responder", 
// используемые в программах ExportInFile, EncryptKey и DecryptKey на момент
// выполнения этих программ существовали.
//--------------------------------------------------------------------



#pragma region vars

#define BLOCK_LENGTH 4096
#define SALT_LENGTH 16

static void WriteBlobToFile(BYTE *pbData, DWORD cbData,char *FName);//!!!!!!!!!!!!!!!
static void HandleError(const char *s);
static void CleanUp(void);


static HCRYPTPROV hProv = 0;		// Дескриптор CSP 
static HCRYPTKEY hKey = 0;		// Дескриптор копируемого закрытого Ключа отправителя AT_EXCHANGE
static HCRYPTKEY hDerivedKey = 0;	// Дескриптор сессионного ключа
static HCRYPTHASH hHash = 0;		// Дескриптор хэша пароля

static BYTE *pbKeyBlobExport = NULL;    // Указатель на  BLOB копирумого закрытого Ключа отправителя AT_EXCHANGE
static BYTE *pbIV = NULL;		// Вектор инициализации закрытого Ключа

static CRYPT_DATA_BLOB salt = {0, NULL}; // Структура CRYPT_DATA_BLOB для хранения соли


#pragma endregion

#define MAX_PUBLICKEYBLOB_SIZE 200

int main(void)
{
    DWORD dwKeyBlobExportLen;		// Длина  BLOBа передаваемого ключа

    DWORD dwIV = 0;			// Длина вектора инициализации
    ALG_ID ke_alg = CALG_PRO12_EXPORT;	// Алгоритм сессионного ключа

    char password[] = "password";   // Пароль
    CRYPT_DATA_BLOB password_blob;  // Структура CRYPT_DATA_BLOB для хранения пароля
    password_blob.cbData = (DWORD)strlen(password);
    password_blob.pbData = (BYTE*)password;

    
    // Получение дескриптора контейнера получателя с именем "Sender" 
    if(CryptAcquireContext(
	&hProv, 
	_TEXT("Sender"),
	NULL, 
	PROV_GOST_2012_256,
	0)) 
    {
	printf("The key container \"Sender\" has been acquired. \n");
    }
    else
    {
	HandleError("Error during CryptAcquireContext.");
    }

    // Заполнение соли случайными байтами.
    salt.cbData = SALT_LENGTH;
    salt.pbData = (BYTE*)malloc(salt.cbData);
    if (!salt.pbData)
	HandleError("Out of memory.");
    CryptGenRandom(hProv, salt.cbData, salt.pbData);

    // Запись соли в файл.
    WriteBlobToFile(salt.pbData, salt.cbData, "salt.bin");

    //----------------------------------------------------------------------------
    // Хэширование пароля.
    //----------------------------------------------------------------------------

    // Создание пустого объекта хэширования.
    if (!CryptCreateHash(
	hProv,
	CALG_PBKDF2_2012_512,
	0,
	0,
	&hHash))
    {
	HandleError("CryptCreateHash failed.");
    }

    // Задание соли.
    if (!CryptSetHashParam(
	hHash,
	HP_PBKDF2_SALT,
	(BYTE*)&salt,
	0))
    {
	HandleError("CryptSetHashParam HP_PBKDF2_SALT failed.");
    }

    // Задание строки пароля.
    if (!CryptSetHashParam(
	hHash,
	HP_PBKDF2_PASSWORD,
	(BYTE*)&password_blob,
	0))
    {
	HandleError("CryptSetHashParam HP_PBKDF2_PASSWORD failed.");
    }

    //--------------------------------------------------------------------
    // Создание сессионного ключа, основанного на хэше, полученном из пароля.

    if(CryptDeriveKey(
	hProv,
	CALG_G28147,
	hHash,
	CRYPT_EXPORTABLE,
	&hDerivedKey))
    {
	printf("The key has been derived. \n");
    }
    else
    {
	HandleError("Error during CryptDeriveKey!");
    }

    // Установление PRO12_EXPORT алгоритма сессионного ключа
    if(CryptSetKeyParam(
        hDerivedKey,
        KP_ALGID,
        (LPBYTE)&ke_alg,
        0))
    {
	printf("PRO12_EXPORT agree key algorithm has been set. \n");
    }
    else
    {
	HandleError("Error during CryptSetKeyParam agree key.");
    }
    
    
    //--------------------------------------------------------------------
    // Зашифрование закрытого Ключа.
    //--------------------------------------------------------------------
    // Получение дескриптора закрытого Ключа, который будет скопирован.
    if(CryptGetUserKey( 
	hProv, 
	AT_KEYEXCHANGE, 
	&hKey)) 
    {
	printf("The sender private key has been acquired. \n");
    }
    else
    {
	HandleError("Error during CryptGetUserKey private key.");
    }

    //--------------------------------------------------------------------
    // Определение размера вектора инициализации Ключа. 
    if(CryptGetKeyParam(
	hDerivedKey, 
	KP_SV, 
	NULL, 
	&dwIV, 
	0))
    {
	printf("Size of the IV for the sender secret Key determined. \n");
    }
    else
    {
	HandleError("Error computing IV length.");

    }

    pbIV = (BYTE*)malloc(dwIV);
    if (!pbIV)
	HandleError("Out of memory. \n");

    // Определение вектора инициализации Ключа.
    if(CryptGetKeyParam(
	hDerivedKey, 
	KP_IV, 
	pbIV, 
	&dwIV, 
	0))
    {
	printf( "CryptGetKeyParam succeeded. \n");
    }
    else
    {
	HandleError("Error during CryptGetKeyParam.");
    }
    
    // Запись вектора инициализации в файл.
    WriteBlobToFile(pbIV, dwIV, "vector.bin"); 

    //--------------------------------------------------------------------
    // Определение размера BLOBа Ключа и распределение памяти.   
    
    if(CryptExportKey(
	hKey, 
	hDerivedKey, 
	PRIVATEKEYBLOB, 
	0, 
	NULL,
	&dwKeyBlobExportLen)) 
    {
	printf("Size of the BLOB for the sender secret Key determined. \n");
    }
    else
    {
	HandleError("Error computing BLOB length.");
    }

    pbKeyBlobExport = (BYTE*)malloc(dwKeyBlobExportLen);

    if(!pbKeyBlobExport) 
	HandleError("Out of memory. \n");

    // Зашифрование Ключа на ключе Derived.

    if(CryptExportKey(
	hKey, 
	hDerivedKey, 
	PRIVATEKEYBLOB,
	0, 
	pbKeyBlobExport, 
	&dwKeyBlobExportLen))
    {
	printf("Contents have been written to the BLOB. \n");
    }
    else
    {
	HandleError("Error during CryptExportKey.");
    }

    // запись блоба зашифрованного ключа
    WriteBlobToFile(pbKeyBlobExport, dwKeyBlobExportLen, "EncryptedKeyBlob.enc");    
    return 0;
}


void WriteBlobToFile(BYTE *pbData, DWORD cbData, char *FName )
{
    FILE *file;

    // Открытие файла на запись в него BLOB-а
    if((file = fopen(FName, "wb")))
    {
	printf("The file '%s' was opened\n", FName);
    }
    else
    {
	fclose(file);
	HandleError("Problem opening the file\n");
    }

    // Запись BLOB-а в файл
    if(fwrite(pbData, 1, cbData, file))
    {
	printf("The blob was written to the '%s'\n", FName);
    }
    else
    {
	fclose(file);
	HandleError("The blob can not be written to file.");
    }

    fclose(file);
}


void CleanUp(void)
{
    // Уничтожение дескриптора копируемого закрытого ключа.
    if(hKey)
	CryptDestroyKey(hKey);

    // Уничтожение дескриптора сессионного ключа.
    if(hDerivedKey)
	CryptDestroyKey(hDerivedKey);

    // Уничтожение дескриптора хэша.
    if(hHash)
	CryptDestroyHash(hHash);

    // Освобождение дескриптора провайдера.
    if(hProv) 
	CryptReleaseContext(hProv, 0);

    free(pbKeyBlobExport);
    free(pbIV);

    free(salt.pbData);
}


// Конец примера 
// (не следует удалять данный комментарий, он используется 
//  для автоматической сборки документации)

//--------------------------------------------------------------------
//  В этом примере используется функция HandleError, функция обработки
//  простых ошибок, для печати сообщения и выхода из программы. 
//  В большинстве приложений эта функция заменяется другой функцией, 
//  которая выводит более полное сообщение об ошибке.


void HandleError(const 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);
}
