Pull to refresh
0
InterSystems
InterSystems IRIS: СУБД, ESB, BI, Healthcare

Использование MS CRYPTO API в Caché

Reading time 10 min
Views 3.9K
Говоря о базах данных, как об источниках знаний, мы всегда подразумеваем, что это не только большой склад разнообразной упорядоченной (или не очень) информации, но и место ее безопасного хранения. Под безопасностью, как правило, понимается защита цифровых данных от несанкционированного доступа при передаче через каналы связи, но не стоит забывать и о физической защите носителей данных. Тем не менее, я не буду рассуждать, какие бронированные двери лучше ставить в вашу серверную и сколько охранников должно дежурить на проходной, а расскажу о криптографии.

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

Внимание, вопрос! Как разработчику, далекому от криптографических премудростей, обеспечить защиту данных в каждом конкретном приложении? Нужно ли придумывать свой собственный гениальный алгоритм шифрования или можно использовать СКЗИ, написанное кем-то другим, и если можно, то как? К счастью, необходимости заново изобретать велосипед нет. Можно, например, воспользоваться единым программным интерфейсом – MS CryptoAPI. В нем описан широкий набор криптографических функций и алгоритмов, которые у различных компаний-разработчиков СКЗИ (также они называются криптопровайдеры или CSP – Cryptography Service Provider) могут быть реализованы по-своему, но для доступа к ним используется единый API.
Для реализации в Вашем приложении криптографических функций нужно сделать следующее:
  • во-первых, определиться, какие алгоритмы криптозащиты Вы хотите использовать. Выбор может зависеть от надежности шифра, от скорости работы, от длины ключа, или он может быть просто прописан в техническом задании.
  • во-вторых, найти, у какого криптопровайдера этот алгоритм реализован, сертифицирован и стоит дешевле.
  • в-третьих, стать счастливым обладателем пакета лицензий на СКЗИ и установить его на Вашем сервере.
  • и, в-четвертых, написать в Вашем решении модуль, позволяющий вызывать функции СКЗИ для работы внутри приложения.

Задачей, которую я ставил перед собой, было пройти каждый из перечисленных шагов, и, в результате получить решение, которое позволит из Caché обращаться к функциям MS Crypto API.
Изучив предлагаемый подход, нетрудно обнаружить, что он имеет два огромных плюса:
  • нам не надо изучать устройство полиномов для формирования собственных алгоритмов шифрования, хеширования или подписи данных
  • многие СКЗИ уже сертифицированы по ГОСТ, а для многих компаний-заказчиков, особенно государственных это – одно из важнейших требований

Есть также один «маленький» минус – работает CryptoAPI исключительно под Windows.

Предположим, что плюсы нас воодушевили, а минусы не разочаровали, но задача вполне конкретна — найти подход к использованию функций CryptoAPI для работы с СУБД Caché. Пусть, CSP уже установлен и настроен: как с ним работать из Caché? Одним из вариантов является использования механизма Callout который реализован в Caché. О нем далее и пойдет речь.

Callout предназначен для вызова в Caché функций из библиотек DLL. Фактически это означает, что можно создавать код, например на C++, компоновать его в DLL, и затем использовать в своих решениях. Также такой подход позволяет использовать при создании DLL разнообразные возможности API Windows, в частности, CryptoAPI.

На этапе планирования я сразу решил создавать не только саму DLL (называться она будет ISCAPI), но и средство тестирования и отладки (CryptoConsole).

На рисунке ниже это представлено графически. Есть базовый класс CacheCommon (С++), в котором реализованы функции, позволяющие производить настройку криптопровайдера, инициализацию СКЗИ, получение контекста, ключей, хеширование данных, создание и проверку цифровой подписи, шифрование, логирование и многое другое. Все эти функции единообразно используются в отладочной консоли и библиотеке (консоль и DLL написаны на C++). Единая форма обращений играет особую роль, ведь DLL в Caché отлаживать весьма проблематично, поэтому основная часть нагрузки при разработке, отладке и тестировании возлагается на консоль. Код всего решения открыт, найти его можно здесь.

image

На стороне сервера Caché взаимодействие с ISCAPI.DLL осуществляется через класс iscapi.Signer

