Page tree

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 57 Next »


Введение

Функциональность устройств Рутокен охватывает широкий спектр задач обеспечения информационной безопасности. Устройства Рутокен могут быть применены для строгой двухфакторной аутентификации, защиты электронной переписки, установления защищенных соединений, безопасного проведения финансовых транзакций и криптографической защиты информации.

Рутокен S

Рутокен S – это носитель ключевой информации и устройство для авторизации в компьютерных системах и защиты персональных данных с реализацией отечественного стандарта шифрования "на борту".

Через интерфейс PKCS#11 доступны следующие возможности Рутокен S:

  • создание, запись, чтение, изменение, удаление двоичных файлов,
  • генерация и импорт ключей шифрования ГОСТ 28147-89, 
  • шифрование данных по алгоритму ГОСТ 28147-89 в режимах простой замены, гаммирования и гаммирования с обратной связью, 
  • вычисление имитовставки по алгоритму ГОСТ 28147-89 длиной 32 бит, 
  • генерация последовательности случайных чисел длиной 256 бит.

Ограничения Rutoken S

  • Рутокен S для работы требует установки драйверов: для Windows, для Linux, для macOS.
  • Рутокен S не поддерживается библиотекой rtpkcs11ecp.
  • Рутокен S в ОС Windows поддерживается устаревшей библиотекой rtPKCS11.dll и очень ограниченно поддерживается библиотекой opensc-pkcs11 из состава OpenSC.

Рутокен Lite

Рутокен Lite – это ключевой носитель для безопасного хранения ключей шифрования и электронной подписи, паролей и других данных во встроенной защищенной памяти устройства. 

Через интерфейс PKCS#11 доступны следующие возможности Рутокен Lite:

  • создание, запись, чтение, изменение, удаление двоичных файлов.

Рутокен ЭЦП 2.0

Рутокен ЭЦП 2.0 – электронный идентификатор с аппаратной реализацией отечественных и зарубежных стандартов электронной подписи, шифрования и хеширования.

Через интерфейс PKCS#11 доступны следующие возможности Рутокен ЭЦП 2.0:

  • алгоритмы ГОСТ Р 34.10-2012 и ГОСТ Р 34.10-2001: генерация ключевых пар с проверкой качества, импорт ключевых пар, формирование и проверка электронной подписи,
  • алгоритмы ГОСТ Р 34.11-2012 и ГОСТ Р 34.11-94: вычисление значения хеш-функции данных, в том числе с возможностью последующего формирования электронной подписи внутри устройства,
  • алгоритмы ГОСТ 28147-89: генерация и импорт ключей шифрования, шифрование данных в режимах простой замены, гаммирования и гаммирования с обратной связью, вычисление и проверка криптографической контрольной суммы данных (имитовставки).
  • выработка сессионных ключей (ключей парной связи): по схемам VKO GOST R 34.10-2012 (RFC 7836), VKO GOST R 34.10-2001 (RFC 4357), расшифрование по схеме EC El-Gamal.
  • алгоритм RSA: поддержка ключей размером до 2048 бит, генерация ключевых пар с настраиваемой проверкой качества, импорт ключевых пар, формирование электронной подписи.
  • генерация последовательности случайных чисел требуемой длины.

Рутокен ЭЦП 3.0

Рутокен ЭЦП 3.0 – новый электронный идентификатор с аппаратной реализацией отечественных и зарубежных стандартов электронной подписи, шифрования и хеширования.

Через интерфейс PKCS#11 доступны следующие возможности Рутокен ЭЦП 3.0:

  • алгоритмы ГОСТ Р 34.10-2012 и ГОСТ Р 34.10-2001: генерация ключевых пар с проверкой качества, импорт ключевых пар, формирование и проверка электронной подписи,
  • алгоритмы ГОСТ Р 34.11-2012 и ГОСТ Р 34.11-94: вычисление значения хеш-функции данных, в том числе с возможностью последующего формирования электронной подписи внутри устройства,
  • алгоритмы ГОСТ 28147-89 и ГОСТ Р 34.12-2015: генерация и импорт ключей шифрования, шифрование данных в режимах простой замены, гаммирования и гаммирования с обратной связью, вычисление и проверка криптографической контрольной суммы данных (имитовставки).
  • выработка сессионных ключей (ключей парной связи): по схемам VKO GOST R 34.10-2012 (RFC 7836), VKO GOST R 34.10-2001 (RFC 4357), расшифрование по схеме EC El-Gamal.
  • алгоритм RSA: поддержка ключей размером до 4096 бит, генерация ключевых пар с настраиваемой проверкой качества, импорт ключевых пар, формирование электронной подписи.
  • алгоритм ECDSA с кривыми secp256k1 и secp256r1: генерация ключевых пар с настраиваемой проверкой качества, импорт ключевых пар, формирование электронной подписи.
  • генерация последовательности случайных чисел требуемой длины.

Начало работы

Подключение библиотеки

Для работы с устройствами Рутокен через программный интерфейс PKCS#11 приложение должно предварительно загрузить библиотеку, содержащую реализацию функций и механизмов стандарта PKCS#11.

Рутокен SDK предоставляет две библиотеки rtPKCS11 и rtPKCS11ECP, подробнее об особенностях выбора и использования которых можно ознакомиться в разделе Использование библиотек rtPKCS11 и rtPKCS11ECP. Основная разница заключается в том, что российские алгоритмы доступны в библиотеке rtPKCS11ECP, а зарубежные – в rtPKCS11.

Кроме функций стандартного интерфейса PKCS#11 библиотеки экспортируют функции расширения, которые могут быть удобны при использовании специфической функциональности устройств Рутокен.

После загрузки библиотеки нужно получить адрес экспортируемой библиотекой функции C_GetFunctionList() и вызвать ее для получения списка функций PKCS#11. Теперь все готово для работы с библиотекой.

Для работы с функциями расширения необходимо получить адрес функции CK_C_EX_GetFunctionListExtended() и вызвать ее для получения списка функций расширения PKCS#11.

После работы с библиотекой ее нужно выгрузить из памяти.

Загрузка библиотеки и списка функций PKCS#11
/* Имя библиотеки PKCS#11 */
#ifdef _WIN32
/* Библиотека для Рутокен S, Рутокен Lite и Рутокен ЭЦП, поддерживает только алгоритмы RSA */
	#define PKCS11_LIBRARY_NAME         "rtPKCS11.dll" 
/* Библиотека для Рутокен Lite, Рутокен ЭЦП поддерживает алгоритмы ГОСТ и RSA */
	#define PKCS11ECP_LIBRARY_NAME      "rtPKCS11ECP.dll"
#endif 
#ifdef __unix__
/* Библиотека для Рутокен Lite, Рутокен ЭЦП поддерживает алгоритмы ГОСТ и RSA */
	#define PKCS11_LIBRARY_NAME         "librtpkcs11ecp.so"
	#define PKCS11ECP_LIBRARY_NAME      "librtpkcs11ecp.so"
#endif 	
#ifdef __APPLE__
/* Библиотека для Рутокен Lite, Рутокен ЭЦП поддерживает алгоритмы ГОСТ и RSA */
	#define PKCS11_LIBRARY_NAME         "librtpkcs11ecp.dylib"
	#define PKCS11ECP_LIBRARY_NAME      "librtpkcs11ecp.dylib"
#endif 	

HMODULE hModule = NULL_PTR;                                     // Хэндл загруженной библиотеки PKCS#11
CK_FUNCTION_LIST_PTR pFunctionList = NULL_PTR;                  // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
CK_C_GetFunctionList pfGetFunctionList = NULL_PTR;              // Указатель на функцию C_GetFunctionList
CK_RV rv = CKR_OK;                                              // Вспомогательная переменная для хранения кода возврата
 
while (TRUE)
{
	/* Загрузить библиотеку */
	printf("Loading library %s", PKCS11ECP_LIBRARY_NAME);
	hModule = LoadLibrary(PKCS11ECP_LIBRARY_NAME);
	if (hModule == NULL_PTR)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Получить адрес функции запроса структуры с указателями на функции */	
	printf("Getting GetFunctionList function");
	pfGetFunctionList = (CK_C_GetFunctionList)GetProcAddress(hModule, "C_GetFunctionList");
	if (pfGetFunctionList == NULL_PTR)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	/* Получить структуру с указателями на функции */
	printf("Getting function list");
	rv = pfGetFunctionList(&pFunctionList);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	...
	break;
}
 
/* Выгрузить библиотеку из памяти */
if (hModule)
{
	printf("Unloading library");
	if (FreeLibrary(hModule) != TRUE)
		printf(" -> Failed\n");
	else
		printf(" -> OK\n");
	hModule = NULL_PTR;
}

Инициализация и деинициализация библиотеки

После загрузки библиотеки ее нужно инициализировать вызовом функции C_Initialize(). Параметр NULL при вызове данной функции означает, что функции библиотеки не будут вызываться из нескольких потоков, в противном случае в параметре должен быть передан указатель на структуру типа CK_INITIALIZE_ARGS.

Для завершения работы с библиотекой ее нужно деинициализировать вызовом функции C_Finalize().

Инициализация библиотеки
...
 
/* Инициализировать библиотеку */
printf("Initializing library");
rv = pFunctionList->C_Initialize(NULL_PTR);
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");

...
 
/* Деинициализировать библиотеку */
if (pFunctionList)
{
	printf("Finalizing library");
	rvTemp = pFunctionList->C_Finalize(NULL_PTR);
	if (rvTemp != CKR_OK)
		printf(" -> Failed\n");
	else
		printf(" -> OK\n");
	pFunctionList = NULL_PTR;
}

Определение подключенных устройств

Доступ к каждому подключенному устройству осуществляется с помощью идентификатора слота, к которому оно подключено. Для получения списка всех слотов предназначена функция C_GetSlotList(). Значение первого параметра указывает, должен ли список включать слоты только с подключенным токенами (CK_TRUE) или все слоты (CK_FALSE). 

Получение списка токенов
CK_SLOT_ID_PTR aSlots = NULL_PTR;           // Указатель на массив идентификаторов слотов
CK_ULONG ulSlotCount = 0;                   // Количество идентификаторов слотов в массиве
 
while(TRUE)
{
...
	/* Получить количество слотов c подключенными токенами */
	printf(" Getting number of connected slots");
	rv = pFunctionList->C_GetSlotList(CK_TRUE, NULL_PTR, &ulSlotCount);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	aSlots = (CK_SLOT_ID*)malloc(ulSlotCount * sizeof(CK_SLOT_ID));
	if (aSlots == NULL)
	{
		printf("Memory allocation for aSlots failed! \n");
		break;
	}
	memset(aSlots, 0, (ulSlotCount * sizeof(CK_SLOT_ID)));

	/* Получить список слотов c подключенными токенами */
	printf(" Getting list of connected slots");
	rv = pFunctionList->C_GetSlotList(CK_TRUE,	aSlots, &ulSlotCount);
	if (rv != CKR_OK)
	{
		printf(" -> Failed %X\n", (int)rv);
		break;
	}
	printf(" -> OK\n");

	printf(" Slots available: 0x%8.8X\n", (int)ulSlotCount);
	...
	break;
}
 
if (aSlots)
{
	free(aSlots);
	aSlots = NULL_PTR;
}

Для получения актуальной информации о состоянии конкретного слота вызывается функция C_GetSlotInfo(), в которую передается идентификатор слота. Ее вызов запускает обновление информации обо всех слотах. Если токен извлечь из разъема и затем снова вставить в тот же самый разъем, то он может подключиться к любому свободному слоту, а не обязательно к тому же самому.

Мониторинг событий в слоте

Для мониторинга событий извлечения и подключения токенов для всех слотов используется функция C_WaitForSlotEvent(), запущенная в отдельном потоке.

При вызове C_WaitForSlotEvent() с флагом CKF_DONT_BLOCK функция возвращает код CKR_NO_EVENT при отсутствии событий или код CKR_OK при его наличии (вместе с идентификатором соответствующего событию слота).

При вызове C_WaitForSlotEvent() с флагом 0 выполнение функции блокируется до возникновения события и функция возвращает код CKR_OK и номер соответствующего слота.

Мониторинг событий в слотах
 /* Количество потоков, одновременно ожидающих события в каком-либо слоте*/
#define MONITORING_THREADS_NUMBER    1

/* Структура данных, содержащая параметры работы для функции ожидания событий в слотах */
typedef struct _MONITORING_THREADS_PARAMS
{
	CK_FUNCTION_LIST_PTR m_pFunctionList;
	CK_FLAGS m_flags;
	DWORD m_dwThread_Number;
} MONITORING_THREADS_PARAMS, * PMONITORING_THREADS_PARAMS;

/* Запустить поток, ожидающий событие в слоте.
   До наступления события выполнение потока заблокировано */
void Monitoring_Slots(IN void* param)  // Указатель на структуру данных типа MONITORING_THREADS_PARAMS с параметрами для запуска потоков 
{
	CK_FUNCTION_LIST_PTR pFunctionList = NULL_PTR;        // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
	CK_SLOT_ID slotID = 0xFFFFFFFF;                       // Идентификатор слота, в котором произошло событие
	CK_SLOT_INFO slotInfo;                                // Структура данных типа CK_SLOT_INFO с информацией о слоте
	CK_FLAGS ckFlags = 0;                                 // Вспомогательная переменная для хранения флагов, передаваемых в функцию C_ cWaitForSlotEvent
	DWORD dwThreadNumber = 0;                             // Вспомогательная переменная для хранения порядкового номера запущенного потока
	CK_RV rv = CKR_OK;                                    // Вспомогательная переменная для хранения кода возврата
	
	/* Получить из структуры данных типа MONITORING_THREADS_PARAMS параметры для дальнейшей работы*/
	PMONITORING_THREADS_PARAMS pMonitoring_Threads_Param = (PMONITORING_THREADS_PARAMS)param;
	pFunctionList = pMonitoring_Threads_Param->m_pFunctionList;
	ckFlags = pMonitoring_Threads_Param->m_flags;
	dwThreadNumber = pMonitoring_Threads_Param->m_dwThread_Number;

	while (TRUE)
	{
		/* Ожидать событие в некотором слоте, режим работы функции C_WaitForSlotEvent зависит от значения флагов ckFlags */
		slotID = 0xFFFFFFFF;
		rv = pFunctionList->C_WaitForSlotEvent(ckFlags,  // 0 для блокирования потока и CKF_DONT_BLOCK для неблокирования
		                                       &slotID,	 // Идентификатор слота, в котором произошло событие
		                                       NULL_PTR);
		if (rv == CKR_CRYPTOKI_NOT_INITIALIZED)
		{
			printf("Work with PKCS#11 has been finished.\n");
			break;
		}
		if (rv == CKR_NO_EVENT)
		{
			printf(" -> Failed \n"
			       "No more slot events...\n");
			break;
		}
		if (rv != CKR_OK)
		{
			printf(" -> Failed\n");
			break;
		}
		memset(&slotInfo, 0,  sizeof(CK_SLOT_INFO));

		/* Получить информацию о слоте */
		rv = pFunctionList->C_GetSlotInfo(slotID,
		                                  &slotInfo);
		if (rv != CKR_OK)
		{
			printf(" -> Failed\n");
			break;
		}

		/* Распечатать информацию о номере потока и событии в слоте */
		printf("\n Monitoring thread: 0x%8.8x \n", (int)dwThreadNumber);
		printf("  Slot ID:          0x%8.8x \n", (int)slotID);
		if (slotInfo.flags & CKF_TOKEN_PRESENT)
			printf("  Token has been attached!\n");
		else
			printf("  Token has been detached!\n");
	}
	printf("Exiting from thread: 0x%8.8x \n\n", (int)dwThreadNumber);
}
 
int main(int argc, char* argv[])
{
	DWORD i = 0;                                             // Вспомогательная переменная. Счетчик цикла
	...

	while (TRUE)
	{
		...
		printf("\nPlease attach or detach Rutoken and press Enter...\n");
		getchar();
		i = 1;
		while (TRUE)
		{
			printf("Events counter: 0x%8.8x \n", (int)i);
			/* Получить все события в слотах, не блокируя поток */
			printf("C_WaitForSlotEvent");
			rv = pFunctionList->C_WaitForSlotEvent(CKF_DONT_BLOCK, 	// Не блокировать поток
			                                       &slotID,			// Идентификатор слота, в котором произошло событие
			                                       NULL_PTR);		// Зарезервировано, должно быть NULL_PTR
			if (rv == CKR_NO_EVENT)
			{
				printf(" -> OK\n");
				printf("No more slots events.\n");
				break;
			}
			if (rv != CKR_OK)
			{
				printf(" -> Failed\n");
				break;
			}
			printf(" -> OK\n");
		}

		if ((rv != CKR_NO_EVENT) && (rv != CKR_OK))
			break;

		/* Запустить поток, ожидающих событие в каком-либо слоте.
		   До наступления события выполнение запущенного потока заблокировано. 
           Первое событие разблокирует выполнение ожидающего потока */
		while (TRUE)
		{
			MONITORING_THREADS_PARAMS aThreads_With_Blocking[MONITORING_THREADS_NUMBER];
			uintptr_t aThreads[MONITORING_THREADS_NUMBER];
			for (i = 0;
			     i < MONITORING_THREADS_NUMBER;
			     i++)
			{
				printf("Starting monitoring thread number 0x%8.8X \n", (int)i);
				memset(&aThreads_With_Blocking[i],
				       0,
				       sizeof(MONITORING_THREADS_PARAMS));
				aThreads_With_Blocking[i].m_pFunctionList = pFunctionList;
				aThreads_With_Blocking[i].m_flags = 0;
				aThreads_With_Blocking[i].m_dwThread_Number = i;
				aThreads[i] = CreateProc(&aThreads[i], NULL_PTR, &Monitoring_Slots, &aThreads_With_Blocking[i]);
			}
			printf("\n\nPlease attach or detach Rutoken or press Enter to exit.\n");
			getchar();
			break;
		}
		break;
	}	
}

Определение типа устройств

Стандарт PKCS#11 предлагает определять тип устройства по параметрам, возвращаемым функциями C_GetSlotInfo() и C_GetTokenInfo(). Функция C_GetSlotInfo() возвращает структуру типа CK_SLOT_INFO, содержащую в поле slotDescription имя считывателя  и в поле manufacturerID производителя устройства (Aktiv Co.). Функция C_GetTokenInfo() возвращает структуру типа CK_TOKEN_INFO, содержащую в поле model наименование модели устройства. Имя считывателя или наименование устройства обычно позволяет однозначно идентифицировать тип подключенного устройства. В таблице приведены значения для каждого из устройств Рутокен.

Модель РутокенЗначение  slotDescription структуры CK_SLOT_INFOЗначение  model структуры CK_TOKEN_INFO
Рутокен SAktiv Co. ruToken 0Rutoken S 64K, где 64K - размер памяти
Рутокен LiteAktiv Rutoken lite 0Rutoken lite
Рутокен ЭЦПAktiv Rutoken ECP 0Rutoken ECP

Более удобным способом получить информацию о подключенном к слоту токене можно с помощью функции расширения C_EX_GetTokenInfoExtended(), которая возвращает расширенные данные в виде структуры типа CK_TOKEN_INFO_EXTENDED. Поля ulTokenClass и ulTokenType содержат информацию о классе и типе устройства соответственно и могут быть использованы для их определения. 

Определение класса токена
CK_C_EX_GetFunctionListExtended pfGetFunctionListEx = NULL_PTR; 	// Указатель на функцию C_EX_GetFunctionListExtended
CK_FUNCTION_LIST_EXTENDED_PTR 	pFunctionListEx = NULL_PTR;       	// Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
CK_TOKEN_INFO_EXTENDED			tokenInfoEx;                        // Структура данных типа CK_TOKEN_INFO_EXTENDED с информацией о токене
 
while(TRUE)
{
	...
 
	printf("Determining token type");
 
	/* Получить адрес функции запроса структуры с указателями на функции расширения */
	pfGetFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(hModule,
				                                                          "C_EX_GetFunctionListExtended");
	if (pfGetFunctionListEx == NULL_PTR)
	{
		printf(" -> Failed\n");
		break;
	}
 
	/* Получить структуру с указателями на функции расширения */
	rv = pfGetFunctionListEx(&pFunctionListEx);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	memset(&tokenInfoEx,
	   		0,
	  		sizeof(CK_TOKEN_INFO_EXTENDED));
	tokenInfoEx.ulSizeofThisStructure = sizeof(CK_TOKEN_INFO_EXTENDED);
	/* Получить расширенную информацию о подключенном токене */
	rv = pFunctionListEx->C_EX_GetTokenInfoExtended(aSlots[0],		// Идентификатор слота, к которому подключен токен
			                                   		&tokenInfoEx);	// Буфер для помещения информации о токене
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	/* Определить класс токена */
	switch (tokenInfoEx.ulTokenClass)
	{
		case TOKEN_CLASS_S: 
			printf(": Rutoken / Rutoken S\n");
		case TOKEN_CLASS_ECP:
			printf(": Rutoken ECP\n");
		case TOKEN_CLASS_LITE:
			printf(": Rutoken Lite\n");
		default:
			printf(": undefined\n");
	}

	break;
}

Открытие и закрытие сессии

Большинство функций PKCS#11 требует наличие открытой сессии между токеном и приложением.

Для открытия сессии используется функция C_OpenSession(), для закрытия сессии – C_CloseSession(), для закрытия всех открытых сессий – C_CloseAllSessions().

Сессия может быть открыта только для чтения объектов на токене или для чтения и записи (флаг CKF_RW_SESSION). После открытия сессии приложение получает доступ к публичным объектам на токене. Для доступа к приватным объектом пользователь должен получить доступ Пользователя или Администратора функций C_Login().

При закрытии сессии все сессионные объекты уничтожаются, даже если приложение использует эти объекты в других сессиях. 

Мониторинг событий в слотах
CK_SESSION_HANDLE hSession = NULL_PTR;       // Хэндл открытой сессии

...
 
/* Открыть RW сессию в первом доступном слоте */
printf("Opening Session");
rv = pFunctionList->C_OpenSession(aSlots[0], 								// Идентификатор слота
								  CKF_SERIAL_SESSION | CKF_RW_SESSION,		// Флаги сессии
								  NULL_PTR,
								  NULL_PTR,
								  &hSession);								// Хэндл сессии
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");
 
