Использование средств шифрования .Net из 1С

Материал из КинтВики
Перейти к: навигация, поиск


Шифрование в .Net

Среда .Net предоставляет достаточно полный и удобный набор средств симметричного (DES,AES,RC2..) и асимметричного (RSA) шифрования.

Эти средства собраны в пространстве имен System.Security.Cryptography уже начиная с релиза 1.1 .Net. На современных компьютерах практически гарантированно стоит та или иная версия .Net.

Благодаря тому, что значительная часть управляемого кода .Net прекрасно работает через шлюзы COM-interop, средства шифрования .Net можно использовать из скриптовых языков, поддерживающих COM-automation. Это можно делать из vbscript, jscript и из 1С.


Пример использования RSA-шифрования

По умолчанию используются 1024-битные ключи RSA, что считается на сегодняшний день вполне надежным
Процедура Тест_ОбъектRSAиз1С_2() Экспорт
	strSrcTextValue = "abc тест";
	
	objCrypt = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	strXMLPrivateAndPublicKey = objCrypt.ToXmlString(True);
	strXMLPublicKey = objCrypt.ToXmlString(False);
	
	objCrypt.FromXmlString(strXMLPublicKey);
	
	binSrcTextValue = UtfToSafeArray(strSrcTextValue);
	binEncryptValue = objCrypt.Encrypt(binSrcTextValue, False);
	
	strEncryptValue = SafeArrayToBase64(binEncryptValue);
	
	Тестирование.ПроверитьНеРавенство(strSrcTextValue, strEncryptValue);

	objCrypt.FromXmlString(strXMLPrivateAndPublicKey);
	
	binEncryptValue2 = Base64ToSafeArray(strEncryptValue);
	binDecryptValue = objCrypt.Decrypt(binEncryptValue2, False);
	
	
	strDecryptValue = SafeArrayToUtf(binDecryptValue);
	
	Сообщить("Decrypt: " + strDecryptValue);
	
	Тестирование.ПроверитьРавенство(strSrcTextValue, strDecryptValue);
	objCrypt = Неопределено;
КонецПроцедуры

Функции поддержки преобразования параметров

Одна из основных сложностей состоит в преобразовании строк и бинарных данных в массивы COMSafaArray с типом элементов "VT_UI1". Возможная реализация приведена ниже:

Функция UtfToSafeArray(строка) Экспорт 
	adTypeBinary = 1;
	adTypeText = 2;
	adReadAll = -1;
	csUTF8 = "utf-8";
	
	objStream = Новый COMОбъект("ADODB.Stream");
	
	objStream.Open();
	objStream.Type = adTypeText;
	objStream.Charset = csUTF8;
	objStream.WriteText(строка);
	
	objStream.Position = 0;
	objStream.Type = adTypeBinary;
	
	bin = objStream.Read(adReadAll);
	
	objStream.Close();
	objStream = Неопределено;
	
	Return bin;
КонецФункции

Функция SafeArrayToUtf(bin) Экспорт 
	adTypeBinary = 1;
	adTypeText = 2;
	adReadAll = -1;
	csUTF8 = "utf-8";
	
	objStream = Новый COMОбъект("ADODB.Stream");
	objStream.Open();
	objStream.Type = adTypeBinary;
	objStream.Write(bin);
	
	objStream.Position = 0;
	objStream.Type = adTypeText;
	objStream.Charset = csUTF8;
	
	str = objStream.ReadText(adReadAll);
	
	objStream.Close();
	objStream = Неопределено;
	
	Return str;
КонецФункции

Функция SafeArrayToBase64(bin) Экспорт 
	ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
	
	adTypeBinary = 1;
	adSaveCreateNotExist = 1;
	objStream = Новый COMОбъект("ADODB.Stream");
	objStream.Open();
	objStream.Type = adTypeBinary;
	objStream.Write(bin);
	objStream.SaveToFile(ИмяВременногоФайла, adSaveCreateNotExist);
	objStream.Close();
	objStream = Неопределено;
	
	ДвоичныеДанные = Новый ДвоичныеДанные(ИмяВременногоФайла);
	УдалитьФайлы(ИмяВременногоФайла);

	Возврат Base64Строка(ДвоичныеДанные);
КонецФункции

Функция Base64ToSafeArray(str) Экспорт 
	ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
	ДвоичныеДанные = Base64Значение(str);
	ДвоичныеДанные.Записать(ИмяВременногоФайла);
	
	adTypeBinary = 1;
	adReadAll = -1;
	objStream = Новый COMОбъект("ADODB.Stream");
	objStream.Open();
	objStream.Type = adTypeBinary;
	objStream.LoadFromFile(ИмяВременногоФайла);
	bin = objStream.Read(adReadAll);
	objStream.Close();
	objStream = Неопределено;
	
	УдалитьФайлы(ИмяВременногоФайла);
	Возврат bin;
КонецФункции

Пример использования симметричного шифрования AES

Процедура Тест_AESРасшифровать() Экспорт 
	saKey = Неопределено;
	saIV = Неопределено;
	AESСоздатьКлюч(saKey, saIV);
	
	strSrc = "тест test";
	binSrc = UtfToSafeArray(strSrc);
	
	binEnc = AESЗашифровать(saKey, saIV, binSrc);
	binDec = AESРасшифровать(saKey, saIV, binEnc);
	
	strDec = SafeArrayToUtf(binDec);
	
	Тестирование.ПроверитьРавенство(strSrc, strDec);
КонецПроцедуры


Процедура AESСоздатьКлюч(saKey, saIV, KeySize = 128) Экспорт
	aes = Новый COMОбъект("System.Security.Cryptography.RijndaelManaged");
	aes.KeySize = KeySize;
	aes.GenerateKey();
	aes.GenerateIV();
	saKey = aes.Key;
	saIV = aes.IV;
	aes = Неопределено;
КонецПроцедуры

Функция AESЗашифровать(saKey, saIV, binSrc) Экспорт
	aes = Новый COMОбъект("System.Security.Cryptography.RijndaelManaged");
	aes.Key = saKey;
	aes.IV = saIV;
	encryptor = aes.CreateEncryptor();
	binEnc = encryptor.TransformFinalBlock(binSrc, 0, binSrc.GetLength());
	aes = Неопределено;
	Возврат binEnc;
КонецФункции


Функция AESРасшифровать(saKey, saIV, binEnc) Экспорт
	aes = Новый COMОбъект("System.Security.Cryptography.RijndaelManaged");
	aes.Key = saKey;
	aes.IV = saIV;
	Decryptor = aes.CreateDecryptor();
	binDec = decryptor.TransformFinalBlock(binEnc, 0, binEnc.GetLength());
	aes = Неопределено;
	Возврат binDec;
КонецФункции

Пример использования цифровой подписи RSA

Процедура Тест_RSAПроверитьПодпись() Экспорт 
	strSrc = "тест test";
	binSrc = UtfToSafeArray(strSrc);
	
	ОткрытыйКлючОтправителя = "";
	ЗакрытыйКлючОтправителя = "";
	RSAСоздатьКлючи(ОткрытыйКлючОтправителя, ЗакрытыйКлючОтправителя);
	
	binSign = RSAПодписать(ЗакрытыйКлючОтправителя, binSrc);
	Тестирование.ПроверитьРавенство(128, binSign.GetLength());
	
	результат = RSAПроверитьПодпись(ОткрытыйКлючОтправителя, binSrc, binSign);
	Тестирование.ПроверитьИстину(результат);
КонецПроцедуры



Процедура RSAСоздатьКлючи(ОткрытыйКлюч, ЗакрытыйКлюч) Экспорт 
	objCrypt = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	ЗакрытыйКлюч = objCrypt.ToXmlString(True);
	ОткрытыйКлюч = objCrypt.ToXmlString(False);
	objCrypt = Неопределено;