Текст класса iscapi.Signer
/// Функции для работы с крипто-провайдерами через MS CRYPTO API
Class iscapi.Signer Extends %RegisteredObject
{

/// Загрузка DLL
/// 		dllPath			- полный путь к библиотеке
ClassMethod LoadDLL(dllPath As %String) As %Status
{
	s result = $$$OK
	if (dllPath = "") {
		w "Please set dllPath equal to path to the ISCAPI.dll"
		q $$$ERROR($$$GeneralError, "No path to iscapi.dll is provided")
	}
	try {
		d $zf(-3, dllPath)
	}
	catch (ex) {
		s result = ex.AsStatus()	
	}
	
	if (result=1)
		{w "DLL from "_dllPath_" was loaded"}
	else
		{w "Cannot load DLL from "_dllPath}
	
	q result
}

/// Выгрузка DLL
ClassMethod UnloadDLL()
{
	d $zf(-3, "")
}

/// Инициализация крипто-провайдера.
/// 		provType		- тип провайдера (VipNet=2, CryptoPro=75)
/// 		algId			- используемый алгоритм (32798)
/// 		containerName 	- полное имя контейнера с ключами
/// 		pin				- пароль к контейнеру (если не указан, CSP будет запршивать его в интерактивном режиме, что не всегда возможно)
/// 		providerName	- имя крипто-провайдера
ClassMethod Init(provType = 75, algId = 32798, containerName As %String, pin As %String = "111111", providerName As %String = "") As %Status
{
	s result = $$$OK
	try {
		d $zf(-3, "", "Init", provType, algId, containerName, pin, providerName)
	}
	catch (ex) {
		s result = ex.AsStatus()	
	}
	
	if (result=1)
		{w "CSP was successfully initialized"}
	else
		{w "Error during CSP initialization"}
	q result
}

/// Инициализация протоколирования.
/// 	logFileName 	- имя файла протокола. Должно быть право на запись.
/// 		logLevel 		- уровень протоколирования
/// 			0 - ничего
/// 			1 - только ошибки
/// 			2 - все сообщения
/// 		logTargets		- устройства, в которые нужно писать протокол
/// 			0 - не писать никуда
/// 			1 - писать в файл
/// 			2 - выводить на консоль
/// 			3 - файл и консоль
ClassMethod InitLogger(logFileName As %String = "c:\iscapi.log", logLevel As %Integer = 2, logTargets As %Integer = 3) As %Status
{
	s result = $$$OK
	try {
		d $zf(-3, "", "InitLogger", logFileName, logLevel, logTargets)
	}
	catch ex {
		s result = ex.AsStatus()
	}
	
	if (result=1)
		{w "Logger was successfully initialized"}
	else
		{w "Error during Logger initialization"}
	q result
}

/// Хеширование порции данных.
/// Можно вызывать в цикле для хеширования потока по чанкам, каждая порция данных будет прибавляться к хешу.
ClassMethod HashData(dataPortion As %String) As %Status
{
	s result = $$$OK
	try {
		d $zf(-3, "", "HashData", dataPortion)
	}
	catch ex {
		s result = ex.AsStatus()
	}
	q result
}

/// Хеширование файла.
ClassMethod HashFile(fileName As %String) As %Status
{
	s result = $$$OK
	try {
		d $zf(-3, "", "HashFile", fileName)
	}
	catch ex {
		s result = ex.AsStatus()
	}
	q result
}

/// Возвращает значение хеша.
/// После вызова хеш сохраняется
ClassMethod GetHashValue() As %String
{
	s result = ""
	try {
		s result = $zf(-3, "", "GetHashValue", "")
	}
	catch ex {
		w "GHV exception", !
		zw ex
		s result = ""
	}
	w "GHV result is:", result, !
	q result
}

ClassMethod ExportUserKey() As %String
{
	s result = ""
	try {
		s result = $zf(-3, "", "ExportUserKey", "")
	}
	catch ex {
		s result = ""
	}
	q result
}

/// Экспорт открытого ключа.
/// Подпись хеша.
ClassMethod SignNewHash(dataPortion As %String) As %String
{
	s result = ""
	try {
		s result = $zf(-3, "", "SignNewHash", dataPortion, "")
	}
	catch ex {
		s result = ""
	}
	q result
}

/// Подпись текущего объекта хеша.
ClassMethod SignCurrentHash() As %String
{
	s result = ""
	try {
		s result = $zf(-3, "", "SignCurrentHash", "")
	}
	catch ex {
		s result = ""
	}
	w "Signature recieved: ",result,!
	q result
}

/// Проверка подписи хэша.
ClassMethod VerifyHash(hash As %String, sign As %String) As %Boolean
{
	s result = 0
	try {
		s result = $zf(-3, "", "VerifyHash", hash, sign, 0)
	}
	catch ex {
		s result = 0
	}
	q result
}

ClassMethod VerifyHashByKey(hash As %String, sign As %String, pubKey As %String) As %Boolean
{
	s result = 0
	try {
		s result = $zf(-3, "", "VerifyHashByKey", hash, sign, pubKey, 0)
	}
	catch ex {
		s result = 0
	}
	q result
}

/// Проверка подписи хэша по открытому ключу.
/// Проверка подписи данных
ClassMethod VerifySignature(dataPortion As %String, sign As %String) As %Boolean
{
	s result = 0
	try {
		s result = $zf(-3, "", "VerifySignature", dataPortion, sign, 0)
	}
	catch ex {
		s result = 0
	}
	q result
}

/// Освобождение ресурсов и выгрузка DLL
ClassMethod ReleaseAll() As %Status
{
	s result = $$$OK
	try {
		d $zf(-3, "", "ReleaseAll")
		d ..UnloadDLL()
	}
	catch ex {
		s result = ex.AsStatus()
	}
	q result
}

/// Преобразование бинарной строки в HEX
/// TODO: rewrite
ClassMethod ByteToHex(bString As %String) As %String
{
	s str = ""
	for i=1:1:$l(bString) {
		s hex = $zhex($ascii($e(bString, i)))
		if ($l(hex) = 1) s hex = "0" _ hex
		s str = str _ hex
	}
	q str
}

ClassMethod HexToString(value As %String) As %String
{
	s str = ""
	for i=1:2:$l(value) {
		s hex = $e(value, i, i + 1)
		s str = str _ $c($zhex(hex))
	}
	
	q str
}

/// Выгрузка DLL
ClassMethod PrintProviders() As %Status
{
		s result = $$$OK
	try {
			d $zf(-3, "", "PrintProviders")
	}
	catch ex {
		s result = ex.AsStatus()
	}
	q result
}

/// Тестовый прогон
ClassMethod Test()
{
	s data = "123!"
	
	d ..LoadDLL("C:\ISCAPI.dll")
	w "DLL loaded", !
	d ..InitLogger("c:\iscapiL.txt", 2, 1)
	w "Logger initialized", !	
	d ..PrintProviders()
	d ..Init(75, 32798, "CacheCrypt", "", "Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider")
	w "CSP initialized", !
	d ..HashData(data)
	w "Hash created on: ", data, !
	
	s hash = ..GetHashValue()	
	w "Hash received, hash length=", $l(hash), !
	w "Hash to base64:", !, $system.Encryption.Base64Encode(hash), !
	w "Hash to HEX:", !, ..ByteToHex(hash), !
	w "Hash value:", hash, !
	
	s sign = ..SignCurrentHash()
	w "Hash signed, sign length=", $l(sign), !
	w "Sign to base64:", !, $system.Encryption.Base64Encode(sign), !
		
	w "Sign to HEX:", !, ..ByteToHex(sign), !

	s vfy = ..VerifyHash(hash, sign)
	w "Verifying Hash signature result = ", vfy, !
	
	s vfy = ..VerifySignature(data, sign)
	w "Verifying Signature by input text result = ", vfy, !
	
	w "Exporting User Key...",!
	s userKey = ..ExportUserKey()
	w "Size: ", $l(userKey), !	
	w "UserKeyBytes: ", ..ByteToHex(userKey), !
	
	s vfy = ..VerifyHashByKey(hash, sign, userKey)
	w "Verifying Hash signature ByKey result = ", vfy, !

	
	d ..ReleaseAll()
}

}