...
 
/* Закрыть все открытые сессии в слоте */
printf("C_CloseAllSession");
rv = pFunctionList->C_CloseAllSessions(aSlots[0]);
if (rvTemp != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");
hSession = NULL_PTR;

Получение и сброс прав доступа

В PKCS#11 доступны две глобальные роли: CKU_USER – пользователь Рутокен, CKU_SO – администратор Рутокен. Помимо них, устройства Рутокен ЭЦП имеют локальные роли, про которые можно ознакомиться в разделе Установка и смена локального PIN-кода.

Для аутентификации предварительно необходимо открыть сессию. 

Мониторинг событий в слотах
/* DEMO PIN-код Пользователя Рутокен */
CK_UTF8CHAR      USER_PIN[]      = {'1', '2', '3', '4', '5', '6', '7', '8'};
 
...
 
/* Выполнить аутентификацию Пользователя */
printf("Logging in");
rv = pFunctionList->C_Login(hSession,				// Хэндл сессии
							CKU_USER,				// Тип пользователя
							USER_PIN,				// PIN-код пользователя
							sizeof(USER_PIN));		// Длина PIN-кода
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");
 
...
 
/*Сбросить права доступа */
printf("Logging out");
rv = pFunctionList->C_Logout(hSession);
if ((rv == CKR_OK) || (rv == CKR_USER_NOT_LOGGED_IN))
	printf(" -> OK\n");
else
	printf(" -> Failed\n");

Управление объектами на токене

Общие атрибуты объектов 

Стандарт PKCS#11 различает несколько классов объектов. Объекты содержат набор атрибутов, каждый из которых имеет только одно определенное значение. 

Атрибут CKA_TOKEN определяет, будет ли созданный объект храниться только в рамках текущей сессии (значение CK_FALSE), или будет сохранен в памяти Рутокен (значение CK_TRUE). Сессионный объект будет уничтожен автоматически при закрытии сессии, а сохраненный на токене доступен вплоть до физического удаления объекта.

Атрибут CKA_PRIVATE определяет доступность объекта. Пользователь не имеет доступа к приватному объекту (значение CK_TRUE) до тех пор, пока не выполнит аутентификацию на токене. Публичный же объект доступен без аутентификации (значение CK_FALSE). 

Атрибут CKA_MODIFIABLE отвечает за возможность изменения атрибутов объекта после его создания. По умолчанию атрибут принимает значение CK_TRUE, при котором редактирование атрибутов объекта становится возможным. Значение CK_FALSE означает, что созданный объект будет доступен «только для чтения» и значения атрибутов объекта после его создания не могут быть изменены.

Сгенерированные устройствами Рутокен закрытые и секретные ключевые объекты в целях безопасности является неизвлекаемыми. Это означает, что значение ключа (значение атрибута CKA_VALUE) невозможно получить через функцию C_GetAttributeValue(). Все криптографические операции с такими ключами производятся внутри устройства без передачи значения ключа наружу.

Создание объекта на токене

Для создания объектов на токене предназначена функция C_CreateObject(). В нее передается предварительно созданный шаблон с атрибутами создаваемого объекта, размер шаблона и хэндл открытой сессии с правами Пользователя. Функция возвращает хэндл созданного с указанными атрибутами объекта.

Устройства Рутокен, сертифицированные ФСБ, не поддерживают создание (импорт) ключей функцией C_CreateObject по алгоритмам ГОСТ 28147-89, ГОСТ Р 34.10-2001 и ГОСТ Р 34.10-2012 в долговременную память (с флагом CKA_TOKEN = TRUE).

Создание объекта типа CKO_DATA на токене
CK_OBJECT_CLASS ocData = CKO_DATA;
CK_UTF8CHAR 	label[] = "Data object sample";
CK_UTF8CHAR 	application[] = "Rutoken Control Panel";
CK_BYTE 		data[] = "Sample data";
CK_BBOOL 		true = CK_TRUE;


CK_ATTRIBUTE attrDataTmpl[] = {
  {CKA_CLASS, &ocData, sizeof(ocData)},
  {CKA_LABEL, label, sizeof(label)-1},
  {CKA_VALUE, data, sizeof(data)},
  {CKA_APPLICATION, application, sizeof(application)-1},
  {CKA_TOKEN, &true, sizeof(true )}
};
 
CK_OBJECT_HANDLE hData; // Хэндл созданного объекта типа CKO_DATA
 
...
 
printf("Create object");
rv = pFunctionList->C_CreateObject( hSession, 				// Открытая с правами Пользователя сессия
									attrDataTmpl, 			// Шаблон с атрибутами создаваемого объекта
									arraysize(attrDataTmpl),// Размер шаблона
									&hData);				// Возвращаемый хэндл созданного объекта
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");	

Импорт объектов на токен 

Для импорта объектов также используется функция C_CreateObject(). В нее передается предварительно созданный шаблон с атрибутами импортируемого объекта (в том числе и значением CKA_VALUE), размер шаблона и хэндл открытой сессии с правами Пользователя. Функция возвращает хэндл созданного с указанными атрибутами объекта.

Устройства Рутокен, сертифицированные ФСБ, не поддерживают создание (импорт) ключей функцией C_CreateObject по алгоритмам ГОСТ 28147-89, ГОСТ Р 34.10-2001 и ГОСТ Р 34.10-2012 в долговременную память (с флагом CKA_TOKEN = TRUE).

Импорт сертификата
/* Значение сертификата ключа подписи */
CK_BYTE cbCertificate[] = { 0x30, 0x82, 0x03, 0x36, 0x30, 0x82, 0x02, 0xe5,
							0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0a, 0x24,
							0xa5, 0x24, 0x63, 0x00, 0x02, 0x00, 0x01, 0xa7,
							0x15, 0x30, 0x08, 0x06, 0x06, 0x2a, 0x85, 0x03,
							0x02, 0x02, 0x03, 0x30, 0x65, 0x31, 0x20, 0x30,
							0x1e, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
							0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, 0x69, 0x6e,
							0x66, 0x6f, 0x40, 0x63, 0x72, 0x79, 0x70, 0x74,
							0x6f, 0x70, 0x72, 0x6f, 0x2e, 0x72, 0x75, 0x31,
							0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
							0x13, 0x02, 0x52, 0x55, 0x31, 0x13, 0x30, 0x11,
							0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x43,
							0x52, 0x59, 0x50, 0x54, 0x4f, 0x2d, 0x50, 0x52,
							0x4f, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55,
							0x04, 0x03, 0x13, 0x16, 0x54, 0x65, 0x73, 0x74,
							0x20, 0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x20,
							0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x2d, 0x50,
							0x52, 0x4f, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31,
							0x31, 0x31, 0x32, 0x32, 0x31, 0x30, 0x31, 0x33,
							0x34, 0x32, 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x31,
							0x30, 0x30, 0x34, 0x30, 0x37, 0x30, 0x39, 0x34,
							0x31, 0x5a, 0x30, 0x65, 0x31, 0x10, 0x30, 0x0e,
							0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x07, 0x49,
							0x76, 0x61, 0x6e, 0x6f, 0x66, 0x66, 0x31, 0x0b,
							0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
							0x02, 0x52, 0x55, 0x31, 0x14, 0x30, 0x12, 0x06,
							0x03, 0x55, 0x04, 0x05, 0x13, 0x0b, 0x31, 0x32,
							0x33, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x31,
							0x32, 0x31, 0x1d, 0x30, 0x1b, 0x06, 0x09, 0x2a,
							0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01,
							0x16, 0x0e, 0x69, 0x76, 0x61, 0x6e, 0x6f, 0x76,
							0x40, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x72, 0x75,
							0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04,
							0x08, 0x13, 0x06, 0x4d, 0x6f, 0x73, 0x63, 0x6f,
							0x77, 0x30, 0x63, 0x30, 0x1c, 0x06, 0x06, 0x2a,
							0x85, 0x03, 0x02, 0x02, 0x13, 0x30, 0x12, 0x06,
							0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01,
							0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e,
							0x01, 0x03, 0x43, 0x00, 0x04, 0x40, 0xfb, 0x3c,
							0xdc, 0x59, 0xc3, 0x9c, 0x4a, 0x43, 0x89, 0x87,
							0xc7, 0xd7, 0xfe, 0x50, 0x19, 0xb3, 0x0c, 0x8b,
							0x76, 0x97, 0xa9, 0xdf, 0xb7, 0xca, 0x2c, 0x6c,
							0x3b, 0xa9, 0x13, 0xf4, 0xe0, 0x69, 0x02, 0x59,
							0x92, 0x47, 0x21, 0x1a, 0xef, 0x90, 0x61, 0x91,
							0x40, 0x30, 0xdd, 0x7c, 0xb0, 0x4f, 0x64, 0x5f,
							0x24, 0x9a, 0xf1, 0xd6, 0x0f, 0xa9, 0xf0, 0x86,
							0xd9, 0x35, 0x2b, 0x3e, 0xf2, 0xf3, 0xa3, 0x82,
							0x01, 0x73, 0x30, 0x82, 0x01, 0x6f, 0x30, 0x0b,
							0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03,
							0x02, 0x04, 0xf0, 0x30, 0x26, 0x06, 0x03, 0x55,
							0x1d, 0x25, 0x04, 0x1f, 0x30, 0x1d, 0x06, 0x07,
							0x2a, 0x85, 0x03, 0x02, 0x02, 0x22, 0x06, 0x06,
							0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03,
							0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05,
							0x07, 0x03, 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55,
							0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x8a, 0x24,
							0x35, 0x74, 0x6b, 0xf7, 0x91, 0x17, 0x92, 0xb2,
							0xcf, 0x8f, 0x63, 0x87, 0xb7, 0x69, 0x06, 0xe1,
							0x71, 0xf2, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
							0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x6d,
							0x8f, 0x5e, 0x05, 0xd9, 0x5f, 0xac, 0x91, 0x17,
							0x94, 0x1e, 0x95, 0x9a, 0x05, 0x30, 0x38, 0x37,
							0x7a, 0x10, 0x2a, 0x30, 0x55, 0x06, 0x03, 0x55,
							0x1d, 0x1f, 0x04, 0x4e, 0x30, 0x4c, 0x30, 0x4a,
							0xa0, 0x48, 0xa0, 0x46, 0x86, 0x44, 0x68, 0x74,
							0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
							0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x70,
							0x72, 0x6f, 0x2e, 0x72, 0x75, 0x2f, 0x43, 0x65,
							0x72, 0x74, 0x45, 0x6e, 0x72, 0x6f, 0x6c, 0x6c,
							0x2f, 0x54, 0x65, 0x73, 0x74, 0x25, 0x32, 0x30,
							0x43, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x25, 0x32,
							0x30, 0x43, 0x52, 0x59, 0x50, 0x54, 0x4f, 0x2d,
							0x50, 0x52, 0x4f, 0x28, 0x32, 0x29, 0x2e, 0x63,
							0x72, 0x6c, 0x30, 0x81, 0xa0, 0x06, 0x08, 0x2b,
							0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
							0x81, 0x93, 0x30, 0x81, 0x90, 0x30, 0x33, 0x06,
							0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30,
							0x01, 0x86, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a,
							0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x72,
							0x79, 0x70, 0x74, 0x6f, 0x70, 0x72, 0x6f, 0x2e,
							0x72, 0x75, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x6e,
							0x63, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x73,
							0x72, 0x66, 0x30, 0x59, 0x06, 0x08, 0x2b, 0x06,
							0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x4d,
							0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
							0x77, 0x77, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74,
							0x6f, 0x70, 0x72, 0x6f, 0x2e, 0x72, 0x75, 0x2f,
							0x43, 0x65, 0x72, 0x74, 0x45, 0x6e, 0x72, 0x6f,
							0x6c, 0x6c, 0x2f, 0x70, 0x6b, 0x69, 0x2d, 0x73,
							0x69, 0x74, 0x65, 0x5f, 0x54, 0x65, 0x73, 0x74,
							0x25, 0x32, 0x30, 0x43, 0x65, 0x6e, 0x74, 0x65,
							0x72, 0x25, 0x32, 0x30, 0x43, 0x52, 0x59, 0x50,
							0x54, 0x4f, 0x2d, 0x50, 0x52, 0x4f, 0x28, 0x32,
							0x29, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x08, 0x06,
							0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x03, 0x03,
							0x41, 0x00, 0x2b, 0xd2, 0xfe, 0x64, 0x54, 0x3a,
							0xe1, 0xf6, 0x89, 0x75, 0xfe, 0xbb, 0xa6, 0x29,
							0xed, 0x0b, 0x92, 0xc0, 0xa4, 0x84, 0x15, 0x59,
							0x23, 0x12, 0x08, 0xbb, 0xd3, 0xab, 0x8e, 0x2e,
							0x75, 0xb9, 0xbf, 0x9e, 0xd1, 0x9d, 0x1e, 0xf9,
							0x6a, 0x24, 0xed, 0xb8, 0x58, 0x15, 0x1f, 0x03,
							0x11, 0xfa, 0xd3, 0x85, 0xf1, 0x34, 0x96, 0xac,
							0x20, 0x8e, 0xdd, 0xad, 0x4e, 0xae, 0x55, 0x3e,
							0x8d, 0xd1, 0xff,
};

/* Шаблон для импорта сертификата ключа подписи */
CK_ATTRIBUTE CertTmpl[] =
{
	{ CKA_CLASS, &ocCert, sizeof(ocCert)},							// Объект сертификата
	{ CKA_ID, &KeyPairIDGOST1, sizeof(KeyPairIDGOST1) - 1},         // Идентификатор сертификата
	{ CKA_TOKEN, &bTrue, sizeof(bTrue)},                            // Сертификат является объектом токена
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)},                        // Сертификат доступен без авторизации на токене
	{ CKA_VALUE, &cbCertificate, sizeof(cbCertificate)}				// Значение сертификата
};

CK_OBJECT_HANDLE 	hCert;								 // Хэндл сертификата
 
printf("Import certificate");
rv = pFunctionList->C_CreateObject( hSession, 			// Открытая с правами Пользователя сессия
									CertTmpl, 			// Шаблон с атрибутами создаваемого объекта
									arraysize(CertTmpl),// Размер шаблона
									&hCert);			// Возвращаемый хэндл созданного объекта
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");	

Поиск объектов на токене

Для поиска объектов на токене предназначены три функции C_FindObjectsInit()C_FindObjects() и  C_FindObjectsFinal(). Сначала операция поиска инициализируется функцией C_FindObjectsInit(), в которую передается указатель шаблон атрибутов для поиска объекта и его размер, затем функция C_FindObjects() выполняет поиск объектов согласно заданным атрибутам и возвращает хэндлы всех найденных объектов в массиве. C_FindObjectsFinal() завершает процедуру поиска.

Поиск объектов на токене
/* Шаблон для поиска симметричного ключа ГОСТ 28147-89 */
CK_ATTRIBUTE attrGOST28147SecKey[] =
{
	{ CKA_ID, &SecKeyID, sizeof(SecKeyID) - 1}       // Критерий поиска - идентификатор ключа
};
 
CK_OBJECT_HANDLE_PTR 	phObject = NULL_PTR;         // Указатель на массив хэндлов объектов, соответствующих критериям поиска
CK_ULONG 				ulObjectCount = 0;           // Количество хэндлов объектов в массиве
CK_RV 					rvTemp = CKR_OK;             // Вспомогательная переменная для хранения кода возврата

while (TRUE)
{
	...

	while(TRUE)
	{
		/* Инициализировать операцию поиска */
		printf("C_FindObjectsInit");
		rv = pFunctionList->C_FindObjectsInit(hSession,							// Хэндл сессии, открытой с правами Пользователя
		                                      attrGOST28147SecKey,				// Шаблон для поиска с атрибутами объекта
		                                      arraysize(attrGOST28147SecKey));	// Количество строк в шаблоне
		if (rv != CKR_OK)
		{
			printf(" -> Failed\n");
			break;
		}
		printf(" -> OK\n");
	
		/* Найти все объекты, соответствующие критериям поиска */
		printf("C_FindObjects");
		// Считаем, что максимальное количество объектов не превышает 100
		phObject = (CK_OBJECT_HANDLE*)malloc(100 * sizeof(CK_OBJECT_HANDLE));
		if (phObject == NULL)
		{
			printf("Memory allocation for aSlots failed! \n");
			break;
		}
		memset(phObject,
		       0,
		       (100 * sizeof(CK_OBJECT_HANDLE)));
		rv = pFunctionList->C_FindObjects(hSession,			// Хэндл открытой сессии
		                                  phObject,			// Указатель на массив хэндлов найденных объектов
		                                  100,				// Максимальное количество хэндлов найденных объектов
		                                  pulObjectCount);	// Фактическое количество найденных объектов
		if (rv != CKR_OK)
			printf(" -> Failed\n");
		else
			printf(" -> OK\n");
		break;
	}

	/* Деинициализировать операцию поиска */
	printf("C_FindObjectsFinal");
	rvTemp = pFunctionList->C_FindObjectsFinal(hSession);	// Хэндл открытой сессии
	if (rvTemp != CKR_OK)
		printf(" -> Failed\n");
	else
		printf(" -> OK\n");
	if (rv == CKR_OK)
		printf("Search has been completed.\n"
		       "Objects found: %d \n",
		       (int)ulObjectCount);
	else
	{
		printf("Search failed!\n");
		if (phObject)
		{
			free(phObject);
			phObject = NULL_PTR;
			ulObjectCount = 0;
		}
	}
	break;
}

Чтение и изменение объектов

Для чтения атрибутов созданного объекта используется функция C_GetAttributeValue(). Если объекты или его атрибуты не являются защищенными от записи (например, значение атрибута CKA_MODIFIABLE равно CK_TRUE), то значения атрибутов могут быть изменены функцией C_SetAttributeValue().

Сгенерированные устройствами Рутокен закрытые и секретные ключевые объекты в целях безопасности является неизвлекаемыми. Это означает, что значение ключа (значение атрибута CKA_VALUE) невозможно получить через функцию C_GetAttributeValue(). Все криптографические операции с такими ключами производятся внутри устройства без извлечения ключа и передачи его значения наружу.

Чтение и изменение атрибутов объекта
CK_BYTE 		new_data[] = "Sample Rutoken data";

CK_ATTRIBUTE attrDataReadTmpl[] = {CKA_VALUE, NULL_PTR, NULL};
CK_ATTRIBUTE attrDataEditTmpl[] = {CKA_VALUE, new_data, sizeof(new_data)};


while(true)
{
	...
 
	/* Изменить значение объекта */
	printf("Setting object value");
	rv = pFunctionList->C_SetAttributeValue(hSession,			// Хэндл открытой с правами Пользователя сессии 
		                                    hData,				// Хэндл объекта ключа
		                                    &attrDataEditTmpl,	// Шаблон изменения значения атрибута
		                                    1);					// Количество атрибутов в шаблоне
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	/* Получить размер буфера для хранения значения атрибута CKA_VALUE*/
	printf("Getting object value size");
	rv = pFunctionList->C_GetAttributeValue(hSession,			// Хэндл открытой с правами Пользователя сессии 
		                                    hData,				// Хэндл объекта ключа
		                                    &attrDataReadTmpl,	// Шаблон получения значения атрибута
		                                    1);					// Количество атрибутов в шаблоне
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Выделить необходимое количество памяти для значения атрибута */
	attrDataReadTmpl.pValue = (CK_BYTE*)malloc(attrDataReadTmpl.ulValueLen);
	if (attrDataReadTmpl.pValue == NULL)
	{
		printf("Memory allocation for attrDataReadTmpl failed! \n");
		break;
	}
	memset(attrDataReadTmpl.pValue,
	       0,
	       (attrDataReadTmpl.ulValueLen * sizeof(CK_BYTE)));
 
	/* Получить значение объекта */
	printf("Getting object value");
	rv = pFunctionList->C_GetAttributeValue(hSession,			// Хэндл открытой с правами Пользователя сессии 
		                                    hData,				// Хэндл объекта ключа
		                                    &attrDataReadTmpl,	// Шаблон получения значения атрибута
		                                    1);					// Количество атрибутов в шаблоне
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	/* Распечатать буфер со значением объекта */
	printf("Data is:\n");
	for (i = 0;
	     i < attrDataReadTmpl.ulValueLen;
	     i++)
		printf("%s", attrDataReadTmpl.pValue[i]);
	break;
}
 
if (attrDataReadTmpl.pValue)
{
	free(attrDataReadTmpl.pValue);
	attrDataReadTmpl.pValue = NULL_PTR;
	attrDataReadTmpl.ulValueLen= 0;
}
 

Удаление объектов на токене

Для удаления объекта на токене необходимо знать его хэндл, который следует передать в функцию C_DestroyObject() (предварительно должна быть открыта сессия с правами Пользователя). Если хэндл объекта неизвестен, то по любому известному атрибуту объекта можно получить его хэндл через группу функций  C_FindObjectsInit()C_FindObjects() и C_FindObjectsFinal()

Удаление объектов на токене
/* Удалить все найденные объекты */
for (i = 0;
	 i < ulObjectCount;
	 i++)
{
	printf(" C_DestroyObject %d", (int)(i + 1));
	rv = pFunctionList->C_DestroyObject(hSession,		// Открытая с правами Пользователя сессия
	                                    phObject[i]);	// Массив хэндлов объектов для удаления
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
}	
if (ulObjectCount != 0)
	printf("Destruction objects has been completed successfully.\n");
free(phObject);
phObject = NULL_PTR;
ulObjectCount = 0;

Генерация ключевой пары

Атрибуты ключевых объектов

Все поддерживаемые устройствами Рутокен атрибуты объектов представлены в разделе Объекты PKCS #11.

Поддерживаемые типы ключей