КонецПроцедуры

Функция RSAПодписать(ЗакрытыйКлючОтправителя, binSrc) Экспорт
	objCrypt = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	objCrypt.FromXmlString(ЗакрытыйКлючОтправителя);
	
	objHash = Новый COMОбъект("System.Security.Cryptography.SHA1CryptoServiceProvider");
	hash = objHash.ComputeHash_2(binSrc);
	
	binSign = objCrypt.SignHash(hash, "SHA1");
	
	objCrypt = Неопределено;
	objHash = Неопределено;
	
	Возврат binSign;
КонецФункции


Функция RSAПроверитьПодпись(ОткрытыйКлючОтправителя, binSrc, binSign) Экспорт
	objCrypt = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	objCrypt.FromXmlString(ОткрытыйКлючОтправителя);
	
	objHash = Новый COMОбъект("System.Security.Cryptography.SHA1CryptoServiceProvider");
	hash = objHash.ComputeHash_2(binSrc);
	
	результат = objCrypt.VerifyHash(hash, "SHA1", binSign);
	
	objCrypt = Неопределено;
	objHash = Неопределено;
	
	Возврат результат;
КонецФункции

Совместное использование асимметричного и симметричного шифрования

При использовании алгоритмов асимметричного шифрования, таких как RSA, есть определенные ограничения:

  • работает медленно, примерно в 100-1000 раз медленнее чем симметричное шифрование
  • при реальных размерах ключа (1024 бита и более) приходится ограничивать размер сообщения, иначе алгоритмы требуют слишком много ресурсов (памяти и процессора)
  • практически в реализации .Nеt при размере ключа 1024 бит максимальный размер сообщения RSA ограничен 117 байтами
  • "многократное" применение RSA (то есть нарезка исходного сообщения по 100 байт и шифрование их по отдельности) не рекомендуется, так как это резко снижает потенциальную стойкость алгоритма

С учетом этих ограничений все реальные схемы использования асимметричных ключей работают примерно так:

  • генерируется сеансовый ключ для какого-либо симметричного алгоритма
  • сеансовый ключ шифруется схемой RSA
  • сообщение шифруется сеансовым ключом

Пример совместного использования асимметричного и симметричного шифрования с подписью и верификацией данных:

Процедура Тест_RSAРаспаковать() Экспорт 
	ОткрытыйКлючПолучателя = "";
	ЗакрытыйКлючПолучателя = "";
	RSAСоздатьКлючи(ОткрытыйКлючПолучателя, ЗакрытыйКлючПолучателя);
	
	ОткрытыйКлючОтправителя = "";
	ЗакрытыйКлючОтправителя = "";
	RSAСоздатьКлючи(ОткрытыйКлючОтправителя, ЗакрытыйКлючОтправителя);
	
	НаДату = ТекущаяДата();
	стДанные = Новый Структура("Наименование,Дата", "Тестовая организация", НаДату);
	
	стрПакет = RSAУпаковать(ОткрытыйКлючПолучателя, ЗакрытыйКлючОтправителя, стДанные);
	
	стДанные1 = RSAРаспаковать(ЗакрытыйКлючПолучателя, ОткрытыйКлючОтправителя, стрПакет);
	
	Тестирование.ПроверитьРавенство(Тип("Структура"), ТипЗнч(стДанные1));
	Тестирование.ПроверитьРавенство("Тестовая организация", стДанные1.Наименование);
	Тестирование.ПроверитьРавенство(НаДату, стДанные1.Дата);
	
КонецПроцедуры

Процедура RSAСоздатьКлючи(ОткрытыйКлюч, ЗакрытыйКлюч) Экспорт 
	objCrypt = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	ЗакрытыйКлюч = objCrypt.ToXmlString(True);
	ОткрытыйКлюч = objCrypt.ToXmlString(False);
	objCrypt = Неопределено;
КонецПроцедуры


Функция RSAУпаковать(ОткрытыйКлючПолучателя, ЗакрытыйКлючОтправителя, Данные1С) Экспорт
	Если ЗначениеЗаполнено(ЗакрытыйКлючОтправителя) Тогда
		rsaОтправителя = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
		rsaОтправителя.FromXmlString(ЗакрытыйКлючОтправителя);
	Иначе
		rsaОтправителя = Неопределено;
	КонецЕсли;
	
	
	rsaПолучателя = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	rsaПолучателя.FromXmlString(ОткрытыйКлючПолучателя);
	
	sha1 = Новый COMОбъект("System.Security.Cryptography.SHA1CryptoServiceProvider");
	
	стрДанные = ЗначениеВСтрокуВнутр(Данные1С);
	saДанные = UtfToSafeArray(стрДанные);
	
	aes = Новый COMОбъект("System.Security.Cryptography.RijndaelManaged");
	aes.KeySize = 128;
	aes.GenerateKey();
	aes.GenerateIV();
	encryptor = aes.CreateEncryptor();
	
	saКлючСеанса = saСоединить(aes.IV, aes.Key);
	saEncКлючСеанса = rsaПолучателя.Encrypt(saКлючСеанса, False);
	
	saHash = sha1.ComputeHash_2(saДанные);
	
	Если rsaОтправителя = Неопределено Тогда
		saПодпись = Новый COMSafeArray("VT_UI1", 0);
	Иначе
		saПодпись = rsaОтправителя.SignHash(saHash, "SHA1");
	КонецЕсли;
	
	saEncПодпись = encryptor.TransformFinalBlock(saПодпись, 0, saПодпись.GetLength());
	
	saEncДанные = encryptor.TransformFinalBlock(saДанные, 0, saДанные.GetLength());
	
	saПакет = saСобратьПакет(saEncКлючСеанса, saEncПодпись, saEncДанные);
	
	стрПакет = SafeArrayToBase64(saПакет);
	
	rsaОтправителя = Неопределено;
	rsaПолучателя = Неопределено;
	sha1 = Неопределено;
	aes = Неопределено;
	
	Возврат стрПакет;
КонецФункции

Функция RSAРаспаковать(ЗакрытыйКлючПолучателя, ОткрытыйКлючОтправителя, стрПакет) Экспорт
	Если ЗначениеЗаполнено(ОткрытыйКлючОтправителя) Тогда
		rsaОтправителя = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
		rsaОтправителя.FromXmlString(ОткрытыйКлючОтправителя);
	Иначе
		rsaОтправителя = Неопределено;
	КонецЕсли;
	
	rsaПолучателя = Новый COMОбъект("System.Security.Cryptography.RSACryptoServiceProvider");
	rsaПолучателя.FromXmlString(ЗакрытыйКлючПолучателя);
	
	sha1 = Новый COMОбъект("System.Security.Cryptography.SHA1CryptoServiceProvider");
	
	aes = Новый COMОбъект("System.Security.Cryptography.RijndaelManaged");
	
	saПакет = Base64ToSafeArray(стрПакет);
	
	смещение = 0;
	saEncКлючСеанса = saПолучитьБлок(saПакет, смещение);
	saEncПодпись = saПолучитьБлок(saПакет, смещение);
	saEncДанные = saПолучитьБлок(saПакет, смещение);
	
	saКлючСеанса = rsaПолучателя.Decrypt(saEncКлючСеанса, False);
	saIV = saВыделитьДиапазон(saКлючСеанса, 0, 16);
	saKey = saВыделитьДиапазон(saКлючСеанса, 16, saКлючСеанса.GetLength() - 16);
	aes.Key = saKey;
	aes.IV = saIV;
	decryptor = aes.CreateDecryptor();
	
	saПодпись = decryptor.TransformFinalBlock(saEncПодпись, 0, saEncПодпись.GetLength());
	saДанные = decryptor.TransformFinalBlock(saEncДанные, 0, saEncДанные.GetLength());
	
	Если rsaОтправителя <> Неопределено Тогда
		saHash = sha1.ComputeHash_2(saДанные);
		ПодписьКорректна = rsaОтправителя.VerifyHash(saHash, "SHA1", saПодпись);
		Если НЕ ПодписьКорректна Тогда
			ВызватьИсключение "Обнаружено разрушение данных: некорректная цифровая подпись";
		КонецЕсли;
	КонецЕсли;
	
	стрДанные = SafeArrayToUtf(saДанные);
	Данные1С = ЗначениеИзСтрокиВнутр(стрДанные);
	
	rsaОтправителя = Неопределено;
	rsaПолучателя = Неопределено;
	sha1 = Неопределено;
	aes = Неопределено;
	
	Возврат Данные1С;
КонецФункции


Функция saПолучитьБлок(saПакет, смещение)
	старшийБайт = saПакет.GetValue(смещение + 0);
	младшийБайт = saПакет.GetValue(смещение + 1);
	длина = старшийБайт * 256 + младшийБайт;
	
	saБлок = Новый COMSafeArray("VT_UI1", длина);
	
	смещение = смещение + 2;
	Для индекс = 0 по длина - 1 Цикл
		saБлок.SetValue(индекс, saПакет.GetValue(индекс + смещение));
	КонецЦикла;
	смещение = смещение + длина;
	
	Возврат saБлок;
КонецФункции

Функция saВыделитьДиапазон(saПакет, смещение, длина)
	saБлок = Новый COMSafeArray("VT_UI1", длина);
	
	Для индекс = 0 по длина - 1 Цикл
		saБлок.SetValue(индекс, saПакет.GetValue(индекс + смещение));
	КонецЦикла;

	Возврат saБлок;
КонецФункции

Процедура saДобавитьДанные(saПакет, saДанные, смещение)
	длина = saДанные.GetLength();
	saПакет.SetValue(смещение + 0, Цел(длина / 256));
	saПакет.SetValue(смещение + 1, длина % 256);
	смещение = смещение + 2;
	Для индекс = 0 по длина - 1 Цикл
		saПакет.SetValue(смещение + индекс, saДанные.GetValue(индекс));
	КонецЦикла;
	смещение = смещение + длина;
КонецПроцедуры

Функция saСобратьПакет(saКлюч, saПодпись, saДанные) Экспорт 
	saПакет = Новый COMSafeArray("VT_UI1", saКлюч.GetLength() + saПодпись.GetLength() + saДанные.GetLength() + 6);
	
	смещение = 0;
	
	saДобавитьДанные(saПакет, saКлюч, смещение);
	saДобавитьДанные(saПакет, saПодпись, смещение);
	saДобавитьДанные(saПакет, saДанные, смещение);
	
	Возврат saПакет;
КонецФункции

Функция saСоединить(sa1, sa2)
	длина1 = sa1.GetLength();
	длина2 = sa2.GetLength();
	saРезультат = Новый COMSafeArray("VT_UI1", длина1 + длина2);
	
	Для  индекс = 0 по длина1 - 1 Цикл
		saРезультат.SetValue(индекс, sa1.GetValue(индекс));
	КонецЦикла;
	
	Для индекс = 0 по длина2 - 1 Цикл
		saРезультат.SetValue(индекс + длина1, sa2.GetValue(индекс));
	КонецЦикла;
	
	Возврат saРезультат;
КонецФункции


Пример упакованного пакета данных