Здесь реализован механизм callout, а значит можно прямо из методов классов Caché обращаться к функциям CryptoAPI, существующим в DLL. Доступ реализуется через вызов класс-методов iscapi.Signer из Caché Object Script. Кроме всего прочего, это еще и познавательный пример для начинающих разработчиков, позволяющий постичь особенности работы с внешними библиотеками из Caché.

Теперь хочу сказать несколько слов о тех функциях, которые были реализованы. Удобнее всего проверить их из консольного приложения (CryptoConsole.exe). Сразу после её запуска можно увидеть все доступные команды и порядок их вызова. Чтобы ввести параметры для функции, просто перечислите их, разделив пробелом. Длинные параметры, состоящие из нескольких слов, как обычно, обрамляются кавычками.
Команды консоли можно условно разделить на три типа:
1. Сервисные команды
Типичный пример – команда help, выводящая список всех доступных команд в процессе работы. К сервисным также относятся команды showProviders и showProvParams, выводящие пользователю данные об установленных в системе криптопровайдерах.
2. Команды инициализации
Инициализационные команды нужны для настройки и запуска криптопровайдера. В общем случае для инициализации его контекста нужно задать:
  • номер типа криптопровайдера (команда provID). Это число, которое почти ничего не значит, но помогает системе идентифицировать, с каким именно CSP Вы хотите работать.
  • номер алгоритма (команда signAlgID). Вообще говоря, алгоритмов может быть несколько, в данном случае целесообразно задавать алгоритм хеширования и подписывания данных.
  • имя контейнера ключей (команда contName).
  • пин-код от контейнера ключей (команда contPIN). Настройки ключевого контейнера производятся в процессе установки и настройки криптопровайдера.
  • полное название используемого CSP (команда provName). Как правило, это длинная строка. Нужна она для того, чтобы система поняла, с каким именно криптопровайдером мы собираемся работать, и является дополнением типу CSP, который в одиночку не может его уникально идентифицировать.

Существуют и дополнительные параметры которые можно задавать системе, например коды алгоритмов шифрования, но для первичной инициализации CSP необходимы эти пять.
3. Команды криптографии
Когда CSP проинициализирован, мы должны получить контекст криптопровайдера (команда aContext), а после этого можно создавать хеши, подписывать данные, шифровать – то есть, делать все то, для чего этот пакет и создавался.
Основные команды:
  • hashData, хеширует строку данных
  • hashFile, хеширует файл данных
  • signCurrentHash, подписывает текущий рассчитанный хеш
  • signNewHash, создает новый хеш в системе и подписывает его
  • verifyHash, проверяет соответствие подписи хешу
  • verifySignature, проверяет соответствие подписи исходным данным
  • encryptData, шифрует данные в строке
  • decryptData, дешифрует ранее зашифрованные данные.

Список команд на самом деле гораздо шире, чем приведено здесь. И дело не только в том, что приложение постоянно находится в стадии разработки. При отладке и тестировании очень выгодно писать временные функции-сценарии, которые вызывают последовательно несколько связанных процедур, проверяя конечный результат, или производят инициализацию CSP заранее прописанными в коде значениями.
Как правило, эти функции редко документируются, но если посмотреть на код приложения – они бросаются в глаза.

Если Вы хотите добавить новые функции, сначала реализуйте их в CacheCommon, протестируйте с помощью консольного приложения, затем добавьте в DLL, и, наконец, откройте доступ к ним через iscapi.Signer. Функции в динамической библиотеке желательно называть так же, как и в консольном приложении. Обратите внимание на то, что в коде DLL в блоке «ZFBEGIN… ZFEND» обязательно присутствует перечисление всех доступных процедур.

Набор функций, которые реализованы на текущий момент невелик, но я и не ставил задачу сделать абсолютно все. Уже сейчас можно создать хеш данных, подписать их и проверить цифровую подпись. По крайней мере есть основа, которая, которая позволит каждому заинтересованному разработчику быстро настроить криптопровайдер, инициализировать его контекст и начать использовать. Добавление новых функций не будет занимать много времени, поскольку выполняется по аналогии с уже существующими.
Хочу пригласить всех Caché-разработчиков использовать результаты моего проекта, а также принять участие в его развитии.
Tags:
Hubs:
+6
Comments 0
Comments Leave a comment

Articles

Information

Website
www.intersystems.com
Registered
Founded
1978
Employees
1,001–5,000 employees
Location
США