Устройства Рутокен поддерживают следующие типы ключей асимметричной криптографии (CK_KEY_TYPE

  • CKK_GOSTR3410 для ключей ГОСТ Р 34.10-2001 и  ГОСТ Р 34.10-2012 (256 бит),

  • CKK_GOSTR3410_512 для ключей ГОСТ Р 34.10-2012 (512 бит),
  • CKK_RSA для ключей RSA,
  • CKK_EC для ключей ECDSA.

Примеры шаблонов ключей ГОСТ Р 34.10-2012

Ниже представлены примеры шаблонов закрытого и открытого ключа ГОСТ Р 34.10-2012 с пояснениями.

Шаблон закрытого ключа ГОСТ Р 34.10-2012
CK_OBJECT_CLASS	ocPrivKey = CKO_PRIVATE_KEY;
CK_UTF8CHAR 	PrivKeyLabel[] = { "GOST Private Key" };
CK_BYTE 		KeyPairID[] = { "GOST keypair" };
CK_KEY_TYPE 	keyTypeGostR3410 = CKK_GOSTR3410;		// Для закрытого ключа длиной 256 бит
CK_KEY_TYPE 	keyTypeGostR3410_512 = CKK_GOSTR3410_512;	// Для закрытого ключа длиной 512 бит
CK_BBOOL 		bTrue = CK_TRUE;

/* Набор параметров КриптоПро A алгоритма ГОСТ Р 34.10-2012(256) */
CK_BYTE 		parametersGostR3410_2012_256[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01 };

/* Набор параметров КриптоПро A алгоритма ГОСТ Р 34.10-2012(512) */
CK_BYTE 		parametersGostR3410_2012_512[] = { 0x06, 0x09, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x02, 0x01, 0x02, 0x01 };

/* Набор параметров КриптоПро алгоритма ГОСТ Р 34.11-2012(256) */
CK_BYTE 		parametersGostR3411_2012_256[] = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02 };

/* Набор параметров КриптоПро алгоритма ГОСТ Р 34.11-2012(512) */
CK_BYTE 		parametersGostR3411_2012_512[] = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03 };


CK_ATTRIBUTE GOST34_10_2012_256_PrivateKey[] = {					// Шаблон для ключа длиной 256 бит
	{ CKA_CLASS, &ocPrivKey, sizeof(ocPrivKey)},					// Объект закрытого ключа 
	{ CKA_LABEL, &PrivKeyLabel, sizeof(PrivKeyLabel) - 1},			// Метка ключа 
	{ CKA_ID, &KeyPairID, sizeof(KeyPairID) - 1},					// Идентификатор ключевой пары #1 (должен совпадать у открытого и закрытого ключей) 
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410)},	// Тип ключа 
	{ CKA_TOKEN, &bTrue, sizeof(bTrue)},							// Ключ является объектом токена 
	{ CKA_PRIVATE, &bTrue, sizeof(bTrue)},							// Ключ доступен только после авторизации на токене 
	{ CKA_DERIVE, &bTrue, sizeof(bTrue)},							// Ключ поддерживает деривацию (из него могут быть получены другие ключи) 
	{ CKA_GOSTR3410_PARAMS, parametersGostR3410_2012_256, sizeof(parametersGostR3410_2012_256)},// Параметры алгоритма 
	{ CKA_GOSTR3411_PARAMS, parametersGostR3411_2012_256, sizeof(parametersGostR3411_2012_256)}	// Параметры алгоритма ГОСТ Р 34.11-2012(256)
};

CK_ATTRIBUTE GOST34_10_2012_512_PrivateKey[] = {					// Шаблон для ключа длиной 512 бит
	{ CKA_CLASS, &ocPrivKey, sizeof(ocPrivKey)},					// Объект закрытого ключа 
	{ CKA_LABEL, &PrivKeyLabel, sizeof(PrivKeyLabel) - 1},			// Метка ключа 
	{ CKA_ID, &KeyPairID, sizeof(KeyPairID) - 1},					// Идентификатор ключевой пары #1 (должен совпадать у открытого и закрытого ключей) 
	{ CKA_KEY_TYPE, &keyTypeGostR3410_512, sizeof(keyTypeGostR3410_512)},	// Тип ключа 
	{ CKA_TOKEN, &bTrue, sizeof(bTrue)},							// Ключ является объектом токена 
	{ CKA_PRIVATE, &bTrue, sizeof(bTrue)},							// Ключ доступен только после авторизации на токене 
	{ CKA_DERIVE, &bTrue, sizeof(bTrue)},							// Ключ поддерживает деривацию (из него могут быть получены другие ключи) 
	{ CKA_GOSTR3410_PARAMS, parametersGostR3410_2012_512, sizeof(parametersGostR3410_2012_512)},// Параметры алгоритма
	{ CKA_GOSTR3411_PARAMS, parametersGostR3411_2012_512, sizeof(parametersGostR3411_2012_512)}	// Параметры алгоритма ГОСТ Р 34.11-2012(512)
};
Шаблон открытого ключа ГОСТ Р 34.10-2012
CK_OBJECT_CLASS ocPubKey = CKO_PUBLIC_KEY;
CK_UTF8CHAR 	PubKeyLabel[] = { "GOST Public Key" };
CK_BYTE 		KeyPairID[] = { "GOST keypair" };
CK_KEY_TYPE 	keyTypeGostR3410 = CKK_GOSTR3410;			// Для закрытого ключа длиной 256 бит
CK_KEY_TYPE 	keyTypeGostR3410_512 = CKK_GOSTR3410_512;	// Для закрытого ключа длиной 512 бит
CK_BBOOL 		bTrue = CK_TRUE;
CK_BBOOL 		bFalse = CK_FALSE;

/* Набор параметров КриптоПро A алгоритма ГОСТ Р 34.10-2012(256) */
CK_BYTE 		parametersGostR3410_2012_256[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01 };

/* Набор параметров КриптоПро A алгоритма ГОСТ Р 34.10-2012(512) */
CK_BYTE 		parametersGostR3410_2012_512[] = { 0x06, 0x09, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x02, 0x01, 0x02, 0x01 };

/* Набор параметров КриптоПро алгоритма ГОСТ Р 34.11-2012(256) */
CK_BYTE 		parametersGostR3411_2012_256[] = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02 };

/* Набор параметров КриптоПро алгоритма ГОСТ Р 34.11-2012(512) */
CK_BYTE 		parametersGostR3411_2012_512[] = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03 };


CK_ATTRIBUTE GOST34_10_2012_256_PublicKey[] = {						// Шаблон для открытого ключа, соответствующего закрытому длиной 256 бит
	{ CKA_CLASS, &ocPubKey, sizeof(ocPubKey)}, 						// Объект открытого ключа 
	{ CKA_LABEL, &PubKeyLabel, sizeof(PubKeyLabel) - 1}, 				// Метка ключа 
	{ CKA_ID, &KeyPairID, sizeof(KeyPairID) - 1}, 					// Идентификатор ключевой пары 
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410)}, 	// Тип ключа 
	{ CKA_TOKEN, &bTrue, sizeof(bTrue)}, 							// Ключ является объектом токена 
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)}, 						// Ключ доступен без авторизации на токене 
	{ CKA_DERIVE, &bTrue, sizeof(bTrue)}, 							// Ключ поддерживает деривацию (из него могут быть получены другие ключи)
	{ CKA_GOSTR3410_PARAMS, parametersGostR3410_2012_256, sizeof(parametersGostR3410_2012_256)},	// Параметры алгоритма 
	{ CKA_GOSTR3411_PARAMS, parametersGostR3411_2012_256, sizeof(parametersGostR3411_2012_256)} 	// Параметры алгоритма 
};

CK_ATTRIBUTE GOST34_10_2012_512_PublicKey[] = {						// Шаблон для открытого ключа, соответствующего закрытому длиной 512бит
	{ CKA_CLASS, &ocPubKey, sizeof(ocPubKey)}, 						// Объект открытого ключа 
	{ CKA_LABEL, &PubKeyLabel, sizeof(PubKeyLabel) - 1}, 				// Метка ключа 
	{ CKA_ID, &KeyPairID, sizeof(KeyPairID) - 1}, 					// Идентификатор ключевой пары 
	{ CKA_KEY_TYPE, &keyTypeGostR3410_512, sizeof(keyTypeGostR3410_512)}, 	    // Тип ключа 
	{ CKA_TOKEN, &bTrue, sizeof(bTrue)}, 							// Ключ является объектом токена 
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)}, 						// Ключ доступен без авторизации на токене 
	{ CKA_DERIVE, &bTrue, sizeof(bTrue)}, 							// Ключ поддерживает деривацию (из него могут быть получены другие ключи)
	{ CKA_GOSTR3410_PARAMS, parametersGostR3410_2012_512, sizeof(parametersGostR3410_2012_512)}, // Параметры алгоритма
	{ CKA_GOSTR3411_PARAMS, parametersGostR3411_2012_512, sizeof(parametersGostR3411_2012_512)}  // Параметры алгоритма ГОСТ Р 34.11-2012(512)
};

Поддерживаемые механизмы генерации ключей

Устройства Рутокен поддерживают следующие механизмы генерации ключевой пары:

  • CKM_GOSTR3410_KEY_PAIR_GEN для генерации ключевой пары ГОСТ Р 34.10.2001  и  ГОСТ Р 34.10.2012 с длиной ключа 256 бит,
  • CKM_GOSTR3410_512_KEY_PAIR_GEN для генерации ключевой пары ГОСТ Р 34.10.2012 с длиной ключа 512 бит,
  • CKM_RSA_PKCS_KEY_PAIR_GEN для генерации ключевой пары RSA,
  • CKM_EC_KEY_PAIR_GEN для генерации ключевой пары ECDSA.

Пример генерации ключевой пары ГОСТ Р 34.10-2012

Предварительно должна быть открыта сессия чтения/записи с авторизацией с правами пользователя Рутокен. 

Генерация ключевой пары ГОСТ Р 34.10-2012
/* Вычисление размера массива */
#define 		 arraysize(a)   (sizeof(a)/sizeof(a[0]))
 
CK_MECHANISM     gostR3410_256KeyPairGenMech = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL_PTR, 0 }; 		// Механизм генерации ключевой пары ГОСТ Р 34.10-2012(256)
CK_MECHANISM 	 gostR3410_512KeyPairGenMech = { CKM_GOSTR3410_512_KEY_PAIR_GEN, NULL_PTR, 0 };  	// Механизм генерации ключевой пары ГОСТ Р 34.10-2012(512) */


CK_OBJECT_HANDLE hPublicKey_256 = NULL_PTR;		// Хэндл открытого ключа
CK_OBJECT_HANDLE hPrivateKey_256 = NULL_PTR;	// Хэндл закрытого ключа
CK_OBJECT_HANDLE hPublicKey_512 = NULL_PTR;		// Хэндл открытого ключа
CK_OBJECT_HANDLE hPrivateKey_512 = NULL_PTR;	// Хэндл закрытого ключа

...

printf("Generating 256 bit key pair");
rv = pFunctionList->C_GenerateKeyPair(hSession,	// Хэндл открытой сессии
	&gostR3410_256KeyPairGenMech,				// Используемый механизм генерации ключевой пары 
	GOST34_10_2012_256_PublicKey,				// Шаблон открытого ключа
	arraysize(GOST34_10_2012_256_PublicKey),	// Размер шаблона открытого ключа
	GOST34_10_2012_256_PrivateKey,				// Шаблон закрытого ключа 
	arraysize(GOST34_10_2012_256_PrivateKey),	// Размер шаблона закрытого ключа
	&hPublicKey_256,							// Хэндл открытого ключа
	&hPrivateKey_256);							// Хэндл закрытого ключа

if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");

printf("Generating 512 bit key pair");
rv = pFunctionList->C_GenerateKeyPair(hSession,	// Хэндл открытой сессии
	&gostR3410_512KeyPairGenMech,				// Используемый механизм генерации ключевой пары 
	GOST34_10_2012_512_PublicKey,				// Шаблон открытого ключа
	arraysize(GOST34_10_2012_512_PublicKey),	// Размер шаблона открытого ключа
	GOST34_10_2012_512_PrivateKey,				// Шаблон закрытого ключа 
	arraysize(GOST34_10_2012_512_PrivateKey),	// Размер шаблона закрытого ключа
	&hPublicKey_512,							// Хэндл открытого ключа
	&hPrivateKey_512);							// Хэндл закрытого ключа

if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");

Примеры шаблонов ключей ECDSA

Ниже представлены примеры шаблонов закрытого и открытого ключа ECDSA ключа

Шаблоны открытого и закрытого ключа ECDSA
CK_OBJECT_CLASS publicKeyObject = CKO_PUBLIC_KEY;
CK_OBJECT_CLASS privateKeyObject = CKO_PRIVATE_KEY;

/* Метка открытого ключа ECDSA */
CK_UTF8CHAR publicKeyLabelEcdsa[] = { "Sample ECDSA Public Key (Aktiv Co.)" };

/* Метка закрытого ключа ECDSA */
CK_UTF8CHAR privateKeyLabelEcdsa[] = { "Sample ECDSA Private Key (Aktiv Co.)" };

/* ID пары ключей ECDSA */
CK_BYTE keyPairIdEcdsa[] = { "ECDSA sample key pair ID (Aktiv Co.)" };

CK_KEY_TYPE keyTypeEcdsa = CKK_EC;
CK_BBOOL attributeTrue = CK_TRUE;
CK_BBOOL attributeFalse = CK_FALSE;

// Набор параметров для EC
CK_BYTE secp256k1Oid[] = { 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A };


/*************************************************************************
* Шаблон для генерации открытого ключа ECDSA                             *
*************************************************************************/
CK_ATTRIBUTE publicKeyTemplate[] =
{
	{ CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject)},              // Класс - открытый ключ
	{ CKA_LABEL, &publicKeyLabelEcdsa, sizeof(publicKeyLabelEcdsa) - 1 }, // Метка ключа
	{ CKA_ID, &keyPairIdEcdsa, sizeof(keyPairIdEcdsa) - 1 },              // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
	{ CKA_KEY_TYPE, &keyTypeEcdsa, sizeof(keyTypeEcdsa) },                // Тип ключа - ECDSA
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                  // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse)},              // Ключ доступен без аутентификации на токене
	{ CKA_EC_PARAMS, secp256k1Oid, sizeof(secp256k1Oid) }                 // Параметры используемой эллиптической кривой
};

/*************************************************************************
* Шаблон для генерации закрытого ключа ECDSA                             *
*************************************************************************/
CK_ATTRIBUTE privateKeyTemplate[] =
{
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},              // Класс - закрытый ключ
	{ CKA_LABEL, &privateKeyLabelEcdsa, sizeof(privateKeyLabelEcdsa) - 1 }, // Метка ключа
	{ CKA_ID, &keyPairIdEcdsa, sizeof(keyPairIdEcdsa) - 1 },                // Идентификатор ключевой пары #1 (должен совпадать у открытого и закрытого ключей)
	{ CKA_KEY_TYPE, &keyTypeEcdsa, sizeof(keyTypeEcdsa) },                  // Тип ключа - ECDSA
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                    // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)}                   // Ключ доступен только после аутентификации на токене
};

Пример генерации ключевой пары ECDSA

Генерация ключевой пары ECDSA
/* Вычисление размера массива */
#define 		 arraysize(a)   (sizeof(a)/sizeof(a[0]))

CK_MECHANISM ecdsaKeyPairGenMech = { CKM_EC_KEY_PAIR_GEN, NULL_PTR, 0 };

CK_OBJECT_HANDLE hPublicKey_Ecdsa = NULL_PTR;		// Хэндл открытого ключа
CK_OBJECT_HANDLE hPrivateKey_Ecdsa = NULL_PTR;	// Хэндл закрытого ключа

/*************************************************************************
* Сгенерировать ключевую пару                                            *
*************************************************************************/
printf("\nGenerating ECDSA key pair...\n");

rv = pFunctionList->C_GenerateKeyPair(hSession, &ecdsaKeyPairGenMech,
	publicKeyTemplate, arraysize(publicKeyTemplate),
	privateKeyTemplate, arraysize(privateKeyTemplate),
	&hPublicKey_Ecdsa, &hPrivateKey_Ecdsa);

if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");


Генерация секретного ключа

Атрибуты ключевых объектов

Все поддерживаемые устройствами Рутокен атрибуты объектов представлены в разделе Объекты секретных ключей

Поддерживаемые типы ключей

Устройства Рутокен поддерживают следующие типы секретных ключей (CK_KEY_TYPE

  • CKK_GENERIC_SECRET для абстрактных ключей произвольной длины,

  • CKK_GOST28147 для ключей ГОСТ 28147-89,
  • CKK_MAGMA для использования в алгоритмах шифрования ГОСТ 34.12-2018 (ГОСТ Р 34.12-2015) с длиной блока 64 бит,
  • CKK_KUZNECHIK для использования в алгоритмах шифрования ГОСТ 34.12-2018 (ГОСТ Р 34.12-2015) с длиной блока 128 бит,
  • CKK_MAGMA_TWIN_KEY для использования в алгоритме экспорта и импорта ключей Р 1323565.1.017-2018, построенном на основании блочного шифра «Магма»,

  • CKK_KUZNECHIK_TWIN_KEY для использования в алгоритме экспорта и импорта ключей Р 1323565.1.017-2018, построенном на основании блочного шифра «Кузнечик»,

Примеры шаблона секретного ключа

Шаблон секретного ключа ГОСТ 28147-89
CK_OBJECT_CLASS ocSecKey 		= CKO_SECRET_KEY; 
CK_UTF8CHAR 	SecKeyLabel[] 	= {"GOST Secret Key"}; 
CK_BYTE 		SecKeyID[] 		= {"GOST Secret Key"}; 
CK_KEY_TYPE 	KeyType 		= CKK_GOST28147; 
CK_BBOOL 		bTrue 			= CK_TRUE; 
CK_BBOOL 		bFalse 			= CK_FALSE; 
 
/* Набор параметров КриптоПро A алгоритма ГОСТ 28147-89 */
CK_BYTE 	GOST28147params[]	= { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x01 }; 
  
CK_ATTRIBUTE attrGOST28147_89SecKey[] =
{
	{ CKA_CLASS, &ocSecKey, sizeof(ocSecKey)},              // Объект секретного ключа ГОСТ 28147-89
	{ CKA_LABEL, &SecKeyLabel, sizeof(SecKeyLabel) - 1},    // Метка ключа
	{ CKA_ID, &SecKeyID, sizeof(SecKeyID) - 1},             // Идентификатор ключа
	{ CKA_KEY_TYPE, &KeyType, sizeof(KeyType)},       		// Тип ключа
	{ CKA_ENCRYPT, &bTrue, sizeof(bTrue)},                  // Ключ предназначен для зашифрования
	{ CKA_DECRYPT, &bTrue, sizeof(bTrue)},                  // Ключ предназначен для расшифрования
	{ CKA_TOKEN, &bTrue, sizeof(bTrue)},                    // Ключ является объектом токена
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)},                // Ключ доступен без авторизации на токене
	{ CKA_GOST28147_PARAMS, GOST28147params, sizeof(GOST28147params)} // Параметры алгоритма 
};
Шаблоны секретных ключей для ГОСТ Р 34.12-2015
CK_OBJECT_CLASS secretKeyObject = CKO_SECRET_KEY;
CK_UTF8CHAR secretKeyKuznechikLabel[] = { "Sample Kuznechik Secret Key (Aktiv Co.)" };
CK_UTF8CHAR secretKeyMagmaLabel[] = { "Sample Magma Secret Key (Aktiv Co.)" };
CK_BYTE secretKeyKuznechikId[] = { "Kuznechik Secret Key ID (Aktiv Co.)" };
CK_BYTE secretKeyMagmaId[] = { "Magma Secret Key ID (Aktiv Co.)" };
CK_KEY_TYPE keyTypeKuznechik = CKK_KUZNECHIK;
CK_KEY_TYPE keyTypeMagma = CKK_MAGMA;
CK_BBOOL attributeTrue = CK_TRUE;
CK_BBOOL attributeFalse = CK_FALSE;

/*************************************************************************
* Шаблон для генерации симметричного ключа Кузнечик для шифрования       *
*************************************************************************/
CK_ATTRIBUTE kuznechikKeyTemplate[] =
{
	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },                     // Класс - секретный ключ
	{ CKA_LABEL, &secretKeyKuznechikLabel, sizeof(secretKeyKuznechikLabel) - 1 }, // Метка ключа
	{ CKA_ID, &secretKeyKuznechikId, sizeof(secretKeyKuznechikId) - 1 },          // Идентификатор ключа
	{ CKA_KEY_TYPE, &keyTypeKuznechik, sizeof(keyTypeKuznechik) },                // Тип ключа - Кузнечик
	{ CKA_ENCRYPT, &attributeTrue, sizeof(attributeTrue) },                       // Ключ предназначен для зашифрования
	{ CKA_DECRYPT, &attributeTrue, sizeof(attributeTrue) },                       // Ключ предназначен для расшифрования
	{ CKA_SIGN, &attributeFalse, sizeof(attributeFalse) },                          // Ключ предназначен для вычисления MAC
	{ CKA_VERIFY, &attributeFalse, sizeof(attributeFalse) },                        // Ключ предназначен для проверки MAC
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },                         // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue) },                       // Ключ недоступен без аутентификации на токене
};

/*************************************************************************
* Шаблон для генерации симметричного ключа Магма для шифрования          *
*************************************************************************/
CK_ATTRIBUTE magmaKeyTemplate[] =
{
	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },             // Класс - секретный ключ
	{ CKA_LABEL, &secretKeyMagmaLabel, sizeof(secretKeyMagmaLabel) - 1 }, // Метка ключа
	{ CKA_ID, &secretKeyMagmaId, sizeof(secretKeyMagmaId) - 1 },          // Идентификатор ключа
	{ CKA_KEY_TYPE, &keyTypeMagma, sizeof(keyTypeMagma) },                // Тип ключа - Магма
	{ CKA_ENCRYPT, &attributeTrue, sizeof(attributeTrue) },               // Ключ предназначен для зашифрования
	{ CKA_DECRYPT, &attributeTrue, sizeof(attributeTrue) },               // Ключ предназначен для расшифрования
	{ CKA_SIGN, &attributeFalse, sizeof(attributeFalse) },                  // Ключ предназначен для вычисления MAC
	{ CKA_VERIFY, &attributeFalse, sizeof(attributeFalse) },                // Ключ предназначен для проверки MAC
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },                 // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue) },               // Ключ недоступен без аутентификации на токене
};

Поддерживаемые механизмы генерации ключей

Устройства Рутокен поддерживают следующие механизмы генерации секретного ключа:

  • CKM_GOST28147_KEY_GEN для генерации секретного ключа ГОСТ 28147-89 (библиотекой rtPKCS11ECP),
  • CKM_GOST_KEY_GEN для генерации секретного ключа ГОСТ 28147-89 (библиотекой rtPKCS11).
  • CKM_KUZNECHIK_KEY_GEN для генерации секретного ключа ГОСТ 34.12-2018 (ГОСТ Р 34.12-2015), используемого в алгоритмах шифрования с длиной блока 64 бит.
  • CKM_MAGMA_KEY_GEN для генерации секретного ключа ГОСТ 34.12-2018 (ГОСТ Р 34.12-2015), используемого в алгоритмах шифрования с длиной блока 128 бит.

Пример генерации секретного ключа

Для генерации секретного ключа предназначена функция C_GenerateKey(), в которую передается механизм генерации и шаблон ключа.

Предварительно должна быть открыта сессия чтения/записи с авторизацией с правами Пользователя. 

Генерация симметричного ключа ГОСТ 28147-89
/* Вычисление размера массива */
#define 		 arraysize(a)   (sizeof(a)/sizeof(a[0]))
 
CK_MECHANISM     KeyGenMech	 = {CKM_GOST28147_KEY_GEN, NULL_PTR, 0}; // Генерация ключа ГОСТ 28147-89. Для генерации секретных ключей Кузнечик и Магма нужно использовать механизмы CKM_KUZNECHIK_KEY_GEN и CKM_MAGMA_KEY_GEN
 
CK_OBJECT_HANDLE hSecKey	 = NULL_PTR;    						// Хэндл cекретного ключа
 
...
printf("\n Generating key");
rv = pFunctionList->C_GenerateKey(hSession,                             // Хэндл открытой сессии
			                      &KeyGenMech,              			// Используемый механизм генерации ключа
			                      attrGOST28147_89SecKey,               // Шаблон для создания секретного ключа
			                      arraysize(attrGOST28147_89SecKey),    // Размер шаблона секретного ключа
			                      &hSecKey);                   			// Хэндл секретного ключа
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");

Выработка сеансового симметричного ключа 

Функция C_DeriveKey() позволяет получить одинаковый симметричный ключ для сеансовой связи на каждой из сторон, имея закрытый ключ одной стороны и открытый ключ другой стороны.

Устройства Рутокен поддерживают следующие механизмы согласования ключей:

  • CKM_GOSTR3410_DERIVE для алгоритма VKO GOST R 34.10-2001;
  • CKM_GOSTR3410_12_DERIVE для алгоритма VKO GOST R 34.10-2012 (256 и 512 бит).
  • CKM_KDF_TREE_GOSTR3411_2012_256 для алгоритма KDF_TREE_GOSTR3411_2012_256
  • CKM_VENDOR_GOST_KEG для алгоритма Keg определенного в стандарте ГОСТ Р 1323565.1.020-2018

 VKO GOST R 34.10-2001

Выработанный общий ключ согласно VKO GOST R 34.10-2001 может быть возвращен в одном из следующих форматов:

  • не диверсифицированный Key Encryption Key (KEK)
  • KEK, диверсифицированный по RFC-4357, п.6.5.  

Параметры механизма CKM_GOSTR3410_DERIVE задаются структурой CK_GOSTR3410_DERIVE_PARAMS, которая имеет следующие поля:


структура CK_GOSTR3410_DERIVE_PARAMS
typedef struct CK_GOSTR3410_DERIVE_PARAMS{
	CK_EC_KDF_TYPE kdf;
	CK_BYTE_PTR pPublicData;
	CK_ULONG ulPublicDataLen;
	CK_BYTE_PTR pUKM;
	CK_ULONG ulUKMLen;
} CK_GOSTR3410_DERIVE_PARAMS;


  1. kdf – идентификатор механизма диверсификации. Может быть использован один из двух механизмов:
    • CKD_NULL - нет диверсификации
    • CKD_CPDIVERSIFY_KDF
  2. pPublicData – открытый ключ получателя
  3. pUKM – синхропосылка

VKO GOST R  34.10-2012 (256 бит и 512 бит)

Выработанный общий ключ согласно VKO GOST R  34.10-2012 может быть возвращен только в формате не диверсифицированного Key Encryption Key (KEK).

Параметры механизма CKM_GOSTR3410_12_DERIVE задаются байтовым массивом, который имеет следующую структуру:

  1.  4 байта (little-endian, т.е. младшие байты сначала) представляют собой значение KDF. Значение определяет механизм диверсификации:
    • CKD_NULL - нет диверсификации
    • CKM_KDF_4357
    • CKD_CPDIVERSIFY_KDF
    • CKM_KDF_GOSTR3411_2012_256
  2. 4 байта (little-endian) задают длину открытого ключа в байтах. (для 256 бит – это 64. Для 512 бит – 128)
  3. открытый ключ (n-байтовый вектор в little-endian), длина которого определена предыдущим полем
  4. 4 байта (little-endian) задают длину UKM (8 байт)
  5. UKM (n-байтовый вектор в little-endian) , длина определена выше.

KDF_TREE_GOSTR3411_2012_256

Механизм CKM_KDF_TREE_GOSTR3411_2012_256 позволяет вырабатывать секретные и двойственные ключи из уже имеющегося секретного ключа. Этот механизм может применяется к ключу, выработанному с помощью алгоритма VKO.

Механизм может применяться к ключам шифрования CKK_GOST28147, CKK_KUZNYECHIK, CKK_MAGMA и CKK_GENERIC_SECRET.

Результатом работы этого механизма могут быть ключи типа CKK_GOST28147, CKK_KUZNYECHIK, CKK_MAGMA, CKK_GENERIC_SECRET, CKK_KUZNYECHIK_TWIN_KEY и CKK_MAGMA_TWIN_KEY.

Для задания параметров механизма используется структура CK_KDF_TREE_GOST_PARAMS :


структура CK_KDF_TREE_GOST_PARAMS
typedef struct CK_KDF_TREE_GOST_PARAMS {
	CK_ULONG ulLabelLength;
	CK_BYTE_PTR pLabel;
	CK_ULONG ulSeedLength;
	CK_BYTE_PTR pSeed;
    CK_ULONG ulR;
    CK_ULONG ulL;
    CK_ULONG ulOffset;
} CK_KDF_TREE_GOST_PARAMS;


  1. pLabel, pSeed – строки задающие параметры label и seed.
  2. ulR –  количество байт в счетчике итераций, с возможными значениями 1, 2, 3, 4.
  3. ulL – необходимая байтовая длина вырабатываемого ключевого материала.
  4. ulOffset – байтовое смещение, в последовательности ключевого материала, начиная с которого полученные байты используются для получения диверсифицированного ключа.

CKM_VENDOR_GOST_KEG

Механизм CKM_VENDOR_GOST_KEG позволяет вырабатывать двойственный ключ из закрытого ключа отправителя и открытого ключа получателя.

Механизм может применяться к ключам ГОСТ Р 34.10-2012 с длиной ключа 256 и 512 бит.

Результатом работы этого механизма могут быть ключи типа CKK_KUZNYECHIK_TWIN_KEY и CKK_MAGMA_TWIN_KEY.

Для задания параметров механизма используется структура CK_VENDOR_GOST_KEG_PARAMS:


структура CK_VENDOR_GOST_KEG_PARAMS
typedef struct CK_VENDOR_GOST_KEG_PARAMS {
	CK_BYTE_PTR pPublicData;
	CK_ULONG ulPublicDataLen;
	CK_BYTE_PTR pUKM;
	CK_ULONG ulUKMLen;
} CK_VENDOR_GOST_KEG_PARAMS;


  1. pPublicData – открытый ключ получателя
  2. pUKM – синхропосылка.

Пример выработки общего ключа парной связи по алгоритму VKO GOST R 34.10-2012

Выработка общего ключа парной связи по схеме ключевого обмена VKO GOST 34.10-2001 на стороне отправителя
/* Размер синхропосылки в байтах */
#define UKM_LENGTH					8

#define DERIVE_PARAMS_256_LENGTH	84

/*************************************************************************
* Параметры для выработки ключа обмена по схеме VKO GOST R 34.10-2012-256*
* Содержат в себе данные по диверсификации ключа, открытый ключ и UKM    *
*************************************************************************/
CK_BYTE deriveParameters2012_256[DERIVE_PARAMS_256_LENGTH] = { 0x00, };

const CK_ULONG keyLengthOffset = 4;       // Смещение длины ключа в массиве
const CK_ULONG publicKeyValueOffset = 8;  // Смещение значения ключа в массиве
const CK_ULONG ukmLengthOffset = 72;      // Смещение длины UKM в массиве
const CK_ULONG ukmDataOffset = 76;        // Смещение UKM в массиве

/*************************************************************************
* Функция записи четырёхбайтного значения длины в буфер                  *
*************************************************************************/
void ulongToBuffer(CK_BYTE_PTR buffer, CK_ULONG value)
{
	buffer[0] = value & 0xFF;
	buffer[1] = (value >> 8) & 0xFF;
	buffer[2] = (value >> 16) & 0xFF;
	buffer[3] = (value >> 24) & 0xFF;
}

/* Механизм выработки ключа обмена по алгоритму VKO GOST R 34.10-2012 */
CK_MECHANISM gostR3410_12DerivationMech = { CKM_GOSTR3410_12_DERIVE, NULL_PTR, 0 };

/* Значение открытого ключа получателя */
CK_BYTE cbPubRecipientKey[] = { 0xFF, 0x8D, 0xAB, 0x7F, 0x1C, 0x0B, 0x74, 0xA5, 0xAD, 0x7F, 0x0B, 0x5F, 0x8D, 0x5B, 0x3C, 0x44,
								0x58, 0x37, 0x98, 0xC9, 0x25, 0x86, 0x40, 0x7E, 0xEC, 0x6E, 0xAF, 0x00, 0xCB, 0x44, 0x65, 0xA5,
								0x22, 0x9A, 0x53, 0x56, 0x32, 0x97, 0x35, 0x80, 0x99, 0xCA, 0x1E, 0x17, 0x21, 0x3A, 0x96, 0x0E,
								0x21, 0xFB, 0xC6, 0x0F, 0x25, 0x5B, 0x5D, 0x99, 0x4E, 0xC4, 0x5C, 0x42, 0x08, 0x7D, 0x06, 0x04 };

CK_OBJECT_CLASS ocSecKey = CKO_SECRET_KEY;
CK_UTF8CHAR 	DerivedKeyLabel[] = { "Derived Key" };
CK_BYTE 		SecKeyID[] = { "GOST Secret Key" };
CK_KEY_TYPE 	KeyType = CKK_GOST28147;
CK_BBOOL 		bTrue = CK_TRUE;
CK_BBOOL 		bFalse = CK_FALSE;

/* Шаблон для создания общего ключа */
CK_ATTRIBUTE attrGOST28147DerivedKey[] =
{
	{ CKA_CLASS, &ocSecKey, sizeof(ocSecKey)},                      // Объект секретного ключа ГОСТ 28147-89
	{ CKA_LABEL, &DerivedKeyLabel, sizeof(DerivedKeyLabel) - 1},    // Метка ключа
	{ CKA_KEY_TYPE, &KeyType, sizeof(KeyType)},               		// Тип ключа
	{ CKA_TOKEN, &bFalse, sizeof(bFalse)},                          // Ключ является объектом сессии
	{ CKA_MODIFIABLE, &bTrue, sizeof(bTrue)},                       // Ключ может быть изменен после создания
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)},                        // Ключ доступен без авторизации
	{ CKA_EXTRACTABLE, &bTrue, sizeof(bTrue)},                      // Ключ может быть извлечен и зашифрован
	{ CKA_SENSITIVE, &bFalse, sizeof(bFalse)}                       // Ключ не может быть извлечен в открытом виде
};

CK_ATTRIBUTE 		attrDerivedKeyValue = { CKA_VALUE, NULL_PTR, 0 }; 	// Структура данных типа CK_ATTRIBUTE для хранения значения атрибута CKA_VALUE
CK_BYTE 			ukm[UKM_LENGTH];									// Буфер, содержащий UKM
CK_OBJECT_HANDLE 	hDerivedKey = NULL_PTR;           					// Хэндл выработанного общего ключа
CK_OBJECT_HANDLE 	hObject;											// Хэндл объекта

...

/*************************************************************************
* Установить параметры в структуре типа CK_GOSTR3410_DERIVE_PARAMS       *
* для выработки ключа обмена                                             *
*************************************************************************/
rv = pFunctionList->C_GenerateRandom(hSession, ukm, sizeof(ukm));
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}


/*************************************************************************
* Поместить в структуру типа CK_MECHANISM параметры, необходимые         *
* для выработки ключа обмена                                             *
*************************************************************************/
ulongToBuffer(deriveParameters2012_256, CKM_KDF_GOSTR3411_2012_256);
ulongToBuffer(deriveParameters2012_256 + keyLengthOffset, sizeof(cbPubRecipientKey));
memcpy(deriveParameters2012_256 + publicKeyValueOffset, cbPubRecipientKey, sizeof(cbPubRecipientKey));
ulongToBuffer(deriveParameters2012_256 + ukmLengthOffset, sizeof(ukm));
memcpy(deriveParameters2012_256 + ukmDataOffset, ukm, sizeof(ukm));
gostR3410_12DerivationMech.pParameter = deriveParameters2012_256;
gostR3410_12DerivationMech.ulParameterLen = sizeof(deriveParameters2012_256);

/* Выработать общий ключ ГОСТ 28147-89 на основании закрытого ключа отправителя и открытого ключа получателя */
printf("C_DeriveKey");
rv = pFunctionList->C_DeriveKey(hSession,	// Хэндл открытой с правами Пользователя сессии 
	&gostR3410_12DerivationMech,  			// Механизм ключевого обмена
	hPrivateKey,							// Хэндл закрытого ключа отправителя
	attrGOST28147DerivedKey,				// Шаблон создания общего ключа
	arraysize(attrGOST28147DerivedKey), 	// Размер шаблона
	&hDerivedKey);						// Хэндл общего выработанного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}
printf(" -> OK\n");

/* Получить размер буфера для хранения значения атрибута CKA_VALUE*/
printf("Getting object value size");
rv = pFunctionList->C_GetAttributeValue(hSession,	// Хэндл открытой с правами Пользователя сессии 
	hDerivedKey,									// Хэндл общего ключа
	&attrDerivedKeyValue,							// Шаблон получения значения атрибута
	1);												// Количество атрибутов в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}
printf(" -> OK\n");

/* Выделить необходимое количество памяти для значения атрибута */
attrDerivedKeyValue.pValue = (CK_BYTE*)malloc(attrDerivedKeyValue.ulValueLen);
if (attrDerivedKeyValue.pValue == NULL)
{
	printf("Memory allocation for attrDerivedKeyValue failed! \n");
	goto exit;
}
memset(attrDerivedKeyValue.pValue,
	0,
	(attrDerivedKeyValue.ulValueLen * sizeof(CK_BYTE)));

/* Получить значение общего ключа ГОСТ 28147-89 */
printf("Getting object value");
rv = pFunctionList->C_GetAttributeValue(hSession,	// Хэндл открытой с правами Пользователя сессии 
	hDerivedKey,									// Хэндл общего ключа
	&attrDerivedKeyValue,							// Шаблон получения значения атрибута
	1);												// Количество атрибутов в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}
printf(" -> OK\n");
/* Распечатать буфер со значением общего ключа ГОСТ 28147-89 */
printf("Derived key data is:\n");
for (size_t i = 0; i < attrDerivedKeyValue.ulValueLen; i++)
{
	printf("%02X ", ((CK_BYTE_PTR) attrDerivedKeyValue.pValue)[i]);
	if ((i + 1) % 8 == 0)
		printf("\n");
}

exit:
if (attrDerivedKeyValue.pValue)
{
	free(attrDerivedKeyValue.pValue);
	attrDerivedKeyValue.pValue = NULL_PTR;
	attrDerivedKeyValue.ulValueLen = 0;
}

if (rv != CKR_OK)
{
	pFunctionList->C_DestroyObject(hSession,
		hDerivedKey);
	hDerivedKey = NULL_PTR;
}
if (rv != CKR_OK)
	printf("\nDeriving failed!\n\n");
else
	printf("Deriving has been completed successfully.\n\n");

Пример выработки двойственного ключа по алгоритму KEG

Выработка двойственного ключа по алгоритму KEG
/* Размер синхропосылки в байтах */
#define UKM_KEG_LENGTH				24

CK_UTF8CHAR derivedKuznechikTwinKeyLabel[] = { "Derived Kuznechik twin key" };
CK_UTF8CHAR derivedMagmaTwinKeyLabel[] = { "Derived Magma twin key" };
CK_OBJECT_CLASS secretKeyObject = CKO_SECRET_KEY;
CK_KEY_TYPE keyTypeKuznechikTwin = CKK_KUZNECHIK_TWIN_KEY;
CK_KEY_TYPE keyTypeMagmaTwin = CKK_MAGMA_TWIN_KEY;
CK_BBOOL attributeTrue = CK_TRUE;
CK_BBOOL attributeFalse = CK_FALSE;


/* Значение открытого ключа получателя */
CK_BYTE cbPubRecipientKey[] = { 0xFF, 0x8D, 0xAB, 0x7F, 0x1C, 0x0B, 0x74, 0xA5, 0xAD, 0x7F, 0x0B, 0x5F, 0x8D, 0x5B, 0x3C, 0x44,
								0x58, 0x37, 0x98, 0xC9, 0x25, 0x86, 0x40, 0x7E, 0xEC, 0x6E, 0xAF, 0x00, 0xCB, 0x44, 0x65, 0xA5,
								0x22, 0x9A, 0x53, 0x56, 0x32, 0x97, 0x35, 0x80, 0x99, 0xCA, 0x1E, 0x17, 0x21, 0x3A, 0x96, 0x0E,
								0x21, 0xFB, 0xC6, 0x0F, 0x25, 0x5B, 0x5D, 0x99, 0x4E, 0xC4, 0x5C, 0x42, 0x08, 0x7D, 0x06, 0x04 };

/*************************************************************************
* Шаблон для создания двойственного ключа экспорта типа Кузнечик         *
*************************************************************************/
CK_ATTRIBUTE derivedTwinKeyTemplate[] =
{
	{ CKA_LABEL, &derivedKuznechikTwinKeyLabel, sizeof(derivedKuznechikTwinKeyLabel) - 1},  // Метка ключа
	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },								// Класс - секретный ключ
	{ CKA_KEY_TYPE, &keyTypeKuznechikTwin, sizeof(keyTypeKuznechikTwin)},					// Тип ключа - двойственный Кузнечик
	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},									// Ключ является объектом сессии
	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},								// Ключ может быть изменен после создания
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},									// Ключ доступен только после аутентификации на токене
	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},								// Ключ может быть извлечен в зашифрованном виде
	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}								// Ключ может быть извлечен в открытом виде
};

/*************************************************************************
* Шаблон для создания двойственного ключа экспорта типа Магма            *
*************************************************************************/
//CK_ATTRIBUTE derivedTwinKeyTemplate[] =
//{
//	{ CKA_LABEL, &derivedMagmaTwinKeyLabel, sizeof(derivedMagmaTwinKeyLabel) - 1},          // Метка ключа
//	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },								// Класс - секретный ключ
//	{ CKA_KEY_TYPE, &keyTypeMagmaTwin, sizeof(keyTypeMagmaTwin)},							// Тип ключа - двойственный Магма
//	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},									// Ключ является объектом сессии
//	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},								// Ключ может быть изменен после создания
//	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},									// Ключ доступен только после аутентификации на токене
//	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},								// Ключ может быть извлечен в зашифрованном виде
//	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}								// Ключ может быть извлечен в открытом виде
//};

CK_MECHANISM gostKegDerifivationMech = { CKM_VENDOR_GOST_KEG, NULL_PTR, 0 };
CK_VENDOR_GOST_KEG_PARAMS keg256DeriveParams;
CK_ATTRIBUTE 		attrDerivedKeyValue = { CKA_VALUE, NULL_PTR, 0 }; 	// Структура данных типа CK_ATTRIBUTE для хранения значения атрибута CKA_VALUE
CK_BYTE 			ukm[UKM_KEG_LENGTH];								// Буфер, содержащий UKM
CK_OBJECT_HANDLE 	hDerivedKey = NULL_PTR;           					// Хэндл выработанного общего ключа
CK_OBJECT_HANDLE 	hObject;											// Хэндл объекта

...

/*************************************************************************
* Установить параметры в структуре типа CK_VENDOR_GOST_KEG_PARAMS        *
* для выработки двлйственного ключа                                      *
*************************************************************************/
rv = pFunctionList->C_GenerateRandom(hSession, ukm, sizeof(ukm));
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}


/*************************************************************************
* Поместить в структуру типа CK_MECHANISM параметры, необходимые         *
* для выработки ключа обмена                                             *
*************************************************************************/

keg256DeriveParams.pPublicData = cbPubRecipientKey;
keg256DeriveParams.ulPublicDataLen = sizeof(cbPubRecipientKey);
keg256DeriveParams.pUKM = ukm;
keg256DeriveParams.ulUKMLen = sizeof(ukm);

gostKegDerifivationMech .pParameter = &keg256DeriveParams;
gostKegDerifivationMech .ulParameterLen = sizeof(keg256DeriveParams);

/* Выработать общий двойственный ключ на основании закрытого ключа отправителя и открытого ключа получателя */
printf("C_DeriveKey");
rv = pFunctionList->C_DeriveKey(hSession,	// Хэндл открытой с правами Пользователя сессии 
	&gostKegDerifivationMech,  			// Механизм ключевого обмена
	hPrivateKey,							// Хэндл закрытого ключа отправителя
	derivedTwinKeyTemplate,					// Шаблон создания общего ключа
	arraysize(derivedTwinKeyTemplate), 		// Размер шаблона
	&hDerivedKey);							// Хэндл общего выработанного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}
printf(" -> OK\n");

/* Получить размер буфера для хранения значения атрибута CKA_VALUE*/
printf("Getting object value size");
rv = pFunctionList->C_GetAttributeValue(hSession,	// Хэндл открытой с правами Пользователя сессии 
	hDerivedKey,									// Хэндл общего ключа
	&attrDerivedKeyValue,							// Шаблон получения значения атрибута
	1);												// Количество атрибутов в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}
printf(" -> OK\n");

/* Выделить необходимое количество памяти для значения атрибута */
attrDerivedKeyValue.pValue = (CK_BYTE*)malloc(attrDerivedKeyValue.ulValueLen);
if (attrDerivedKeyValue.pValue == NULL)
{
	printf("Memory allocation for attrDerivedKeyValue failed! \n");
	goto exit;
}
memset(attrDerivedKeyValue.pValue,
	0,
	(attrDerivedKeyValue.ulValueLen * sizeof(CK_BYTE)));

/* Получить значение общего двойственного ключа */
printf("Getting object value");
rv = pFunctionList->C_GetAttributeValue(hSession,	// Хэндл открытой с правами Пользователя сессии 
	hDerivedKey,									// Хэндл общего ключа
	&attrDerivedKeyValue,							// Шаблон получения значения атрибута
	1);												// Количество атрибутов в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto exit;
}
printf(" -> OK\n");
/* Распечатать буфер со значением общего двойственного ключа */
printf("Derived key data is:\n");
for (size_t i = 0; i < attrDerivedKeyValue.ulValueLen; i++)
{
	printf("%02X ", ((CK_BYTE_PTR)attrDerivedKeyValue.pValue)[i]);
	if ((i + 1) % 8 == 0)
		printf("\n");
}

exit:
if (attrDerivedKeyValue.pValue)
{
	free(attrDerivedKeyValue.pValue);
	attrDerivedKeyValue.pValue = NULL_PTR;
	attrDerivedKeyValue.ulValueLen = 0;
}

if (rv != CKR_OK)
{
	pFunctionList->C_DestroyObject(hSession,
		hDerivedKey);
	hDerivedKey = NULL_PTR;
}
if (rv != CKR_OK)
printf("\nDeriving failed!\n\n");
else
printf("Deriving has been completed successfully.\n\n");

Маскирование секретного ключа

Для получения сеансового ключ (CEK), зашифрованного на KEK с синхропосылкой CEK, необходимо после получения ключа функцией C_DeriveKey() использовать функцию маскирования C_WrapKey().

Для выработки сеансового ключ (CEK) в оперативной памяти токена, зашифрованного на KEK с синхропосылкой  CEK, необходимо использовать функцию расширения C_EX_WrapKey().

Для маскирования (шифрования) симметричного ключа используются функция C_WrapKey() для маскирования и C_UnwrapKey() для обратной процедуры. 

В этом примере мы генерируем случайным образом сессионный ключ (CEK), а затем маскируем его общим ключом KEK, полученным из функции C_Derive() .

Маскирование общего выработанного ключа
/* Размер синхропосылки в байтах */
#define UKM_LENGTH			8
/* Размер симметричного ключа ГОСТ 28147-89 в байтах */
#define	GOST_28147_KEY_SIZE	0x20

/* Набор параметров КриптоПро A алгоритма ГОСТ 28147-89 */
CK_BYTE GOST28147params[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1f, 0x01 };

CK_BYTE ukm[UKM_LENGTH] = {0x01, 0x02 ,0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // Синхропосылка

/* Механизм для маскирования/демаскирования ключа */
CK_MECHANISM    ckmWrapMech = { CKM_GOST28147_KEY_WRAP, NULL_PTR, 0 };

CK_OBJECT_CLASS ocSecKey = CKO_SECRET_KEY;
CK_UTF8CHAR 	WrapKeyLabel[] = { "GOST Wrapped Key" };
CK_UTF8CHAR 	UnWrapKeyLabel[] = { "GOST Unwrapped Key" };
CK_KEY_TYPE 	KeyType = CKK_GOST28147;
CK_BBOOL 		bTrue = CK_TRUE;
CK_BBOOL 		bFalse = CK_FALSE;


/* Шаблон маскируемого ключа */
CK_ATTRIBUTE attrGOST28147KeyToWrap[] =
{
	{ CKA_CLASS, &ocSecKey, sizeof(ocSecKey)},              // Объект секретного ключа ГОСТ 28147-89
	{ CKA_LABEL, &WrapKeyLabel, sizeof(WrapKeyLabel) - 1 },	// Метка ключа
	{ CKA_KEY_TYPE, &KeyType, sizeof(KeyType)},       		// Тип ключа
	{ CKA_TOKEN, &bFalse, sizeof(bFalse)},                  // Ключ является объектом сессии
	{ CKA_MODIFIABLE, &bTrue, sizeof(bTrue)},               // Ключ может быть изменен после создания
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)},                // Ключ доступен без авторизации
	{ CKA_VALUE, NULL_PTR, 0},                              // Значение ключа
	{ CKA_EXTRACTABLE, &bTrue, sizeof(bTrue)},              // Ключ может быть извлечен и зашифрован
	{ CKA_SENSITIVE, &bFalse, sizeof(bFalse)}               // Ключ не может быть извлечен в открытом виде
};

/* Шаблон демаскированного ключа */
CK_ATTRIBUTE attrGOST28147UnwrappedKey[] =
{
	{ CKA_CLASS, &ocSecKey, sizeof(ocSecKey)},                    // Объект секретного ключа ГОСТ 28147-89
	{ CKA_LABEL, &UnWrapKeyLabel, sizeof(UnWrapKeyLabel) - 1},    // Метка ключа
	{ CKA_KEY_TYPE, &KeyType, sizeof(KeyType)},                   // Тип ключа
	{ CKA_TOKEN, &bFalse, sizeof(bFalse)},                        // Ключ является объектом сессии
	{ CKA_MODIFIABLE, &bTrue, sizeof(bTrue)},                     // Ключ может быть изменен после создания
	{ CKA_PRIVATE, &bFalse, sizeof(bFalse)},                      // Ключ доступен без авторизации
	{ CKA_EXTRACTABLE, &bTrue, sizeof(bTrue)},                    // Ключ может быть извлечен и зашифрован
	{ CKA_SENSITIVE, &bFalse, sizeof(bFalse)}                     // Ключ не может быть извлечен в открытом виде
};

/* Структура данных типа CK_ATTRIBUTE для хранения значения атрибута CKA_VALUE */
CK_ATTRIBUTE attrValue = { CKA_VALUE, NULL_PTR, 0 };

CK_OBJECT_HANDLE hDerivedKey;           			 // Хэндл выработанного общего ключа
CK_BYTE_PTR pbtSessionKey = NULL_PTR;                // Указатель на буфер, содержащий сессионный ключ
CK_BYTE_PTR pbtWrappedKey = NULL_PTR;                // Указатель на буфер, содержащий маскированный на стороне отправителя сессионный ключ
CK_ULONG ulWrappedKeySize = 0;                       // Размер буфера со значением маскированного на стороне отправителя сессионного ключа, в байтах
CK_BYTE_PTR pbtUnwrappedKey = NULL_PTR;              // Указатель на буфер, содержащий демаскированный на стороне получателя сессионный ключ
CK_ULONG ulUnwrappedKeySize = 0;                     // Размер буфера со значением демаскированного на стороне получателя сессионного ключа, в байтах
CK_OBJECT_HANDLE hTempKey = NULL_PTR;                // Хэндл ключа, который будет маскироваться/демаскироваться

...

/*Установить синхропосылку для маскирования ключа*/
ckmWrapMech.ulParameterLen = sizeof(ukm);
ckmWrapMech.pParameter = ukm;

/* Заполнить шаблон сессионного ключа случайными данными */
GenerateRandomData(GOST_28147_KEY_SIZE, &pbtSessionKey);
for (int i = 0; i < arraysize(attrGOST28147KeyToWrap); i++)
	if (attrGOST28147KeyToWrap[i].type == CKA_VALUE)
	{
		attrGOST28147KeyToWrap[i].pValue = pbtSessionKey;
		attrGOST28147KeyToWrap[i].ulValueLen = GOST_28147_KEY_SIZE;
		break;
	}

/*************************************************************************
* Маскирование ключа                                                     *
*************************************************************************/

/* Создать ключ, который будет маскирован */
printf("Creating the GOST 28147-89 key to wrap");
rv = pFunctionList->C_CreateObject(hSession,							// Хэндл сессии, открытой с правами Пользователя
	attrGOST28147KeyToWrap,				// Шаблон создаваемого ключа
	arraysize(attrGOST28147KeyToWrap),	// Размер шаблона
	&hTempKey);							// Хэндл созданного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto wrap_exit;
}
printf(" -> OK\n");

/* Получить размер буфера, содержащего значение маскированного ключа */
printf("Defining wrapping key size");
rv = pFunctionList->C_WrapKey(hSession,					// Хэндл сессии, открытой с правами Пользователя		
	&ckmWrapMech,			// Механизм маскирования
	hDerivedKey,			// Хэндл ключа, которым будет маскироваться ключ
	hTempKey,				// Хэндл ключа, который будет маскирован
	NULL_PTR,				// Указатель на буфер с маскированным ключом
	&ulWrappedKeySize);		// Размер маскированного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto wrap_exit;
}
printf(" -> OK\n");

pbtWrappedKey = (CK_BYTE*)malloc(ulWrappedKeySize);
if (pbtWrappedKey == NULL)
{
	printf("Memory allocation for pbtWrappedKey failed! \n");
	goto wrap_exit;
}
memset(pbtWrappedKey,
	0,
	ulWrappedKeySize * sizeof(CK_BYTE));

/* Получить маскированный ключ на стороне отправителя */
printf("Wrapping key");
rv = pFunctionList->C_WrapKey(hSession,					// Хэндл сессии, открытой с правами Пользователя		
	&ckmWrapMech,				// Механизм маскирования
	hDerivedKey,			// Хэндл ключа, которым будет маскироваться ключ
	hTempKey,					// Хэндл ключа, который будет маскирован
	pbtWrappedKey,			// Указатель на буфер с маскированным ключом
	&ulWrappedKeySize);		// Размер маскированного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto wrap_exit;
}
printf(" -> OK\n");

/* Распечатать буфер, содержащий маскированный ключ */
printf("Wrapped key data is:\n");
for (int i = 0; i < ulWrappedKeySize; i++)
{
	printf("%02X ", pbtWrappedKey[i]);
	if ((i + 1) % 9 == 0)
		printf("\n");
}


wrap_exit:
if (hTempKey)
{
	pFunctionList->C_DestroyObject(hSession,
		hTempKey);
	hTempKey = NULL_PTR;
}

if (rv == CKR_OK)
	printf("\nWrapping has been completed successfully.\n");
else
{
	printf("\nWrapping failed!\n");
	goto exit;
}


/*************************************************************************
* Демаскирование ключа                                                   *
*************************************************************************/
printf("Unwrapping key");
rv = pFunctionList->C_UnwrapKey(hSession,								// Хэндл сессии, открытой с правами Пользователя	
	&ckmWrapMech,							// Механизм маскирования
	hDerivedKey,							// Хэндл ключа, которым был маскирован ключ
	pbtWrappedKey,							// Указатель на буфер с маскированным ключом
	ulWrappedKeySize,						// Размер буфера с маскированным ключом
	attrGOST28147UnwrappedKey,				// Указатель на шаблон для демаскированного ключа
	arraysize(attrGOST28147UnwrappedKey),	// Размер шаблона для демаскированного ключа
	&hTempKey);								// Указатель на буфер с маскированным ключом
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto unwrap_exit;
}
printf(" -> OK\n");

/* Получить буфер со значением демаскированного ключа */
printf("Getting unwrapped key value...\n");

/* Получить размер буфера для хранения значения атрибута CKA_VALUE */
printf("Getting object value size");
rv = pFunctionList->C_GetAttributeValue(hSession,   // Хэндл сессии, открытой с правами Пользователя
	hTempKey,	// Хэндл объекта, значение атрибутов которых требуется получить 
	&attrValue, // Указатель на шаблон с атрибутами, значение которых требуется получить
	1);			// Количество строк в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto unwrap_exit;
}
printf(" -> OK\n");

/* Выделить необходимое количество памяти для значения атрибута */
attrValue.pValue = (CK_BYTE*)malloc(attrValue.ulValueLen);
if (attrValue.pValue == NULL)
{
	printf("Memory allocation for attrValue failed! \n");
	goto unwrap_exit;
}
memset(attrValue.pValue,
	0,
	(attrValue.ulValueLen * sizeof(CK_BYTE)));

/* Получить значение атрибута CKA_VALUE */
printf("Getting object value");
rv = pFunctionList->C_GetAttributeValue(hSession,   // Хэндл сессии, открытой с правами Пользователя
	hTempKey,	// Хэндл объекта, значение атрибутов которых требуется получить 
	&attrValue, // Указатель на шаблон с атрибутами, значение которых требуется получить
	1);			// Количество строк в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto unwrap_exit;
}
printf(" -> OK\n");
/* Распечатать буфер со значением демаскированного ключа  */
printf("Unwrapped key data:\n");
for (int i = 0; i < attrValue.ulValueLen; i++)
{
	printf("%02X ", ((CK_BYTE_PTR)attrValue.pValue)[i]);
	if ((i + 1) % 8 == 0)
		printf("\n");
}

unwrap_exit:
if (hTempKey)
{
	pFunctionList->C_DestroyObject(hSession,
		hTempKey);
	hTempKey = NULL_PTR;
}
if (rv == CKR_OK)
	printf("Unwrapping has been completed successfully.\n\n");
else
{
	printf("\nUnwrapping failed!\n\n");
	goto exit;
}

/* Сравнить первоначальное значение сессионного ключа со значением демаскированного ключа */
if ((ulUnwrappedKeySize != GOST_28147_KEY_SIZE)
	|| (memcmp(pbtSessionKey,
		attrValue.pValue,
		GOST_28147_KEY_SIZE) != 0))
	printf("\nThe unwrapped key is not equal to the session key!\n");
else
	printf("The unwrapped key is equal to the session key.\n");

exit:
printf("Finish");

Пример экспорта и импорта ключа по алгоритму Kexp15

Маскирование общего выработанного ключа
/* Синхропосылка для механизма импорта и экспорта ключа алгоритма KExp15 на двойственном ключе Кузнечик */
CK_BYTE gostKuznechikKExp15Ukm[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
/* Синхропосылка для механизма импорта и экспорта ключа алгоритма KExp15 на двойственном ключе Магма */
CK_BYTE gostMagmaKExp15Ukm[] = { 0x00, 0x00, 0x00, 0x00 };

/* Механизм импорта и экспорта ключа по алгоритму KExp15 на двойственном ключе Кузнечик */
CK_MECHANISM gostKuznechikKExp15Mech = { CKM_KUZNECHIK_KEXP_15_WRAP, &gostKuznechikKExp15Ukm, sizeof(gostKuznechikKExp15Ukm) };
/* Механизм импорта и экспорта ключа по алгоритму KExp15 на двойственном ключе Кузнечик */
CK_MECHANISM gostMagmaKExp15Mech = { CKM_MAGMA_KEXP_15_WRAP, &gostMagmaKExp15Ukm, sizeof(gostMagmaKExp15Ukm) };


CK_MECHANISM_PTR gostKExp15Mech = &gostKuznechikKExp15Mech;
//CK_MECHANISM gostKExp15Mech = gostMagmaKExp15Mech;

/* DEMO-метка для сессионного ключа */
CK_UTF8CHAR sessionKeyLabel[] = { "GOST 28147-89 key to wrap" };
CK_UTF8CHAR sessionKuznechikKeyLabel[] = { "Kuznechik key to wrap" };
CK_UTF8CHAR sessionMagmaKeyLabel[] = { "Magma key to wrap" };
CK_OBJECT_CLASS secretKeyObject = CKO_SECRET_KEY;
CK_KEY_TYPE keyTypeGost28147 = CKK_GOST28147;
CK_KEY_TYPE keyTypeKuznechik = CKK_KUZNECHIK;
CK_KEY_TYPE keyTypeMagma = CKK_MAGMA;
CK_BBOOL attributeTrue = CK_TRUE;
CK_BBOOL attributeFalse = CK_FALSE;

/*************************************************************************
* Шаблон сессионного ключа типа Кузнечик                                 *
*************************************************************************/
CK_ATTRIBUTE sessionKeyTemplate[] =
{
	{ CKA_LABEL, &sessionKuznechikKeyLabel, sizeof(sessionKuznechikKeyLabel) - 1 },	// Метка ключа
	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },						// Класс - секретный ключ
	{ CKA_KEY_TYPE, &keyTypeKuznechik, sizeof(keyTypeKuznechik)},					// Тип ключа - Кузнечик
	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},							// Ключ является объектом сессии
	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},						// Ключ может быть изменен после создания
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},							// Ключ доступен только после аутентификации на токене
	{ CKA_VALUE, NULL_PTR, 0},														// Значение ключа
	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},						// Ключ может быть извлечен в зашифрованном виде
	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}						// Ключ может быть извлечен в открытом виде
};

/*************************************************************************
* Шаблон сессионного ключа типа Магма                                    *
*************************************************************************/
//CK_ATTRIBUTE sessionKeyTemplate[] =
//{
//	{ CKA_LABEL, &sessionMagmaKeyLabel, sizeof(sessionMagmaKeyLabel) - 1 },// Метка ключа
//	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },              // Класс - секретный ключ
//	{ CKA_KEY_TYPE, &keyTypeMagma, sizeof(keyTypeMagma)},				   // Тип ключа - Магма
//	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},                 // Ключ является объектом сессии
//	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},              // Ключ может быть изменен после создания
//	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                 // Ключ доступен только после аутентификации на токене
//	{ CKA_VALUE, NULL_PTR, 0},                                             // Значение ключа
//	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},             // Ключ может быть извлечен в зашифрованном виде
//	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}              // Ключ может быть извлечен в открытом виде
//};

/*************************************************************************
* Шаблон сессионного ключа типа ГОСТ 28147-89                            *
*************************************************************************/
//CK_ATTRIBUTE sessionKeyTemplate[] =
//{
// 	{ CKA_LABEL, &sessionKeyLabel, sizeof(sessionKeyLabel) - 1 },          // Метка ключа
// 	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },              // Класс - секретный ключ
// 	{ CKA_KEY_TYPE, &keyTypeGost28147, sizeof(keyTypeGost28147)},          // Тип ключа - ГОСТ 28147-89
// 	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},                 // Ключ является объектом сессии
// 	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},              // Ключ может быть изменен после создания
// 	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                 // Ключ доступен только после аутентификации на токене
// 	{ CKA_VALUE, NULL_PTR, 0},                                             // Значение ключа
// 	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},             // Ключ может быть извлечен в зашифрованном виде
// 	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}              // Ключ может быть извлечен в открытом виде
//};


/*************************************************************************
* Шаблон демаскированного сессионного ключа типа Кузнечик                *
*************************************************************************/
CK_ATTRIBUTE unwrapSessionKeyTemplate[] =
{
	{ CKA_LABEL, &sessionKuznechikKeyLabel, sizeof(sessionKuznechikKeyLabel) - 1 },	// Метка ключа
	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },						// Класс - секретный ключ
	{ CKA_KEY_TYPE, &keyTypeKuznechik, sizeof(keyTypeKuznechik)},					// Тип ключа - Кузнечик
	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},							// Ключ является объектом сессии
	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},						// Ключ может быть изменен после создания
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},							// Ключ доступен только после аутентификации на токене
	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},						// Ключ может быть извлечен в зашифрованном виде
	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}						// Ключ может быть извлечен в открытом виде
};

/*************************************************************************
* Шаблон демаскированного сессионного ключа типа Магма                   *
*************************************************************************/
//CK_ATTRIBUTE sessionKeyTemplate[] =
//{
//	{ CKA_LABEL, &sessionMagmaKeyLabel, sizeof(sessionMagmaKeyLabel) - 1 },// Метка ключа
//	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },              // Класс - секретный ключ
//	{ CKA_KEY_TYPE, &keyTypeMagma, sizeof(keyTypeMagma)},				   // Тип ключа - Магма
//	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},                 // Ключ является объектом сессии
//	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},              // Ключ может быть изменен после создания
//	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                 // Ключ доступен только после аутентификации на токене
//	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},             // Ключ может быть извлечен в зашифрованном виде
//	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}              // Ключ может быть извлечен в открытом виде
//};

/*************************************************************************
* Шаблон демаскированного сессионного ключа типа ГОСТ 28147-89           *
*************************************************************************/
//CK_ATTRIBUTE sessionKeyTemplate[] =
//{
// 	{ CKA_LABEL, &sessionKeyLabel, sizeof(sessionKeyLabel) - 1 },          // Метка ключа
// 	{ CKA_CLASS, &secretKeyObject, sizeof(secretKeyObject) },              // Класс - секретный ключ
// 	{ CKA_KEY_TYPE, &keyTypeGost28147, sizeof(keyTypeGost28147)},          // Тип ключа - ГОСТ 28147-89
// 	{ CKA_TOKEN, &attributeFalse, sizeof(attributeFalse)},                 // Ключ является объектом сессии
// 	{ CKA_MODIFIABLE, &attributeTrue, sizeof(attributeTrue)},              // Ключ может быть изменен после создания
// 	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                 // Ключ доступен только после аутентификации на токене
// 	{ CKA_EXTRACTABLE, &attributeTrue, sizeof(attributeTrue)},             // Ключ может быть извлечен в зашифрованном виде
// 	{ CKA_SENSITIVE, &attributeFalse, sizeof(attributeFalse)}              // Ключ может быть извлечен в открытом виде
//};

/* Структура данных типа CK_ATTRIBUTE для хранения значения атрибута CKA_VALUE */
CK_ATTRIBUTE attrValue = { CKA_VALUE, NULL_PTR, 0 };

CK_OBJECT_HANDLE hDerivedTwinKey;           		 // Хэндл выработанного двйоственного ключа
CK_BYTE_PTR pbtSessionKey = NULL_PTR;                // Указатель на буфер, содержащий сессионный ключ
CK_BYTE_PTR pbtWrappedKey = NULL_PTR;                // Указатель на буфер, содержащий маскированный на стороне отправителя сессионный ключ
CK_ULONG ulWrappedKeySize = 0;                       // Размер буфера со значением маскированного на стороне отправителя сессионного ключа, в байтах
CK_BYTE_PTR pbtUnwrappedKey = NULL_PTR;              // Указатель на буфер, содержащий демаскированный на стороне получателя сессионный ключ
CK_ULONG ulUnwrappedKeySize = 0;                     // Размер буфера со значением демаскированного на стороне получателя сессионного ключа, в байтах
CK_OBJECT_HANDLE hTempKey = NULL_PTR;                // Хэндл ключа, который будет маскироваться/демаскироваться

...

/* Заполнить шаблон сессионного ключа случайными данными */
GenerateRandomData(GOST_28147_KEY_SIZE, &pbtSessionKey);
for (int i = 0; i < arraysize(sessionKeyTemplate); i++)
	if (sessionKeyTemplate[i].type == CKA_VALUE)
	{
		sessionKeyTemplate[i].pValue = pbtSessionKey;
		sessionKeyTemplate[i].ulValueLen = GOST_KUZNECHIK_KEY_SIZE;
		break;
	}

/*************************************************************************
* Маскирование ключа                                                     *
*************************************************************************/

/* Создать ключ, который будет маскирован */
printf("Creating the GOST 28147-89 key to wrap");
rv = pFunctionList->C_CreateObject(hSession,	// Хэндл сессии, открытой с правами Пользователя
	sessionKeyTemplate,					// Шаблон создаваемого ключа
	arraysize(sessionKeyTemplate),		// Размер шаблона
	&hTempKey);							// Хэндл созданного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto wrap_exit;
}
printf(" -> OK\n");

/* Получить размер буфера, содержащего значение маскированного ключа */
printf("Defining wrapping key size");
rv = pFunctionList->C_WrapKey(hSession,					// Хэндл сессии, открытой с правами Пользователя		
	gostKExp15Mech,			// Механизм маскирования
	hDerivedTwinKey,		// Хэндл ключа, которым будет маскироваться ключ
	hTempKey,				// Хэндл ключа, который будет маскирован
	NULL_PTR,				// Указатель на буфер с маскированным ключом
	&ulWrappedKeySize);		// Размер маскированного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto wrap_exit;
}
printf(" -> OK\n");

pbtWrappedKey = (CK_BYTE*)malloc(ulWrappedKeySize);
if (pbtWrappedKey == NULL_PTR)
{
	printf("Memory allocation for pbtWrappedKey failed! \n");
	goto wrap_exit;
}
memset(pbtWrappedKey,
	0,
	ulWrappedKeySize * sizeof(CK_BYTE));

/* Получить маскированный ключ на стороне отправителя */
printf("Wrapping key");
rv = pFunctionList->C_WrapKey(hSession,					// Хэндл сессии, открытой с правами Пользователя		
	gostKExp15Mech,			// Механизм маскирования
	hDerivedTwinKey,		// Хэндл ключа, которым будет маскироваться ключ
	hTempKey,				// Хэндл ключа, который будет маскирован
	pbtWrappedKey,			// Указатель на буфер с маскированным ключом
	&ulWrappedKeySize);		// Размер маскированного ключа
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto wrap_exit;
}
printf(" -> OK\n");

/* Распечатать буфер, содержащий маскированный ключ */
printf("Wrapped key data is:\n");
for (int i = 0; i < ulWrappedKeySize; i++)
{
	printf("%02X ", pbtWrappedKey[i]);
	if ((i + 1) % 9 == 0)
		printf("\n");
}


wrap_exit:
if (hTempKey)
{
	pFunctionList->C_DestroyObject(hSession,
		hTempKey);
	hTempKey = NULL_PTR;
}

if (rv == CKR_OK)
printf("\nWrapping has been completed successfully.\n");
else
{
	printf("\nWrapping failed!\n");
	goto exit;
}


/*************************************************************************
* Демаскирование ключа                                                   *
*************************************************************************/
printf("Unwrapping key");
rv = pFunctionList->C_UnwrapKey(hSession,								// Хэндл сессии, открытой с правами Пользователя	
	gostKExp15Mech,							// Механизм маскирования
	hDerivedTwinKey,						// Хэндл ключа, которым был маскирован ключ
	pbtWrappedKey,							// Указатель на буфер с маскированным ключом
	ulWrappedKeySize,						// Размер буфера с маскированным ключом
	unwrapSessionKeyTemplate,				// Указатель на шаблон для демаскированного ключа
	arraysize(unwrapSessionKeyTemplate),	// Размер шаблона для демаскированного ключа
	&hTempKey);								// Указатель на буфер с маскированным ключом
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto unwrap_exit;
}
printf(" -> OK\n");

/* Получить буфер со значением демаскированного ключа */
printf("Getting unwrapped key value...\n");

/* Получить размер буфера для хранения значения атрибута CKA_VALUE */
printf("Getting object value size");
rv = pFunctionList->C_GetAttributeValue(hSession,   // Хэндл сессии, открытой с правами Пользователя
	hTempKey,	// Хэндл объекта, значение атрибутов которых требуется получить 
	&attrValue, // Указатель на шаблон с атрибутами, значение которых требуется получить
	1);			// Количество строк в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto unwrap_exit;
}
printf(" -> OK\n");

/* Выделить необходимое количество памяти для значения атрибута */
attrValue.pValue = (CK_BYTE*)malloc(attrValue.ulValueLen);
if (attrValue.pValue == NULL_PTR)
{
	printf("Memory allocation for attrValue failed! \n");
	goto unwrap_exit;
}
memset(attrValue.pValue,
	0,
	(attrValue.ulValueLen * sizeof(CK_BYTE)));

/* Получить значение атрибута CKA_VALUE */
printf("Getting object value");
rv = pFunctionList->C_GetAttributeValue(hSession,   // Хэндл сессии, открытой с правами Пользователя
	hTempKey,	// Хэндл объекта, значение атрибутов которых требуется получить 
	&attrValue, // Указатель на шаблон с атрибутами, значение которых требуется получить
	1);			// Количество строк в шаблоне
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
	goto unwrap_exit;
}
printf(" -> OK\n");
/* Распечатать буфер со значением демаскированного ключа  */
printf("Unwrapped key data:\n");
for (int i = 0; i < attrValue.ulValueLen; i++)
{
	printf("%02X ", ((CK_BYTE_PTR)attrValue.pValue)[i]);
	if ((i + 1) % 8 == 0)
		printf("\n");
}

unwrap_exit:
if (hTempKey)
{
	pFunctionList->C_DestroyObject(hSession,
		hTempKey);
	hTempKey = NULL_PTR;
}
if (rv == CKR_OK)
printf("Unwrapping has been completed successfully.\n\n");
else
{
	printf("\nUnwrapping failed!\n\n");
	goto exit;
}

/* Сравнить первоначальное значение сессионного ключа со значением демаскированного ключа */
if ((ulUnwrappedKeySize != GOST_KUZNECHIK_KEY_SIZE)
	|| (memcmp(pbtSessionKey,
		attrValue.pValue,
		GOST_KUZNECHIK_KEY_SIZE) != 0))
	printf("\nThe unwrapped key is not equal to the session key!\n");
else
printf("The unwrapped key is equal to the session key.\n");

exit:
printf("Finish");

Вычисление значения хеш-функции

Поддерживаемые механизмы

Устройства Рутокен поддерживают следующие механизмы хеширования:

  • CKM_MD2 для хеширования алгоритмом MD2 (только программно),
  • CKM_MD5 для хеширования алгоритмом MD5 (только программно),
  • CKM_SHA_1 для хеширования алгоритмом SHA-1 (только программно),
  • CKM_GOSTR3411 для хеширования алгоритмом ГОСТ Р 34.11.94 (программно и аппаратно),
  • CKM_GOSTR3411_12_256 для хеширования алгоритмом ГОСТ Р 34.11.2012 с длиной значения 256 бит (программно и аппаратно),
  • CKM_GOSTR3411_12_512 для хеширования алгоритмом ГОСТ Р 34.11.2012 с длиной закрытого ключа 512 бит (программно и аппаратно).

Хеширование данных 

Для хеширования данных служат функции C_DigestInit() и C_Digest(). Сначала операцию хеширования нужно инициализировать через C_DigestInit(), передав в нее идентификатор сессии и ссылку на механизм хеширования. Затем размер буфера хешированных данных можно определить, вызвав C_Digest(), и выполнить хеширование данных, вызвав C_Digest() второй раз.

Предварительно должна быть открыта сессия чтения/записи. 

Пример хеширования данных по алгоритму ГОСТ Р 34.11-94 и ГОСТ Р 34.11-2012

Хеширование данных по алгоритму ГОСТ Р 34.11-94 / ГОСТ Р 34.11-2012
/* Данные для хеширования в виде двоичной строки */
CK_BYTE pbtData[] = { 0x3C, 0x21, 0x50, 0x49, 0x4E, 0x50, 0x41, 0x44, 0x46, 0x49, 0x4C, 0x45, 0x20, 0x52, 0x55, 0x3E, 
					  0x3C, 0x21, 0x3E, 0xED, 0xE5, 0xE2, 0xE8, 0xE4, 0xE8, 0xEC, 0xFB, 0xE9, 0x20, 0xF2, 0xE5, 0xEA };

/*  Механизм хеширования ГОСТ Р 34.11-94 */
CK_MECHANISM gostR3411_946HashMech = {CKM_GOSTR3411, NULL_PTR, 0}; 
/*  Механизм хеширования ГОСТ Р 34.11-2012(256) */
CK_MECHANISM gostR3411_12_256HashMech = {CKM_GOSTR3411_12_256, NULL_PTR, 0}; 
/*  Механизм хеширования ГОСТ Р 34.11-2012(512) */
CK_MECHANISM gostR3411_12_512HashMech = {CKM_GOSTR3411_12_512, NULL_PTR, 0 };
 
CK_BYTE_PTR pbtHash 	= NULL_PTR;            // Указатель на буфер для значения хеша данных
CK_ULONG 	ulHashSize 	= 0;                   // Размер буфера в байтах
 
while(TRUE)
{
	...
 
	/* Инициализировать операцию хеширования */
	printf("C_DigestInit");
	rv = pFunctionList->C_DigestInit(hSession,					// Хэндл сессии
								 	 &gostR3411_946HashMech );	// Механизм хеширования: необходимо выбрать соответствующий из
																// gostR3411_946HashMech, gostR3411_12_256HashMech или gostR3411_12_512HashMech 
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Определить размер значения хеша данных */
	printf("C_Digest step 1");
	rv = pFunctionList->C_Digest( hSession,				// Хэндл сессии
								  pbtData,				// Буфер с данными для хеширования
								  arraysize(pbtData),	// Размер данных для хеширования
								  pbtHash,				// Буфер для вычисленного значения хеша
								  &ulHashSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtHash = (CK_BYTE*)malloc(ulHashSize);
	if (pbtHash == NULL)
	{
		printf("Memory allocation for pbtHash failed! \n");
		break;
	}
	memset(pbtHash,
		   0,
		   (ulHashSize * sizeof(CK_BYTE)));
	/* Сформировать хеш от исходных данных */
	printf("C_Digest step 2");
	rv = pFunctionList->C_Digest(hSession,				// Хэндл сессии
								 pbtData,				// Буфер с данными для хеширования
								 arraysize(pbtData),	// Размер данных для хеширования
								 pbtHash,				// Буфер для вычисленного значения хеша
								 &ulHashSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	break;
}
 

Вычисление MAC (Message Authentication Code)

Поддерживаемые механизмы

Устройства Рутокен поддерживает создание MAC на ключах типа CKK_KUZNECHIK, CKK_MAGMA и CKK_GOST28147. Объекты ключей, на которых вычисляется и проверяется MAC, также должны иметь атрибуты CKA_SIGN и CKA_VERIFY установленные в CK_TRUE.

Вычисление MAC

Для вычисления MAC служат функции C_SignInit() и C_Sign(). Сначала операцию получения MAC нужно инициализировать через  C_SignInit(), передав в нее идентификатор сессии и ссылку на механизм. Размер буфера под MAC можно определить, вызвав C_Sign() с нулевым указателем на выходные данные. Вызвав C_Sign() с не нулевым указателем на выходные данные мы получим вычисленный MAC.

Предварительно должна быть открыта сессия чтения/записи. 

Пример получения MAC от данных по алгоритму ГОСТ Р 34.12-2015

Получение MAC от данных по алгоритму ГОСТ Р 34.12-2015
/* Данные для хеширования в виде двоичной строки */
CK_BYTE pbtData[] = { 0x3C, 0x21, 0x50, 0x49, 0x4E, 0x50, 0x41, 0x44, 0x46, 0x49, 0x4C, 0x45, 0x20, 0x52, 0x55, 0x3E, 
					  0x3C, 0x21, 0x3E, 0xED, 0xE5, 0xE2, 0xE8, 0xE4, 0xE8, 0xEC, 0xFB, 0xE9, 0x20, 0xF2, 0xE5, 0xEA };

/*  Механизм выработки имитовставки ГОСТ 28147-89 */
CK_MECHANISM gost28147MacMech = {CKM_GOST28147_MAC, NULL_PTR, 0};
/*  Механизм выработки имитовставки ГОСТ Р 34.12-2015 */
CK_MECHANISM kuznechikMacMech = { CKM_KUZNECHIK_MAC, NULL_PTR, 0 };
CK_MECHANISM magmaMacMech = { CKM_MAGMA_MAC, NULL_PTR, 0 };
 
CK_BYTE_PTR pbtMac 	= NULL_PTR;            // Указатель на буфер для значения MAC
CK_ULONG 	ulMacSize 	= 0;               // Размер буфера в байтах
 
while(TRUE)
{
	...
 
	/* Инициализировать операцию взятия MAC*/
	printf("C_SignInit");
	rv = functionList->C_SignInit(hSession,					// Хэндл сессии
								  &kuznechikMacMech,	    // Механизм взятия MAC
								  hSecretKey);              // Хендл секретного ключа, на котором вычисляем MAC
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Определить размер значения MAC */
	printf("C_Sign step 1");
	rv = pFunctionList->C_Sign(   hSession,				// Хэндл сессии
								  pbtData,				// Буфер с данными для хеширования
								  arraysize(pbtData),	// Размер данных для хеширования
								  pbtMac,				// Буфер для вычисленного значения хеша
								  &ulMacSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtMac = (CK_BYTE*)malloc(ulMacSize);
	if (pbtHash == NULL)
	{
		printf("Memory allocation for pbtMac failed! \n");
		break;
	}
	memset(pbtMac,
		   0,
		   (ulMacSize * sizeof(CK_BYTE)));
	/* Вычислить MAC от исходных данных */
	printf("C_Sign step 2");
	rv = pFunctionList->C_Sign(  hSession,				// Хэндл сессии
								 pbtData,				// Буфер с данными для хеширования
								 arraysize(pbtData),	// Размер данных для хеширования
								 pbtMac,				// Буфер для вычисленного значения хеша
								 &ulMacSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	break;
}
 

Проверка MAC

Для проверки MAC служат функции C_VerifyInit() и C_Verify(). Сначала операцию проверки MAC нужно инициализировать через  C_VerifyInit(), передав в нее идентификатор сессии и ссылку на механизм. Проверка MAC происходит при вызове функции C_Verify() с переданным указателем на данные и MAC.

Предварительно должна быть открыта сессия чтения/записи. 

Пример проверки MAC от данных по алгоритму ГОСТ Р 34.12-2015

Проверка MAC от данных по алгоритму ГОСТ Р 34.12-2015
/* Данные для хеширования в виде двоичной строки */
CK_BYTE pbtData[] = { 0x3C, 0x21, 0x50, 0x49, 0x4E, 0x50, 0x41, 0x44, 0x46, 0x49, 0x4C, 0x45, 0x20, 0x52, 0x55, 0x3E, 
					  0x3C, 0x21, 0x3E, 0xED, 0xE5, 0xE2, 0xE8, 0xE4, 0xE8, 0xEC, 0xFB, 0xE9, 0x20, 0xF2, 0xE5, 0xEA };

/*  Механизм выработки MAC ГОСТ Р 34.12-2015 */
CK_MECHANISM kuznechikMacMech = { CKM_KUZNECHIK_MAC, NULL_PTR, 0 };
CK_MECHANISM magmaMacMech = { CKM_MAGMA_MAC, NULL_PTR, 0 };
 
while(TRUE)
{
	...
 
	/* Инициализировать операцию проверки MAC*/
	printf("C_VerifyInit");
	rv = functionList->C_VerifyInit(hSession,				  // Хэндл сессии
								    &kuznechikMacMech,	      // Механизм взятия MAC
								    hSecretKey); 		      // Хендл секретного ключа, на котором вычисляем MAC
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Проверка MAC */
	printf("C_Verify");
	rv = pFunctionList->C_Verify( hSession,				// Хэндл сессии
								  pbtData,				// Буфер с данными для хеширования
								  arraysize(pbtData),	// Размер данных для хеширования
								  pbtMac,				// Буфер для вычисленного значения хеша
								  &ulMacSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	break;
}
 

Подпись и проверка подписи

Поддерживаемые механизмы

Устройства Рутокен поддерживают следующие механизмы подписи:

  • CKM_GOSTR3410 подписи алгоритмом ГОСТ Р 34.10.2001 и ГОСТ Р 34.10.2012 с длиной закрытого ключа 256 бит,
  • CKM_GOSTR3410_WITH_GOSTR3411 для совместного хеширования алгоритмом CKM_GOSTR3411 и подписи алгоритмом CKM_GOSTR3410,
  • CKM_GOSTR3410_512 для подписи алгоритмом ГОСТ Р 34.10.2012 с длиной закрытого ключа 512 бит,
  • CKM_GOSTR3410_WITH_GOSTR3411_12_256 для совместного хеширования алгоритмом CKM_GOSTR3411_12_256 и подписи на ключе длиной 256 бит,

  • CKM_GOSTR3410_WITH_GOSTR3411_12_512 для совместного хеширования алгоритмом  CKM_GOSTR3411_12_512 и подписи на ключе длиной 512 бит,

  • CKM_RSA_PKCS для подписи алгоритмом RSA.
  • CKM_MD5_RSA_PKCS, CKM_SHA1_RSA_PKCS, CKM_SHA224_RSA_PKCS, CKM_SHA256_RSA_PKCS, CKM_SHA384_RSA_PKCS, CKM_SHA512_RSA_PKCS для алгоритма взятия хеша с последующим взятием подписи по алгоритму RSA
  • CKM_ECDSA для подписи алгоритмом ECDSA.

Подпись данных 

Для вычисления подписи сообщения служат функции C_SignInit() и C_Sign(). Сначала операцию подписи нужно инициализировать через C_SignInit(), передав в нее идентификатор сессии, механизма и закрытого ключа. Затем размер буфера для подписанных данных можно определить, вызвав C_Sign() с указателем на длину буфера, равной нулю, и подписать данные, вызвав C_Sign() второй раз.

Функция C_Sign(), как и некоторые другие функции интерфейса PKCS#11, возвращает значения различной длины и подчиняется соответствующему соглашению вызова для подобных функций, описанному в пункте 11.2 стандарта PKCS#11. Краткая его суть сводится к тому, что существует два способа вызова таких функций. В первом случае функция может быть вызвана с пустым указателем для возвращаемого буфера (NULL_PTR), в таком случае функция вернет размер возвращаемого буфера в байтах в соответствующую переменную. Во втором случае функция может быть вызвана с непустым указателем для возвращаемого буфера и указанным значением длины буфера, в таком случае функция вернет результат выполнения криптографической операции в соответствующую переменную при условии достаточности размера буфера. Если указанная длина буфера недостаточна для возвращения всего результат, функция вернет ошибку CKR_BUFFER_TOO_SMALL. При успешном выполнении функция возвращает код ошибки CKR_OK.

При использовании совместных алгоритмов хеширования и подписи (например, CKM_GOSTR3410_WITH_GOSTR3411) в  C_Sign() передается открытый текст для подписи, при использовании только алгоритма подписи (например, CKM_GOSTR3410) – уже прохешированные данные.

При использовании совместных алгоритмов хеширования и подписи в механизме должны быть заданы параметры алгоритма хеширования.

В качестве данных на подпись может быть передан запрос на сертификат, представленный в байт-коде.

Подпись данных отдельными механизмами хеширования и подписи

При использовании отдельных механизмов хеширования и подписи сообщение сначала хешируется функциями C_DigestInit() и C_Digest(), а затем значение хеша подписывается функциями C_SignInit() и C_Sign().

Пример подписи данных по алгоритму ГОСТ Р 34.10-2012 отдельными механизмами хеширования и подписи для всех устройств Рутокен

Подпись данных по алгоритму ГОСТ Р 34.10-2012
/* Данные для подписи в виде двоичной строки */
CK_BYTE pbtData[] = { 0x3C, 0x21, 0x50, 0x49, 0x4E, 0x50, 0x41, 0x44, 0x46, 0x49, 0x4C, 0x45, 0x20, 0x52, 0x55, 0x3E, 
					  0x3C, 0x21, 0x3E, 0xED, 0xE5, 0xE2, 0xE8, 0xE4, 0xE8, 0xEC, 0xFB, 0xE9, 0x20, 0xF2, 0xE5, 0xEA, 
					  0xF1, 0xF2, 0x3C, 0x4E, 0x3E, 0xD4, 0xC8, 0xCE, 0x3A, 0x3C, 0x56, 0x3E, 0xCF, 0xE5, 0xF2, 0xF0, 
					  0xEE, 0xE2, 0x20, 0xCF, 0xE5, 0xF2, 0xF0, 0x20, 0xCF, 0xE5, 0xF2, 0xF0, 0xEE, 0xE2, 0xE8, 0xF7, 
					  0x20, 0xCC, 0xEE, 0xF1, 0xEA, 0xE2, 0xE0, 0x2C, 0x20, 0xCF, 0xE8, 0xEE, 0xED, 0xE5, 0xF0, 0xF1, 
					  0xEA, 0xE0, 0xFF, 0x20, 0xF3, 0xEB, 0x2C, 0x20, 0xE4, 0x2E, 0x20, 0x33, 0x2C, 0x20, 0xEA, 0xE2, 
					  0x2E, 0x20, 0x37, 0x32 };
 
/*  Механизм хеширования ГОСТ Р 34.11-2012(256) */
CK_MECHANISM HashMech256 = {CKM_GOSTR3411_12_256, NULL_PTR, 0}; 
 
/*  Механизм хеширования ГОСТ Р 34.11-2012(512) */
CK_MECHANISM HashMech512 = {CKM_GOSTR3411_12_512, NULL_PTR, 0}; 

/* Механизм подписи/проверки подписи по алгоритму ГОСТ Р 34.10-2012, 256 бит */
CK_MECHANISM    SigVerMech256 = {CKM_GOSTR3410, NULL_PTR, 0};
 
/* Механизм подписи/проверки подписи по алгоритму ГОСТ Р 34.10-2012, 512 бит */
CK_MECHANISM    SigVerMech512 = {CKM_GOSTR3410_512, NULL_PTR, 0};
 
 
CK_BYTE_PTR pbtHash 		= NULL_PTR;      // Указатель на буфер для значения хеша данных
CK_ULONG 	ulHashSize 		= 0;             // Размер буфера в байтах
 
CK_BYTE_PTR pbtSignature 	= NULL_PTR;      // Указатель на буфер, содержащий подпись для исходных данных
CK_ULONG 	ulSignatureSize = 0;             // Размер буфера, содержащего подпись для исходных данных, в байтах
 
while(TRUE)
{
	...
 
	/* Инициализировать операцию хеширования  */
	printf("C_DigestInit");
	rv = pFunctionList->C_DigestInit(hSession,		// Хэндл сессии
								 	 &HashMech256);	// Механизм хеширования (HashMech256 или HashMech512)
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Определить размер значения хеша данных */
	printf("C_Digest step 1");
	rv = pFunctionList->C_Digest( hSession,				// Хэндл сессии
								  pbtData,				// Буфер с данными для хеширования
								  arraysize(pbtData),	// Размер данных для хеширования
								  pbtHash,				// Буфер для вычисленного значения хеша
								  &ulHashSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtHash = (CK_BYTE*)malloc(ulHashSize);
	if (pbtHash == NULL_PTR)
	{
		printf("Memory allocation for pbtHash failed! \n");
		break;
	}
	memset(pbtHash,
		   0,
		   (ulHashSize * sizeof(CK_BYTE)));
	/* Сформировать хеш от исходных данных */
	printf("C_Digest step 2");
	rv = pFunctionList->C_Digest(hSession,				// Хэндл сессии
								 pbtData,				// Буфер с данными для хеширования
								 arraysize(pbtData),	// Размер данных для хеширования
								 pbtHash,				// Буфер для вычисленного значения хеша
								 &ulHashSize);			// Размер значения хеша
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	/* Инициализировать операцию подписи данных */
	printf("C_SignInit");
	rv = pFunctionList->C_SignInit( hSession,		// Хэндл сессии
									&SigVerMech256,	// Механизм подписи (SigVerMech256 или SigVerMech512)
									hPrivateKey ); 	// Хэндл закрытого ключа
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	/* Определить размер подписи*/
	printf("C_Sign step 1");
	rv = pFunctionList->C_Sign( hSession,			// Хэндл сессии
								pbtHash,			// Буфер с данными для подписи
								ulHashSize,			// Длина подписываемых данных
								pbtSignature,		// Буфер с подписью
								&ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtSignature = (CK_BYTE*)malloc(ulSignatureSize);
	if (pbtSignature  == NULL)
	{
		printf("Memory allocation for pbtSignature failed! \n");
		break;
	}
	memset( pbtSignature,
			0,
			ulSignatureSize * sizeof(CK_BYTE));

	/* Подписать исходные данные */
	printf("C_Sign step 2");
	rv = pFunctionList->C_Sign( hSession,			// Хэндл сессии
								pbHash,				// Буфер с данными для подписи
								ulHashSize,			// Длина подписываемых данных
								pbtSignature,		// Буфер с подписью
								&ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Распечатать буфер, содержащий подпись */
	printf("Signature buffer is: \n");
	for (i = 0;
		 i < ulSignatureSize;
		 i++)
	{
		printf("%02X ", pbtSignature[i]);
		if ((i + 1) % 8 == 0)
			printf("\n");
	}
	
	break;
}

Подпись совместным механизмом хеширования и подписи

При использовании совместного механизма и хеширование, и подпись выполняются функцией C_Sign(). Сначала в функцию C_SignInit() передается совместный механизм (например, CKM_GOSTR3410_WITH_GOSTR3411), а затем в функцию C_Sign() – сообщение.

Пример подписи данных по алгоритму ГОСТ Р 34.10-2012 совместным механизмом хеширования и подписи

Подпись данных по алгоритму ГОСТ Р 34.10-2012
/* Данные для подписи в виде двоичной строки */
CK_BYTE pbtData[] = { 0x3C, 0x21, 0x50, 0x49, 0x4E, 0x50, 0x41, 0x44, 0x46, 0x49, 0x4C, 0x45, 0x20, 0x52, 0x55, 0x3E, 
					  0x3C, 0x21, 0x3E, 0xED, 0xE5, 0xE2, 0xE8, 0xE4, 0xE8, 0xEC, 0xFB, 0xE9, 0x20, 0xF2, 0xE5, 0xEA, 
					  0xF1, 0xF2, 0x3C, 0x4E, 0x3E, 0xD4, 0xC8, 0xCE, 0x3A, 0x3C, 0x56, 0x3E, 0xCF, 0xE5, 0xF2, 0xF0, 
					  0xEE, 0xE2, 0x20, 0xCF, 0xE5, 0xF2, 0xF0, 0x20, 0xCF, 0xE5, 0xF2, 0xF0, 0xEE, 0xE2, 0xE8, 0xF7, 
					  0x20, 0xCC, 0xEE, 0xF1, 0xEA, 0xE2, 0xE0, 0x2C, 0x20, 0xCF, 0xE8, 0xEE, 0xED, 0xE5, 0xF0, 0xF1, 
					  0xEA, 0xE0, 0xFF, 0x20, 0xF3, 0xEB, 0x2C, 0x20, 0xE4, 0x2E, 0x20, 0x33, 0x2C, 0x20, 0xEA, 0xE2, 
					  0x2E, 0x20, 0x37, 0x32 };
 
/* Набор параметров КриптоПро алгоритма ГОСТ Р 34.11-2012, 256 бит*/
CK_BYTE     GOST3411_256_params[]  = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02 };
 
/*  Механизм подписи подписи по алгоритму ГОСТ Р 34.10-2012 с хешированием по алгоритму ГОСТ Р 34.11-2012 (256 бит)*/
CK_MECHANISM    HashSigVerMech256 = {CKM_GOSTR3410_WITH_GOSTR3411_12_256, GOST3411_256_params, sizeof(GOST3411_256_params)};
 
 
/* Набор параметров КриптоПро алгоритма ГОСТ Р 34.11-2012, 512 бит*/
CK_BYTE     GOST3411_512_params[]  = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03 };
 
/*  Механизм подписи подписи по алгоритму ГОСТ Р 34.10-2012 с хешированием по алгоритму ГОСТ Р 34.11-2012 (512 бит)*/
CK_MECHANISM    HashSigVerMech512 = {CKM_GOSTR3410_WITH_GOSTR3411_12_512, GOST3411_512_params, sizeof(GOST3411_512_params)};

CK_BYTE_PTR pbtSignature = NULL_PTR;                 // Указатель на буфер, содержащий подпись для исходных данных
CK_ULONG ulSignatureSize = 0;                        // Размер буфера, содержащего подпись для исходных данных, в байтах
 
while(TRUE)
{
	...
 
	/* Инициализировать операцию подписи данных */
	printf("C_SignInit");
	rv = pFunctionList->C_SignInit(hSession,			// Хэндл сессии
									&HashSigVerMech256,	// Механизм подписи (HashSigVerMech256 или HashSigVerMech512)
									hPrivateKey ); 		// Хэндл закрытого ключа
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	/* Определить размер подписи*/
	printf("C_Sign step 1");
	rv = pFunctionList->C_Sign(hSession,			// Хэндл сессии
								pbtData,			// Буфер с данными для подписи
								arraysize(pbtData), // Длина подписываемых данных
								pbtSignature,		// Буфер с подписью
								&ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtSignature = (CK_BYTE*)malloc(ulSignatureSize);
	if (pbtSignature  == NULL)
	{
		printf("Memory allocation for pbtSignature failed! \n");
		break;
	}
	memset( pbtSignature,
			0,
			ulSignatureSize * sizeof(CK_BYTE));

	/* Подписать исходные данные */
	printf("C_Sign step 2");
	rv = pFunctionList->C_Sign(hSession,			// Хэндл сессии
								pbtData,			// Буфер с данными для подписи
								arraysize(pbtData), // Длина подписываемых данных
								pbtSignature,		// Буфер с подписью
								&ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Распечатать буфер, содержащий подпись */
	printf("Signature buffer is: \n");
	for (i = 0;
		 i < ulSignatureSize;
		 i++)
	{
		printf("%02X ", pbtSignature[i]);
		if ((i + 1) % 8 == 0)
			printf("\n");
	}
	
	break;
}

Пример подписи данных по алгоритму ECDSA отдельными механизмами хеширования и подписи

Подпись данных по алгоритму ECDSA
/*************************************************************************
* Данные для цифровой подписи                                            *
*************************************************************************/
CK_BYTE pbtData[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
					0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };

CK_MECHANISM sha256Mech = { CKM_SHA256, NULL_PTR, 0 };
CK_MECHANISM ecdsaSigVerMech = { CKM_ECDSA, NULL_PTR, 0 };

CK_BYTE_PTR pbtSignature;                           // Указатель на буфер, содержащий цифровую подпись для данных
CK_ULONG ulSignatureSize ;                          // Размер буфера, содержащего цифровую подпись для данных, в байтах

CK_BYTE_PTR pbtHash ;                               // Указатель на временный буфер для хэш-кода от данных
CK_ULONG ulHashSize;                              // Размер временного буфера в байтах



while (TRUE)
{
	...

		/* Инициализировать операцию хеширования  */
		rv = pFunctionList->C_DigestInit(hSession, &sha256Mech);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Определить размер значения хеша данных */
	printf("C_Digest step 1");
	rv = pFunctionList->C_Digest(hSession, pbtData, sizeof(pbtData), NULL_PTR, &ulHashSize);

	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtHash = (CK_BYTE*)malloc(ulHashSize);
	if (pbtHash == NULL_PTR)
	{
		printf("Memory allocation for pbtHash failed! \n");
		break;
	}
	memset(pbtHash,
		0,
		(ulHashSize * sizeof(CK_BYTE)));
	/* Сформировать хеш от исходных данных */
	printf("C_Digest step 2");
	rv = pFunctionList->C_Digest(hSession, pbtData, sizeof(pbtData), pbtHash, &ulHashSize);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Инициализировать операцию подписи данных */
	printf("C_SignInit");
	rv = pFunctionList->C_SignInit(hSession, &ecdsaSigVerMech, hPrivateKey);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtSignature = (CK_BYTE*)malloc(ulSignatureSize);
	if (pbtSignature == NULL_PTR)
	{
		printf("Memory allocation for pbtSignature failed! \n");
		break;
	}
	memset(pbtSignature,
		0,
		ulSignatureSize * sizeof(CK_BYTE));

	/* Подписать исходные данные */
	printf("C_Sign step 2");
	rv = pFunctionList->C_Sign(hSession, pbtHash, ulHashSize, NULL_PTR, &ulSignatureSize);
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Распечатать буфер, содержащий подпись */
	printf("Signature buffer is: \n");
	for (i = 0;
		i < ulSignatureSize;
		i++)
	{
		printf("%02X ", pbtSignature[i]);
		if ((i + 1) % 8 == 0)
			printf("\n");
	}

	break;
}


Проверка подписи
Для проверки подписи данных служат функции C_VerifyInit() и C_Verify(). Сначала операцию подписи нужно инициализировать через C_VerifyInit(), передав в нее идентификатор сессии, механизма и открытого ключа. Затем можно проверить подпись функцией C_Verify()

Совместные механизмы хеширования и подписи (например, CKM_GOSTR3410_WITH_GOSTR3411) не поддерживаются  C_VerifyInit(). В C_Verify() передаются предварительно прохешированные функцией C_Digest() исходные данные.

Проверка подписи
while(TRUE)
{
	...
	/* Инициализировать операцию проверки подписи */
	printf(" C_VerifyInit");
	rv = pFunctionList->C_VerifyInit(hSession,    	// Хэндл сессии
									 &SigVerMech,	// Механизм подписи
									 hPublicKey);	// Хэндл открытого ключа
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	
	/* Проверить подпись для исходных данных */
	printf(" C_Verify");
	rv = pFunctionList->C_Verify(hSession,   		// Хэндл сессии
								 pbHash,			// Буфер с значением хеша исходногосообщения
								 ulHashSize,		// Длина буфера
								 pbtSignature,		// Буфер с подписью
								 ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	break;
}
...
if (pbtSignature)
{
	free(pbtSignature);
	pbtSignature = NULL_PTR;
}
 
if (pbHash)
{
	free(pbHash);
	pbHash= NULL_PTR;
}

Поточная подпись и проверка подписи

При работе с большим количеством данных бывает удобно отправлять данные токену частями. При таком режиме работе с токеном необходимо использовать функции C_SignInit/C_VerifyInitC_SignUpdate/C_VerifyUpdate и C_SignFinal/C_VerifyFinal

Пример подписи данных в несколько итераций
	/* Инициализировать операцию подписи данных */
	printf("C_SignInit");
	rv = pFunctionList->C_SignInit( hSession,		// Хэндл сессии
									&SigVerMech256,	// Механизм подписи (SigVerMech256 или SigVerMech512)
									hPrivateKey ); 	// Хэндл закрытого ключа
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 	
	for (size_t i=0; i < dataLen; i+=blockLen)
	{
	  size_t len = (dataLen-i) < blockLen? (dataLen-i) : blockLen;
	  // подпись отдельных блоков данных
	  rv = pFunctionList->C_SignUpdate( hSession,			// Хэндл сессии
	  									pData + i,			// Буфер с данными для подписи
	  									len);				// Длина подписываемых данных
											
	  if (rv != CKR_OK)
	  	break;
    }
	
	if (rv != CKR_OK)
	{
	  printf(" -> Failed\n");
	  break;
	}
	printf(" -> OK\n");

	/* Определить размер подписи*/
	printf("C_SignFinal step 1");
	rv = pFunctionList->C_SignFinal(hSession,			// Хэндл сессии
									pbtSignature,		// Буфер с подписью
									&ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtSignature = (CK_BYTE*)malloc(ulSignatureSize);
	if (pbtSignature  == NULL)
	{
		printf("Memory allocation for pbtSignature failed! \n");
		break;
	}
	memset( pbtSignature,
			0,
			ulSignatureSize * sizeof(CK_BYTE));

	/* Подписать исходные данные */
	printf("C_SignFinal step 2");
	rv = pFunctionList->C_SignFinal(hSession,			// Хэндл сессии
									pbtSignature,		// Буфер с подписью
									&ulSignatureSize);	// Длина подписи
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");


Шифрование и расшифрование

Поддерживаемые механизмы

Устройства Рутокен поддерживают следующие механизмы шифрования:

  • CKM_GOST28147_ECB для шифрования алгоритмом ГОСТ 28147-89 в режиме простой замены,
  • CKM_GOST28147 для шифрования алгоритмом ГОСТ 28147-89 в режиме гаммирования с обратной связью (программное и аппаратное),
  • CKM_KUZNECHIK_ECB для шифрования алгоритмом Кузнечик в режиме простой замены (ГОСТ 34.13-2018),
  • CKM_KUZNECHIK_CTR_ACPKM для шифрования алгоритмом Кузнечик в режиме гаммирования CTR с мешингом ACPKM (Р 1323565.1.017-2018),
  • CKM_MAGMA_ECB для шифрования алгоритмом Магма в режиме простой замены (ГОСТ 34.13-2018),
  • CKM_MAGMA_CTR_ACPKM для шифрования алгоритмом Магма в режиме гаммирования CTR с мешингом ACPKM (Р 1323565.1.017-2018),
  • CKM_RSA_PKCS для шифрования алгоритмом RSA.

Внимание!

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

Для того, чтобы шифрование/расшифрование было выполнено аппаратно самим устройством, ключ должен находиться на токене (то есть атрибут CKA_TOKEN используемого для шифрования ключа должен быть равен значению TRUE). Если атрибут CKA_TOKEN ключа, который используется для шифрования/расшифрования, равен FALSE, то операция будет выполняться программно.

Шифрование данных

Для шифрования данных одним блоком служат функции C_EncryptInit() и C_Encrypt(), для поточного – С_EncryptInit(), C_EncryptUpdate() и C_EncryptFinal(). 

Сначала операцию шифрования нужно инициализировать вызовом функции C_EncryptInit(), передав в нее идентификатор сессии, механизма и секретного ключа. В параметрах механизмов для ГОСТ 28147-89 и ГОСТ Р 34.12-2015 можно задать вектор инициализации и его длину.

Далее шифрование можно выполнить для всего блока данных целиком, вызвав функцию C_Encrypt(), или по частям, вызывая  C_EncryptUpdate() для каждого непоследнего блока и  C_EncryptFinal() для завершающего блока. Если в C_EncryptFinal() передать пустой блок данных, то функция вернет суммарный размер всех блоков зашифрованных данных.

Пример шифрования данных по алгоритмам ГОСТ 28147-89 и ГОСТ Р 34.12-2015 

Шифрование данных по алгоритмам ГОСТ 28147-89 ГОСТ Р 34.12-2015 в режиме простой замены
/* Данные для шифрования */
CK_BYTE pbtData[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	                  0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
	                  0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
	                  0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00 };
 
/* Механизм шифрования/расшифрования */
CK_MECHANISM	EncDecMech    		   = 
	{ CKM_GOST28147_ECB, NULL_PTR, 0 }; // по алгоритму ГОСТ 28147-89 в режиме простой замены используйте этот механизм
//    { CKM_KUZNECHIK_ECB, NULL_PTR, 0 }; // по алгоритму Кузнечик в режиме простой замены используйте этот механизм
//    { CKM_MAGMA_ECB, NULL_PTR, 0 };     // по алгоритму Магма в режиме простой замены используйте этот механизм


CK_BYTE_PTR 	pbtEncryptedData 	= NULL_PTR;              // Указатель на буфер, содержащий зашифрованные данные
CK_ULONG 		ulEncryptedDataSize = 0;                     // Размер буфера с зашифрованными данными, в байтах 
 
while(TRUE)
{
	...
	/* Инициализировать операцию шифрования */
	printf("C_EncryptInit");
	rv = pFunctionList->C_EncryptInit(hSession,		// Хэндл сессии
			                          &EncDecMech,	// Механизм шифрования
			                          hSecKey);		// Хэндл секретного ключа
													// Если атрибут CKA_TOKEN равен TRUE, то шифрование будет выполнено аппаратно, 
													// в противном случае - программно.
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Получить размер зашифрованного текста */
	printf("Getting encrypted data size");
	rv = pFunctionList->C_Encrypt(hSession,					// Хэндл сессии 
			                      pbtData,					// Буфер с открытыми данными для шифрования
			                      arraysize(pbtData),		// Длина буфера с открытыми данными, в байтах
			                      NULL,						// Буфер с зашифрованными данными
			                      &ulEncryptedDataSize);	// Длина буфера с зашифрованными данными
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	pbtEncryptedData = (CK_BYTE*)malloc(ulEncryptedDataSize);
	if (pbtEncryptedData == NULL)
	{
		printf("Memory allocation for pbtEncryptedData failed! \n");
		break;
	}
	memset(pbtEncryptedData,
		   0,
		   (ulEncryptedDataSize * sizeof(CK_BYTE)));
	
	/* Зашифровать открытый текст */
	printf("C_Encrypt");
	rv = pFunctionList->C_Encrypt(hSession,					// Хэндл сессии 
			                      pbtData,					// Буфер с открытыми данными для шифрования
			                      arraysize(pbtData),		// Длина буфера с открытыми данными, в байтах
			                      pbtEncryptedData,			// Буфер с зашифрованными данными
			                      &ulEncryptedDataSize);	// Длина буфера с зашифрованными данными
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
 
	printf("Encrypted buffer is:\n");
	for (i = 0;
		 i < ulEncryptedDataSize;
		 i++)
	{
		printf("%02X ", pbtEncryptedData[i]);
		if ((i + 1) % 8 == 0)
			printf("\n");
	}
	break;
}
 
Шифрование данных по алгоритмам ГОСТ 28147-89 в режиме гаммирования с обратной связью и ГОСТ Р 34.12-2015 в режиме гаммирования CTR с мешингом ACPKM
/* Данные для шифрования */
CK_BYTE 	pbtData[] 	= { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	                 	 	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
	                 		0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
	                  		0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00 };
 
CK_BYTE	 	IV[]  	 	= {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};		// Значение вектора инициализации для алгоритма ГОСТ 28147-89 

// Механизмы CKM_KUZNECHIK_CTR_ACPKM и CKM_MAGMA_CTR_ACPKM используют два параметра: 
// период смены ключа и синхропосылку, длина которой равна половине длины блока.
// Параметры задаются в виде последовательно записанного периода смены ключа 32-битного целого, 
// представленного в BigEndian формате и синхропосылки в виде байтового массива.
// Если период смены ключа установлен в нуль, ключ по алгоритму ACPKM не меняется и механизм совпадает с режимом CTR, описанном в ГОСТ 34.13-2018.
CK_BYTE	 	kuznechikEncMechParams[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };	// Параметры алгоритма Кузнечик ГОСТ Р 34.12-2015
CK_BYTE     magmaEncMechParams[]     = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };	                        // Параметры алгоритма Магма ГОСТ Р 34.12-2015


/* Механизм шифрования/расшифрования  */
CK_MECHANISM	EncDecStreamMech   	= 
	{CKM_GOST28147, IV, sizeof(IV)}; // по алгоритму ГОСТ 28147-89 в режиме гаммирования с обратной связью
//    { CKM_KUZNECHIK_CTR_ACPKM, &kuznechikEncMechParams, sizeof(kuznechikEncMechParams) };  // по алгоритму Кузнечик ГОСТ Р 34.12-2015 в режиме гаммирования CTR с мешингом ACPKM
//    { CKM_MAGMA_CTR_ACPKM, &magmaEncMechParams, sizeof(magmaEncMechParams) };              // по алгоритму Магма ГОСТ Р 34.12-2015 в режиме гаммирования CTR с мешингом ACPKM

CK_BYTE_PTR 	pbtEncryptedData 	= NULL_PTR;         // Указатель на буфер, содержащий зашифрованные данные
CK_ULONG 		ulEncryptedDataSize = 0;                // Размер буфера с зашифрованными данными, в байтах 
CK_ULONG 		ulBlockSize 		= 32;				// Размер блока данных, в байтах
CK_ULONG 		ulCurrentPosition 	= 0;				// Текущее начало блока
CK_ULONG 		ulRestLen			= 0;				// Размер оставшегося буфера, в байтах
 
while(TRUE)
{
	...
	/* Инициализировать операцию шифрования */
	printf("C_EncryptInit");
	rv = pFunctionList->C_EncryptInit(hSession,				// Хэндл сессии
			                          &EncDecStreamMech,	// Механизм шифрования
			                          hSecKey);				// Хэндл секретного ключа
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");

	/* Зашифровать открытый текст */
	ulEncryptedDataSize = arraysize(pbtData);
	ulRestLen = arraysize(pbtData);
	pbtEncryptedData = (CK_BYTE*)malloc(ulEncryptedDataSize);
	if (pbtEncryptedData == NULL)
	{
		printf("Memory allocation for pbtEncryptedData failed! \n");
		break;
	}
	memset( pbtEncryptedData,
			0,
			(ulEncryptedDataSize * sizeof(CK_BYTE)));

	while (ulRestLen)
	{
		if (ulBlockSize > ulRestLen)
			ulBlockSize = ulRestLen;
		printf("Block size: %u B (Total: %u of %u) ", ulBlockSize, ulCurrentPosition + ulBlockSize, ulEncryptedDataSize);
		rv = pFunctionList->C_EncryptUpdate(hSession,								// Хэндл сессии
											pbtData + ulCurrentPosition,			// Буфер с блоком данных для шифрования
											ulBlockSize,							// Размер блока, в байтах
											pbtEncryptedData + ulCurrentPosition,	// Буфер с блоком зашифрованных данных
											&ulBlockSize);							// Размер блока, в байтах
		if (rv != CKR_OK)
		{
			printf(" -> Failed\n");
			break;
		}
		printf(" -> OK\n");

		ulCurrentPosition += ulBlockSize;
		ulRestLen -= ulBlockSize;
	}		
	if (rv != CKR_OK)
		break;

	printf("Finalizing encryption");
	rv = pFunctionList->C_EncryptFinal( hSession, 					// Хэндл сессии
										NULL_PTR,					// Буфер с последним блоком данных 
										&ulEncryptedDataSize);		// Длина буфера
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	/* Распечатать буфер, содержащий зашифрованные данные*/
	printf("Encrypted buffer is:\n");
	for (i = 0;
		 i < ulEncryptedDataSize;
	     i++)
	{
		printf("%02X ", pbtEncryptedData[i]);
		if ((i + 1) % 8 == 0)
			printf("\n");
	}

	break;
}

Расшифрование данных

Для расшифрования данных служат функции C_DecryptInit() и C_Decrypt() для любого режима шифрования.

Сначала операцию расшифрования нужно инициализировать вызовом функции C_DecryptInit(), передав в нее идентификатор сессии, механизма и секретного ключа. В параметрах механизма для ГОСТ 28147-89 можно задать вектор инициализации и его длину.

Далее расшифрование выполняется вызовом функции C_Decrypt() с передачей в нее зашифрованные данные. Размер расшифрованных данных можно узнать, вызвав C_Decrypt()  с пустым указателем вместо указателя на буфера для расшифрованных данных.

Пример расшифрования данных по алгоритму ГОСТ 28147-89 

Расшифрование данных по алгоритму ГОСТ 28147-89
while(TRUE)
{
	...
	/* Инициализировать операцию расшифрования */
	printf("C_DecryptInit");
	rv = pFunctionList->C_DecryptInit(hSession,				// Хэндл сессии
			                          &EncDecStreamMech,	// Механизм расшифрования
			                          hSecKey);				// Хэндл секретного ключа
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	
	/* Расшифровать шифротекст */
	printf("Getting decrypted data size");
	rv = pFunctionList->C_Decrypt(hSession,					// Хэндл сессии
			                      pbtEncryptedData,			// Буфер с зашифрованными данными
			                      ulEncryptedDataSize,		// Размер зашифрованных данных
			                      NULL_PTR,					// Буфер с расшифрованными данными
			                      &ulDecryptedDataSize);	// Размер расшифрованных данных
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	pbtDecryptedData = (CK_BYTE*)malloc(ulDecryptedDataSize);
	if (pbtDecryptedData == NULL)
	{
		printf("Memory allocation for pbtDecryptedData failed! \n");
		break;
	}
	memset(pbtDecryptedData,
	       0,
	       (ulDecryptedDataSize * sizeof(CK_BYTE)));
	
	printf("C_Decrypt");
	rv = pFunctionList->C_Decrypt(hSession,					// Хэндл сессии
			                      pbtEncryptedData,			// Буфер с зашифрованными данными
			                      ulEncryptedDataSize,		// Размер зашифрованных данных
	                              pbtDecryptedData,			// Буфер с расшифрованными данными
	                              &ulDecryptedDataSize);	// Размер расшифрованных данных
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n"); 
			
	/* Распечатать буфер, содержащий расшифрованный текст */
	printf("Decrypted buffer is:\n");
	for (i = 0;
		 i < ulDecryptedDataSize;
	     i++)
	{
		printf("%02X ", pbtDecryptedData[i]);
		if ((i + 1) % 8 == 0)
			printf("\n");
	}
	break;
}
 	
if (pbtEncryptedData)
{
	free(pbtEncryptedData);
	pbtEncryptedData = NULL_PTR;
}
if (pbtDecryptedData)
{
	free(pbtDecryptedData);
	pbtDecryptedData = NULL_PTR;
}

Управление устройством

Форматирование токена

Форматирование токена возможно двумя функциями: функцией расширения C_EX_InitToken(), которая полностью очищает память токена и сбрасывает все настройки и PIN-коды, и стандартной функцией C_InitToken(), которая удаляет только объекты PKCS#11.

Все параметры форматирования задаются структурой типа CK_RUTOKEN_INIT_PARAM, указатель на которую вместе с PIN-кодом Администратора передаются функции C_EX_InitToken()

Форматирование Рутокен функцией C_EX_InitToken()
/* Максимальное количество попыток ввода PIN-кода для Администратора */
#define MAX_ADMIN_RETRY_COUNT           10
/* Максимальное количество попыток доступа для Пользователя */
#define MAX_USER_RETRY_COUNT            10    
/* Новый DEMO PIN-код Пользователя Рутокен */
static CK_UTF8CHAR      NEW_USER_PIN[]  = {'5', '5', '5', '5', '5', '5', '5', '5'};
/* DEMO PIN-код Администратора Рутокен */
static CK_UTF8CHAR      SO_PIN[]        = {'8', '7', '6', '5', '4', '3', '2', '1'};
/* DEMO метка Рутокен */
static CK_CHAR TOKEN_STD_LABEL[]        = {"!!!Sample Rutoken label!!!"};
 
CK_RUTOKEN_INIT_PARAM initInfo_st;        // Структура данных типа CK_RUTOKEN_INIT_PARAM, содержащая параметры для работы функции C_EX_InitToken
CK_BBOOL bIsRutokenECP = FALSE;           // Вспомогательная переменная для хранения признака типа токена

/* Заполнение полей структуры CK_RUTOKEN_INIT_PARAM */		
memset(&initInfo_st,
   	   0,
  	   sizeof(CK_RUTOKEN_INIT_PARAM));
 
initInfo_st.ulSizeofThisStructure = sizeof(CK_RUTOKEN_INIT_PARAM); 				// Размер структуры
initInfo_st.UseRepairMode = 0;													// 0 - для форматирования требуются права Администратора, !0 - нет
initInfo_st.pNewAdminPin = SO_PIN;												// Новый PIN-код Администратора
initInfo_st.ulNewAdminPinLen = sizeof(SO_PIN);									// Длина нового PIN-кода Администратора
initInfo_st.pNewUserPin = NEW_USER_PIN;											// Новый PIN-код Пользователя
initInfo_st.ulNewUserPinLen = sizeof(NEW_USER_PIN);								// Длина нового PIN-кода Администратора
initInfo_st.ulMinAdminPinLen = bIsRutokenECP ? 6 : 1;							// Минимальная длина PIN-кода Администратора
initInfo_st.ulMinUserPinLen = bIsRutokenECP ? 6 : 1;							// Минимальная длина PIN-кода Администратора
initInfo_st.ChangeUserPINPolicy = 												// Политика смены PIN-кода Пользователя - Пользователь и Администратор
	(TOKEN_FLAGS_ADMIN_CHANGE_USER_PIN | TOKEN_FLAGS_USER_CHANGE_USER_PIN);
initInfo_st.ulMaxAdminRetryCount = MAX_ADMIN_RETRY_COUNT;						// Максимальное количество неудачных попыток аутентификации Администратора
initInfo_st.ulMaxUserRetryCount = MAX_USER_RETRY_COUNT;							// Максимальное количество неудачных попыток аутентификации Пользователя
initInfo_st.pTokenLabel = TOKEN_STD_LABEL;										// Метка токена
initInfo_st.ulLabelLen = sizeof(TOKEN_STD_LABEL);								// Длина метки токена
 
/* Инициализация токена */
printf("Initializing token");
rv = pFunctionListEx->C_EX_InitToken(aSlots[0],				// Идентификатор слота с подключенным токеном
		                             SO_PIN,				// Текущий PIN-кода Администратора
		                             arraysize(SO_PIN),		// Длина текущего PIN-кода Администратора
		                             &initInfo_st);			// Указатель на структуру с параметрами форматирования
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");

При форматировании стандартной функцией C_InitToken() возможно задать только новый PIN-код Администратора и метку токена. После форматирования следует задать PIN-код Пользователя  функцией C_InitPIN(), поскольку после форматирования через C_InitToken() его значение остается незаданным. 

Форматирование Рутокен функцией C_InitToken()
/* DEMO метка Rutoken */
static CK_CHAR TOKEN_LABEL[]        = { 'M', 'y', ' ', 'R', 'u', 't', 'o', 'k',
                                        'e', 'n', ' ', ' ', ' ', ' ', ' ', ' ',
                                        ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
                                        ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };		
 
while(TRUE)
{
	...
	
	/* Инициализировать токен */
	printf("C_InitToken");
	rv = pFunctionList->C_InitToken(aSlots[0],        // Идентификатор слота с подключенным токеном
		                            SO_PIN,			  // Новый PIN-код Администратора
		                            sizeof(SO_PIN),	  // Длина нового PIN-кода Администратора
		                            TOKEN_LABEL);	  // Новая метка токена
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	...
	
	/* Инициализировать PIN-код Пользователя */
	printf(" Initializing User PIN ");
	rv = pFunctionList->C_InitPIN(hSession,    			// Хэндл открытой сессии с правами Администратора
		                          USER_PIN,				// Новый PIN-код пользователя
		                          sizeof(USER_PIN));    // Длина нового PIN-кода пользователя
	if (rv != CKR_OK)
	{
		printf(" -> Failed\n");
		break;
	}
	printf(" -> OK\n");
	break;
}

Установка и смена локального PIN-кода

Помимо двух глобальных ролей Администратора и Пользователя, устройства Рутокен поддерживают до 29 PIN-кодов Локальных Пользователей для разных технических нужд.

Установка или смена локального PIN-кода должна выполняться Пользователем Рутокен и не требует открытой сессии и авторизации – PIN-код Пользователя передается прямо в функцию C_EX_SetLocalPin().

Установка локального PIN-кода
CK_SLOT_ID  	slotID;				// Идентификатор слота, к которому подключен токен
CK_UTF8CHAR_PTR pUserPin;     		// PIN-код Пользователя Рутокен
CK_ULONG    	ulUserPinLen;		// Длина  PIN-кода Пользователя Рутокен
CK_UTF8CHAR_PTR pNewLocalPin;		// Локальный PIN-код
CK_ULONG    	ulNewLocalPinLen; 	// Длина локального PIN-кода
CK_ULONG    	ulLocalID;         	// Идентификатор локального PIN-кода
 
printf("Setting Local PIN-code");
rv = pfGetFunctionListEx->C_EX_SetLocalPIN( slotID,				// Идентификатор слота, к которому подключен Рутокен
			                  				pUserPin,			// Текущий PIN-код Пользователя Рутокен
			                  				ulUserPinLen,		// Длина текущего PIN-кода Пользователя Рутокен
			                  				pNewLocalPin,		// Новый локальный PIN-код
			                  				&ulNewLocalPinLen, 	// Длина нового локального PIN-кода
											ulLocalID);	    	// Идентификатор локального PIN-кода
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");	

Разблокировка PIN-кода Пользователя

В целях безопасности для пользовательского PIN-кода введен счетчик попыток неправильного ввода. При превышении заданного максимума попыток PIN-код блокируется и дальнейшая аутентификация с правами Пользователя становится невозможной. Сброс счетчика попыток неудачного входа осуществляется функцией C_EX_UnblockUserPIN(), в которую передается хэндл сессии с предварительно выполненным входом с правами Администратора.

Счетчик автоматически сбрасывается при вводе правильного PIN-кода Пользователя, если не был превышен заданный максимум попыток.

Разблокировка токена
/* Разблокировать PIN-код Пользователя */
printf("Unlock User PIN");
rv = pFunctionListEx->C_EX_UnblockUserPIN(hSession); // Хэндл открытой с правами Администратора сессии
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");


Управление памятью Рутокен ЭЦП Flash

Получение объема флеш-памяти

Получить весь объем внешней флеш-памяти можно с помощью функции расширения C_EX_GetDriveSize(), передав в нее идентификатор слота с подключенным токеном и указатель на буфер, в который будет возвращен полученный объем памяти в Мб.

Получение объема флэш-памяти Рутокен ЭЦП Flash
CK_ULONG ulDriveSize = 0;                               // Общий объем флеш-памяти
 
printf("Get Flash memory size");
rv = pFunctionListEx->C_EX_GetDriveSize(aSlots[0],      // Идентификатор слота с подключенным токеном
										&ulDriveSize);  // Возвращаемый размер флеш-памяти в Мб
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
{
	printf(" -> OK\n");
	printf("Memory size: %d Mb\n", (int)ulDriveSize);
}

Создание разделов флеш-памяти

Флеш-память Рутокен ЭЦП Flash может быть разбита на несколько независимых разделов с разными правами доступа к ним. Минимальное количество разделов – 1, максимальное – 8.

Рутокен ЭЦП Flash поддерживает следующие права доступа к разделам: для чтения и записи (ACCESS_MODE_RW), только для чтения (ACCESS_MODE_RO), скрытый раздел, защищенный от отображения в операционной системе, чтения, записи и любого другого типа доступа (ACCESS_MODE_HIDDEN) и раздел, эмулирующий CD-ROM (ACCESS_MODE_CD).

Владельцем раздела может выступать Администратор Рутокен (CKU_SO), Пользователь Рутокен (CKU_USER), а также локальный пользователь (с идентификатором в пределах от 0x03 до 0x1F) с предварительно заданным функцией  C_EX_SetLocalPin() PIN-кодом.

Для разметки флеш-памяти на разделы предназначена функция C_EX_FormatDrive(). Вся информация о разделах (объем памяти, права доступа, владелец и флаги) задается в массиве структур типа CK_VOLUME_FORMAT_INFO_EXTENDED, который затем вместе в PIN-кодом Администратора и идентификатором слота, к которому подключен Рутокен, передается в функцию C_EX_FormatDrive().

Создание разделов флеш-памяти Рутокен ЭЦП Flash
CK_ULONG				VolumeRWSize = 0;              // Размер раздела для чтения и записи
CK_ULONG				VolumeROSize = 0;              // Размер раздела только для чтения
CK_ULONG				VolumeCDSize = 0;              // Размер раздела CD-ROM
CK_ULONG				VolumeHISize = 0;              // Размер раздела скрытого раздела

CK_ULONG			    CKU_LOCAL_1 = 0x03;			   // Идентификатор локального пользователя
CK_ULONG			    CKU_LOCAL_2 = 0x1E;            // Идентификатор локального пользователя
	
/* Шаблон для разметки разделов */
CK_VOLUME_FORMAT_INFO_EXTENDED InitParams[] =
{
	{ VolumeRWSize, ACCESS_MODE_RW, CKU_USER, 0 },
	{ VolumeROSize, ACCESS_MODE_RO, CKU_SO, 0 },
	{ VolumeHISize, ACCESS_MODE_HIDDEN, CKU_LOCAL_1, 0 },
	{ VolumeCDSize, ACCESS_MODE_CD, CKU_LOCAL_2, 0 }
};
 
...

InitParams[0].ulVolumeSize = ulDriveSize / 2;
InitParams[1].ulVolumeSize = ulDriveSize / 4;
InitParams[2].ulVolumeSize = ulDriveSize / 8;
InitParams[3].ulVolumeSize = ulDriveSize - (ulDriveSize / 2) - (ulDriveSize / 4) - (ulDriveSize / 8);
 
printf("\nFormatting flash memory");
rv = pFunctionListEx->C_EX_FormatDrive( aSlots[0],				// Идентификатор слота с подключенным токеном
										CKU_SO,					// Форматирование выполняется только с правами Администратора
										SO_PIN,					// Текущий PIN-код Администратора
										sizeof(SO_PIN),			// Длина PIN-кода Администратора
										InitParams,				// Массив с информацией о разделах
										arraysize(InitParams)); // Размер массива
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");

Получение информации о разделах флеш-памяти

Получить информацию о существующих на флеш-памяти разделах можно с помощью функции C_EX_GetVolumesInfo(), передав в нее идентификатор слота, к которому подключен токен, указатель на буфер и его размер, куда будет возвращен массив структур типа CK_VOLUME_INFO_EXTENDED с информацией о разделах (идентификатор раздела, его размер, права доступа, владелец и флаги). Размер буфера можно определить, вызвав C_EX_GetVolumesInfo() с пустым указателем на буфер.

Получение информации о разделах флеш-памяти Рутокен ЭЦП Flash
CK_VOLUME_INFO_EXTENDED_PTR   pVolumesInfo = NULL_PTR;  // Указатель на массив структур с информацией о разделах
CK_ULONG                      ulVolumesInfoCount = 0;    // Размер массива с информацией о разделах
 
printf("\nGetting volumes info");
rv = pFunctionListEx->C_EX_GetVolumesInfo( aSlots[0],			// Идентификатор слота с подключенным токеном
										   NULL_PTR,			// Указатель на массив с возвращаемой информацией о разделах
										   &ulVolumesInfoCount);// Размер массива 

pVolumesInfo = (CK_VOLUME_INFO_EXTENDED*)malloc(ulVolumesInfoCount * sizeof(CK_VOLUME_INFO_EXTENDED));
memset(pVolumesInfo,
		0,
		(ulVolumesInfoCount * sizeof(CK_ULONG)));

rv = pFunctionListEx->C_EX_GetVolumesInfo(aSlots[0],			// Идентификатор слота с подключенным токеном
										  pVolumesInfo,			// Указатель на массив с возвращаемой информацией о разделах
										  &ulVolumesInfoCount); // Размер массива
if (rv != CKR_OK)
{
	printf(" -> Failed\n");
}
else
{
	printf(" -> OK\n");
	for (i = 0; i < (int)ulVolumesInfoCount; i++)
	{   
		printf("\nPrinting volume %1.2d info:\n", (int)i+1);
		printf(" Volume id:         %2.2d \n", pVolumesInfo[i].idVolume);    	// Идентификатор раздела
	    printf(" Volume size:       %d Mb \n", pVolumesInfo[i].ulVolumeSize);	// Объем раздела
		printf(" Access mode:       %2.2d \n", pVolumesInfo[i].accessMode);		// Права доступа к разделу
		printf(" Volume owner:      %2.2d \n", pVolumesInfo[i].volumeOwner);	// Владелец раздела
		printf(" Flags:             0x%8.8X \n", pVolumesInfo[i].flags);		// Флаги раздела
	}
}

Изменение атрибутов разделов флеш-памяти

Работа с защищенными разделами (с правами доступа только для чтения, скрытым и CD-ROM разделам) сводится к тому, что владелец раздела на время работы с разделом меняет к нему права доступа. Доступно два способа изменения атрибутов раздела: временное и постоянное. Временное изменение меняет права доступа до первого отключения устройства из USB-порта, после чего права доступа сбрасываются на прежние. Постоянное изменение атрибутов доступа действует вплоть до следующего изменения атрибутов.

Для обоих способов изменения атрибутов используется одна функция C_EX_ChangeVolumeAttributes(), в которую передается идентификатор слота с подключенным токеном, владелец раздела и его PIN-код, а также новые права доступа к разделу и флаг временности/постоянности изменения.

Изменение атрибутов разделов флеш-памяти Рутокен ЭЦП Flash
printf("\nChanging volume attributes");
rv = pFunctionListEx->C_EX_ChangeVolumeAttributes(aSlots[0],		// Идентификатор слота с подключенным токеном
													CKU_SO,			// Владелец раздела
													SO_PIN,			// PIN-код владельца раздела
													sizeof(SO_PIN),	// Длина PIN-кода владельца раздела
													VolumeRO,		// Идентификатор раздела
													ACCESS_MODE_RW,	// Новые права доступа к разделу
													CK_TRUE);       // CK_TRUE - постоянное изменение атрибутов, CK_FALSE - временное изменение атрибутов
if (rv != CKR_OK)
	printf(" -> Failed\n");
else
	printf(" -> OK\n");


  • No labels