AICMxryOHYJJuxYufTP4X643HVamNgUuQ4lcY/L1YXk6HkQ9LnL02h/ERIgKwDSX
pFJqTVisEF3Y+BORPJGyDlSeJ/Z/qwnX606AfTQxgEkB1qfW72a86DYyhG6kvjbY
Kc4e9QIW1Za6HJgkw1hjXr85meXIibOVm4BfNDZCvYmRswCQemALjNne1dacqmW4
RzcASMLxLZciFVXDBQB+LbuojpdY5Vn++hpzpWt1vh/Mj4bdeEHNGg3p229kbOBW
Iog5zVHpj+mKADK8IeLKZwYWghP19bAo4cMVJ+zzceMjzLCAWQ+c1GI6aq6FRcyJ
Dh07lzyvDfcw1iS0AbZg23Y5BRW9zCpg5xt4RX2vcAhFHO0iAMC+6/jZofsIY72g
YukEgKWm9TSDhiRMcozbrCjv4Gug+aPXUAK0gvrS42C/ITynTyrtTE1erHFWXmCt
VhAdpiE5sE6QFI8LYFB4pcPAqOooFEj/kBUQHEArJyyacWazs2/jTLCv1SmLrKjs
tTZhafQ2y9pEBGvfqsG4YhKSjA7SVJzgtfcgAi76bHiWyURGQqiqNym1WpXp9WO/
eckaQFAi4rqYpt0Qn93G3/iJ/Wq+hPMN/z9c6UqmQtbm7/Tp0f4=


Пример xml-сериализации открытого ключа RSA

<RSAKeyValue>
<Modulus>u7ptm/rGsEhZ3JhKApnDPUVj6bPnSYmVzGkMrAsElT2CIRD6zWbA0+GcoQ5qCz/ZTeCeDBgCIhDkACdy9Q+kjl1HGlwWS8OGCaOLt
/14zXuYoCeSZMP3sJMI7BuzRCvu5NBduxfZYC75UnKzO29WjXIxF5h32U8nArv6WivhPRs=</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>

Пример xml-сериализации закрытого ключа RSA:

<RSAKeyValue>
<Modulus>u7ptm/rGsEhZ3JhKApnDPUVj6bPnSYmVzGkMrAsElT2CIRD6zWbA0+GcoQ5qCz/ZTeCeDBgCIhDkACdy9Q+kjl1HGlwWS8OGCaOLt
/14zXuYoCeSZMP3sJMI7BuzRCvu5NBduxfZYC75UnKzO29WjXIxF5h32U8nArv6WivhPRs=</Modulus>
<Exponent>AQAB</Exponent>
<P>/HvZG4UCGgsngAPG5jie9K4uctxIuuuTft1yVy9rz3zrO58AyxgqEKan2ezikpjzhKeUF6Y+6SBf/DJIs9W9fQ==</P>
<Q>vle25eGrqNY0y1QvQgQRBnFUDkxnvmH7zIiMJhL/fHzhLBQOIRDsgqIgEvGMGfkjMxPyOSi1cE4eDe1rMBdIdw==</Q>
<DP>vpLQpff4RSYkM3kmWUFlobQTdTkWYJhN5VVK58nwa1WTzJXQqHtdzOGuEky+G/782CURH+So2ZhJOvfNbognlQ==</DP>
<DQ>Mt0rseiJP7fmKcOYUVLW3drg9GU0f+qdJ/4BPZdsEG8qmOXPFMT5/rqmSYmkv7gU0OxumRmoypcFbwFX2GUQZw==</DQ>
<InverseQ>wbxr31t5DAUanNTJojPnQS5ex0RXDVAiGcyMCpktOQNTvvN64Hh6975JYg1wEtFuu8doLh6tgfexvpOtVhJq2A==</InverseQ>
<D>M6bn4bLuWDKQBxIfyvdjsMgW5YaKbMFeZg/BXNTrPeTemRqC52EOTT4WSnoc01uN
/s9+rPUuIRFkqmYwSx0yuT85ayfoiiDDLq7GDt8M21EuAep3yxOAHAgJ3Ddr9UApv72MuDKNHrOnr3WDR5OPWtuS3ZZJ8jVpv5jwADBsEYE=</D>
</RSAKeyValue>

Определение доступности .Net

Процедура Тест_ВерсияDotNet() Экспорт
	objHtml = Новый COMОбъект("htmlfile");
	агент = objHtml.parentWindow.navigator.userAgent;
	
	Сообщить(агент);
КонецПроцедуры