В практике разработки на платформе 1С:Предприятие 8 периодически возникают задачи повышенной конфиденциальности. Одна из наиболее сложных и парадоксальных задач — защита данных от пользователей с административными правами. Обычное разграничение прав доступа на уровне ролей или механизмы RLS (Record Level Security) здесь не помогут: системный администратор СУБД или администратор 1С с полными правами всегда может обойти эти ограничения, написав прямой запрос к базе данных, сделав замер производительности или скопировав резервную копию.
Единственным надежным решением в данном случае является физическое шифрование конфиденциальных полей справочников и табличных частей. При этом ключи или пароли дешифрации не должны храниться в базе данных, а сама расшифровка должна происходить исключительно на стороне клиента в оперативной памяти после ввода пароля пользователем. В рамках данной статьи мы подробно разберем, как спроектировать и реализовать такую систему в среде 1С.
Прежде чем переходить к коду, давайте проанализируем базовые принципы безопасности, которые необходимо соблюдать для надежной защиты данных от администратора:
GUID. Ссылки необходимо преобразовывать в текстовый формат, шифровать и хранить как строки.Рассмотрим подробнее практические варианты реализации шифрования встроенными средствами платформы 1С.
Один из самых простых, кроссплатформенных и абсолютно надежных способов шифрования данных в 1С без использования внешних библиотек и регистрации специальных криптопровайдеров — это использование встроенного объекта ЗаписьZipФайла с поддержкой стойкого алгоритма шифрования AES-256. Данный метод идеально подходит для шифрования длинных текстовых полей, сериализованных структур или табличных частей, которые преобразуются в JSON/XML.
Разберем по шагам процесс шифрования произвольной строки:
Функция ЗашифроватьСтрокуВZIP(ИсходнаяСтрока, Пароль) Экспорт
// Создаем временный файл для исходного текста
ИмяВременногоФайлаДанных = ПолучитьИмяВременногоФайла("txt");
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.УстановитьТекст(ИсходнаяСтрока);
ТекстовыйДокумент.Записать(ИмяВременногоФайлаДанных, КодировкаТекста.UTF8);
// Создаем временный файл для зашифрованного архива
ИмяВременногоФайлаАрхива = ПолучитьИмяВременногоФайла("zip");
// Инициализируем запись ZIP с шифрованием AES-256
Архив = Новый ЗаписьZipФайла(ИмяВременногоФайлаАрхива, Пароль, "Данные защищены",
МетодСжатияZip.Сжатие, УровеньСжатияZip.Максимальный,
МетодШифрованияZip.AES256);
Архив.Добавить(ИмяВременногоФайлаДанных, РежимСохраненияПутейZip.НеСохранятьПути);
Архив.Записать();
// Считываем зашифрованный архив в двоичные данные
ДвоичныеДанныеАрхива = Новый ДвоичныеДанные(ИмяВременногоФайлаАрхива);
// Очищаем временные файлы на диске в целях безопасности
Попытка
УдалитьФайлы(ИмяВременногоФайлаДанных);
УдалитьФайлы(ИмяВременногоФайлаАрхива);
Исключение
// Логирование ошибки удаления при необходимости
КонецПопытки;
// Возвращаем ХранилищеЗначения для записи в реквизит справочника
Возврат Новый ХранилищеЗначения(ДвоичныеДанныеАрхива);
КонецФункции
Теперь рассмотрим обратную процедуру — дешифрацию полученного хранилища значения на клиенте:
Функция РасшифроватьСтрокуИзZIP(ХранилищеАрхива, Пароль) Экспорт
ДвоичныеДанныеАрхива = ХранилищеАрхива.Получить();
Если ДвоичныеДанныеАрхива = Неопределено Тогда
Возврат "";
КонецЕсли;
// Записываем архив во временный файл
ИмяВременногоФайлаАрхива = ПолучитьИмяВременногоФайла("zip");
ДвоичныеДанныеАрхива.Записать(ИмяВременногоФайлаАрхива);
КаталогРаспаковки = ПолучитьИмяВременногоФайла("");
СоздатьКаталог(КаталогРаспаковки);
Попытка
// Открываем ZIP-архив с предоставленным паролем
Архив = Новый ЧтениеZipФайла(ИмяВременногоФайлаАрхива, Пароль);
// Извлекаем файлы (в архиве у нас только один текстовый файл)
Архив.ИзвлечьВсе(КаталогРаспаковки, РежимВосстановленияПутейФайловZip.НеВосстанавливать);
Архив.Закрыть();
Исключение
// Ошибка неверного пароля или повреждения архива
ВызватьИсключение "Ошибка дешифрации данных. Возможно, введен неверный пароль.";
КонецПопытки;
// Читаем извлеченный файл
НайденныеФайлы = НайтиФайлы(КаталогРаспаковки, "*.*");
Если НайденныеФайлы.Количество() = 0 Тогда
Возврат "";
КонецЕсли;
ИмяФайлаДанных = НайденныеФайлы[0].ПолноеИмя;
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.Прочитать(ИмяФайлаДанных, КодировкаТекста.UTF8);
РасшифрованныйТекст = ТекстовыйДокумент.ПолучитьТекст();
// Полная зачистка временных данных на диске
Попытка
УдалитьФайлы(ИмяВременногоФайлаАрхива);
УдалитьФайлы(КаталогРаспаковки);
Исключение
КонецПопытки;
Возврат РасшифрованныйТекст;
КонецФункции
Этот метод гарантирует, что на уровне СУБД в базе данных будут лежать лишь бинарные данные ZIP-архивов. Попытка открыть их или распаковать без знания пароля приведет к ошибке расшифровки AES.
Если в системе развернута инфраструктура открытых ключей (PKI) или требуется использовать сертифицированные криптопровайдеры (например, КриптоПро CSP), мы можем задействовать встроенный объект платформы МенеджерКриптографии. Он позволяет использовать асимметричное шифрование на основе личных сертификатов пользователей.
Посмотрим на пример кода для шифрования данных по сертификату получателя:
Функция ЗашифроватьДанныеСертификатом(ИсходныйТекст, СертификатПолучателя) Экспорт
// Инициализируем менеджер криптографии для работы с системным хранилищем
Менеджер = Новый МенеджерКриптографии("", "", 1); // Использование CryptoAPI
// Преобразуем строку в двоичные данные
ВременныйФайл = ПолучитьИмяВременногоФайла("tmp");
ЗаписьТекста = Новый ЗаписьТекста(ВременныйФайл, КодировкаТекста.UTF8);
ЗаписьТекста.Записать(ИсходныйТекст);
ЗаписьТекста.Закрыть();
ДвоичныеДанные = Новый ДвоичныеДанные(ВременныйФайл);
// Шифруем данные сертификатом получателя
ЗашифрованныеДанные = Менеджер.Зашифровать(ДвоичныеДанные, СертификатПолучателя);
УдалитьФайлы(ВременныйФайл);
Возврат Новый ХранилищеЗначения(ЗашифрованныеДанные);
КонецФункции
Для дешифрации пользователь должен будет вставить свой персональный токен (флешку-ключ) или установить личный сертификат в систему. Дешифрация выполняется методом Менеджер.Расшифровать().
Основной контраргумент против шифрования полей в базах данных — невозможность осуществлять поиск и строить отчеты без полной дешифрации всей таблицы. Если у нас зашифровано поле Наименование, стандартный запрос ПОДОБНО или точное сравнение работать не будут.
Для решения этой проблемы мы применим концепцию криптографических индексов на основе необратимых хеш-функций. Для каждого конфиденциального текстового поля, по которому требуется производить точный поиск (например, ИНН, Телефон, Наименование), мы создадим дополнительный скрытый реквизит с типом Строка фиксированной длины (например, 64 символа для алгоритма SHA-256).
Проанализируем алгоритм работы:
Наименование_Хеш).ГДЕ Наименование_Хеш = &ПараметрХеш.Посмотрим на программную реализацию расчета хеша средствами 1С:
Функция ПолучитьКриптографическийХеш(ИсходнаяСтрока) Экспорт
// Нормализуем строку для обеспечения точного поиска независимо от регистра и пробелов
НормСтрока = НРег(СокрЛП(ИсходнаяСтрока));
// Используем встроенный объект хеширования платформы 1С
Хеширование = Новый ХешированиеДанных(ХэшФункция.SHA256);
Хеширование.Добавить(НормСтрока);
// Преобразуем двоичный хеш в строковое представление (HEX-строку)
Возврат НРег(Строка(Хеширование.ХэшСумма));
КонецФункции
Пример использования данного хеша в запросе для точного поиска элемента:
Функция НайтиЭлементПоЗашифрованномуНаименованию(ИскомоеНаименование) Экспорт
ХешПоиска = ПолучитьКриптографическийХеш(ИскомоеНаименование);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ШифрованныйСправочник.Ссылка КАК Ссылка
|ИЗ
| Справочник.ШифрованныйСправочник КАК ШифрованныйСправочник
|ГДЕ
| ШифрованныйСправочник.Наименование_Хеш = &ХешПоиска";
Запрос.УстановитьПараметр("ХешПоиска", ХешПоиска);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецЕсли;
Возврат Неопределено;
КонецФункции
Таким образом, администратор базы данных, заглянув в СУБД, увидит в поле Наименование_Хеш лишь строку вида e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. Восстановить из этого хеша реальное наименование математически невозможно, но поиск по точному совпадению работает мгновенно благодаря стандартному индексированию поля платформой 1С.
В реальных базах данных элементы справочников часто ссылаются на другие объекты базы данных. Если мы оставим реквизит в стандартном ссылочном формате (например, тип СправочникСсылка.Контрагенты), администратор СУБД сможет с легкостью понять структуру связей и транзакций, сопоставив уникальные идентификаторы (GUID) таблиц.
Для предотвращения этой утечки мы должны хранить ссылки в зашифрованном строковом виде. Рассмотрим по шагам, как это реализовать:
Ссылка.УникальныйИдентификатор().Посмотрим на пример кода конвертации и восстановления ссылок:
// Функция конвертирует ссылку в зашифрованную строку
Функция ПолучитьЗашифрованнуюСсылка(СсылкаНаОбъект, Пароль) Экспорт
Если Не ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
Возврат "";
КонецЕсли;
СтрокаGUID = Строка(СсылкаНаОбъект.УникальныйИдентификатор());
Возврат ЗашифроватьСтрокуВZIP(СтрокаGUID, Пароль);
КонецФункции
// Функция восстанавливает ссылку из зашифрованной строки
Функция ВосстановитьСсылкуИзЗашифрованнойСтроки(ХранилищеСтроки, Пароль, ИмяСправочника) Экспорт
СтрокаGUID = РасшифроватьСтрокуИзZIP(ХранилищеСтроки, Пароль);
Если ПустаяСтрока(СтрокаGUID) Тогда
Возврат Справочники[ИмяСправочника].ПустаяСсылка();
КонецЕсли;
Попытка
НовыйGUID = Новый УникальныйИдентификатор(СтрокаGUID);
Возврат Справочники[ИмяСправочника].ПолучитьСсылку(НовыйGUID);
Исключение
Возврат Справочники[ИмяСправочника].ПустаяСсылка();
КонецПопытки;
КонецФункции
Используя этот подход, мы полностью скрываем связи между нашими конфиденциальными данными и другими справочниками системы. На уровне базы данных это будет выглядеть как несвязанные наборы байтов.
Если требования безопасности максимально высоки и необходимо исключить физическое нахождение расшифрованных данных в оперативной памяти сервера основной рабочей базы 1С, целесообразно использовать архитектуру с выделенным внешним источником данных или отдельной микросервисной базой данных.
В этом сценарии:
Данный подход переносит точку ответственности и позволяет гарантировать, что даже в случае полной компрометации (кражи) основной базы данных злоумышленники и администраторы не получат доступ к конфиденциальным справочникам, так как физически этих данных в основной базе просто нет.
Реализация шифрованного справочника в 1С — это комплексный процесс, требующий детального планирования безопасности на всех этапах жизненного цикла данных. Подводя итог, выделим ключевые правила построения таких систем: