Page tree
Skip to end of metadata
Go to start of metadata


Введение

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

Рутокен 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);

gostR3410_12DerivationMech.pParameter = keg256DeriveParams;
gostR3410_12DerivationMech.ulParameterLen = sizeof(keg256DeriveParams);

/* Выработать общий ключ ГОСТ 28147-89 на основании закрытого ключа отправителя и открытого ключа получателя */
printf("C_DeriveKey");
rv = pFunctionList->C_DeriveKey(hSession,	// Хэндл открытой с правами Пользователя сессии 
	&gostR3410_12DerivationMech,  			// Механизм ключевого обмена
	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)));

/* Получить значение общего ключа ГОСТ 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");

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

Для получения сеансового ключ (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 до 0x1E) с предварительно заданным функцией  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