Первая. Можно обойти регистрацию в журнале. Например, изменив документ напрямую в базе, не используя оболочку 1С:Предприятие.
Вторая. Можно отредактировать сам журнал изменений.
В случае с файловой базой (а таких, по всей видимости, большинство) эти два действия может выполнить вообще кто угодно. С клиент-серверной базой дело обстоит чуть лучше. Но это действительно всего лишь "чуть". Администратор системы (а также любой, кто получит доступ к базе или папке с журналом) может внести такие изменения, которые никто никогда не заметит. Например, изменить документ поступления на склад. Вместо 10 шт. по 1 рублю поставить 1 шт. по 10 рублей. Взаиморасчеты с контрагентом останутся неизменными, а 9 шт. можно спокойно забрать себе.
Можно ли как-то решить эти две проблемы с журналом? Можно и ниже я покажу как. Для демонстрации я создал обработку с таблицей настройки. В ней можно отмечать документы и их реквизиты. Журнал будет формироваться только по отмеченным позициям. Так будет легче проверять его работу.
.
Для решения первой проблемы будем регулярно сравнивать документы в базе с их копиями, хранящимися в журнале. А для того, чтобы сам журнал не стал больше базы, будем хранить не сами документы, а хеш-суммы. В этом случае каждый документ будет занимать всего 64 символа (можно укоротить до 32 байт, если очень надо). Для простоты, я буду вести журнал прямо в рабочей базе. Это избавит нас от ненужных технических подробностей и позволит легче понять сам принцип защищенного журнала (в рабочем варианте я веду журнал в отдельном файле). Создадим документ и назовем его "Докчейн". Добавим два реквизита:
КонтролируемыйДокумент тип ДокументСсылка
ХешДокумента тип Строка длина 64
Добавим в наш журнал все документы, которые туда еще не попали.
запрос=новый запрос;
текстзапроса=
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.<вид> КАК Док
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.Докчейн
| ПО Док.Ссылка = Блокчейн.КонтролируемыйДокумент
|ГДЕ
| Док.Проведен
| И Блокчейн.Ссылка ЕСТЬ NULL";
видыдокументов=настройка.ПолучитьЭлементы();
для каждого вид из видыдокументов цикл
если вид.пометка тогда
запрос.Текст=стрзаменить(текстзапроса,"<вид>",вид.имя);
выб=запрос.Выполнить().Выбрать();
пока выб.Следующий() цикл
новблок=документы.Докчейн.СоздатьДокумент();
новблок.Дата=текущаядата();
новблок.КонтролируемыйДокумент=выб.ссылка;
новблок.ХешДокумента=ПолучитьХешДокумента(выб.ссылка);
новблок.Записать();
конеццикла;
конецесли;
конеццикла;
Получить хеш документа в 1С совсем несложно. Для этого есть специальный объект ХешированиеДанных.
Функция ПолучитьХешДокумента(ссылка)
хд=новый ХешированиеДанных(ХешФункция.SHA256);
хд.Добавить(ПолучитьДокументСтрокой(ссылка));
рез=строка(хд.ХешСумма);
рез=стрзаменить(рез," ","");
возврат рез;
КонецФункции
Как получить документ в виде строки я здесь описывать не буду. Те,кому это не очевидно, могут заглянуть в приложение к данной публикации.
Итак, добавить документ мимо нашего журнала не получится. Теперь проконтролируем изменение(удаление) уже существующих документов.
выб=документы.Докчейн.Выбрать();
пока выб.Следующий() цикл
если лев(строка(выб.КонтролируемыйДокумент),1)="<" тогда
сообщить("Блок "+выб.Номер+" документ удален");
иначеесли выб.ХешДокумента<>ПолучитьХешДокумента(выб.КонтролируемыйДокумент) тогда
сообщить("Блок "+выб.Номер+" документ изменен");
конецесли;
конеццикла;
Первая проблема решена. Как нам теперь защититься от изменения самого журнала? Добавим в журнал новые реквизиты:
КлючНачальный тип Строка длина 64
КлючКонечный тип Строка длина 64
Соль тип Строка длина 16
Поменяем алгоритм создания блоков:
Процедура СоздатьНовыеБлоки()
ПоследнийБлок=ПолучитьПоследнийБлок();
если ПоследнийБлок=неопределено тогда
КлючНачальный="";
иначе
КлючНачальный=ПоследнийБлок.КлючКонечный;
конецесли;
запрос=новый запрос;
текстзапроса=
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.<вид> КАК Док
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.Докчейн
| ПО Док.Ссылка = Блокчейн.КонтролируемыйДокумент
|ГДЕ
| Док.Проведен
| И Блокчейн.Ссылка ЕСТЬ NULL";
видыдокументов=настройка.ПолучитьЭлементы();
для каждого вид из видыдокументов цикл
если вид.пометка тогда
запрос.Текст=стрзаменить(текстзапроса,"<вид>",вид.имя);
выб=запрос.Выполнить().Выбрать();
пока выб.Следующий() цикл
новблок=документы.Докчейн.СоздатьДокумент();
новблок.Дата=текущаядата();
новблок.КонтролируемыйДокумент=выб.ссылка;
новблок.ХешДокумента=ПолучитьХешДокумента(выб.ссылка);
новблок.КлючНачальный=КлючНачальный;
новблок.Соль=ПолучитьСоль(КлючНачальный+новблок.ХешДокумента+строка(новблок.Дата));
новблок.КлючКонечный=ПолучитьКонечныйКлюч(КлючНачальный+новблок.ХешДокумента+строка(новблок.Дата)+новблок.соль);
новблок.Записать();
КлючНачальный=новблок.КлючКонечный;
конеццикла;
конецесли;
конеццикла;
КонецПроцедуры
Как видите, я связал блоки друг с другом. Конечный ключ каждого блока является начальным ключом следующего блока. В то же время, конечный ключ непосредственно связан с содержимым документа (хеш сумма считается по начальному ключу и документу вместе). Изменим, также, и алогритм контроля.
Функция КонтрольПройден()
рез=истина;
ключ="";
выб=документы.Докчейн.Выбрать();
пока выб.Следующий() цикл
если выб.КлючНачальный<>ключ тогда
сообщить("Блок "+выб.Номер+" неверный начальный, ключ нарушена последовательность блоков");
рез=ложь;
иначеесли выб.КлючКонечный<>ПолучитьКонечныйКлюч(выб.КлючНачальный+выб.ХешДокумента+строка(выб.Дата)+выб.соль) тогда
сообщить("Блок "+выб.Номер+" неверный коннечный ключ, нарушена последовательность блоков");
рез=ложь;
иначеесли лев(строка(выб.КонтролируемыйДокумент),1)="<" тогда
сообщить("Блок "+выб.Номер+" документ удален");
рез=ложь;
иначеесли выб.ХешДокумента<>ПолучитьХешДокумента(выб.КонтролируемыйДокумент) тогда
сообщить("Блок "+выб.Номер+" документ изменен");
рез=ложь;
конецесли;
ключ=выб.КлючКонечный;
конеццикла;
возврат рез;
КонецФункции
Здесь добавлен контроль начального и конечного ключа. И теперь, внимание! У нас решены обе проблемы. Наш журнал нельзя изменить, в том смысле, что его нельзя изменить незаметно. Любое изменение в середине журнала приведет к тому, что потребуется изменить весь журнал от этого момента и до конца. А это и есть то, что требует защитная концепция бухгалтерского учета. Подмену цепочки блоков будет нетрудно обнаружить. Например, путем визуального контроля.
Проверяющий записывает сегодняшний(вчерашний) последний блок и сверяет вчерашний(позавчерашний) последний блок с записью, сделанной ранее. Это - самый надежный способ. Есть и другие, менее надежные, но зато не требующие участия человека. Обратите внимание, что ключ на рисунке значительно короче 64 символов. Все правильно. Это - не конечный ключ. Это - соль. Она также участвует в формировании цепочки, поэтому можно контролировать цепочку через нее. Получить соль можно различными способами. Самый простой - использовать генератор случайных чисел. В демо-обработке и в рабочем варианте я использую т.н. POW или поиск "красивого хеша". Это дает возможность усилить защиту (подробности здесь //infostart.ru/public/717210/).
Осталась одна небольшая проблема. Наш алгоритм контроля обнаружит любое изменение документа, но что делать с этим знанием дальше? Общепринятый стиль работы в 1С подразумевает возможность многократного изменения и перепроведения документов. И если оставить все как есть, мы просто утонем в сообщениях об изменении. Выход прост. Будем добавлять факт изменения в журнал. Добавим еще два реквизита в журнал:
Предок тип ДокументСсылка.Докчейн
Потомок тип ДокументСсылка.Докчейн
Алгоритм создания новых блоков не изменится. Алгоритм проверки будет теперь таким:
Функция КонтрольПройден()
вжурнал=новый массив;
рез=истина;
ключ="";
выб=документы.Докчейн.Выбрать();
пока выб.Следующий() цикл
если выб.КлючНачальный<>ключ тогда
строкарезультата="Неверный начальный ключ"+символы.ПС+
"Цепочка документов повреждена."+символы.ПС+
"Идентификатор документа "+выб.УникальныйИдентификатор();
элементы.ПроблемныйДокумент.Видимость=истина;
ПроблемныйДокумент=выб.КонтролируемыйДокумент;
рез=ложь;
прервать;
конецесли;
если выб.КлючКонечный<>ПолучитьКонечныйКлюч(выб.КлючНачальный+выб.ХешДокумента+строка(выб.Дата)+ПолучитьПредка(выб.Предок)+выб.Соль) тогда
строкарезультата="Неверный конечный ключ"+символы.ПС+
"Цепочка документов повреждена."+символы.ПС+
"Идентификатор документа "+выб.УникальныйИдентификатор();
элементы.ПроблемныйДокумент.Видимость=истина;
ПроблемныйДокумент=выб.КонтролируемыйДокумент;
рез=ложь;
прервать;
конецесли;
если выб.Предок.Пустая() тогда
если лев(строка(выб.КонтролируемыйДокумент),1)="<" тогда
если выб.Потомок.Пустая() тогда
ст=новый структура;
ст.Вставить("ссылка",неопределено);
ст.Вставить("предок",выб.Ссылка);
вжурнал.Добавить(ст);
иначе
если не выб.Потомок.Предок=выб.Ссылка тогда
строкарезультата="Обнаружено нарушение связи предок-потомок."+символы.ПС+
"Цепочка документов повреждена.";
элементы.ПроблемныйДокумент.Видимость=истина;
ПроблемныйДокумент=выб.КонтролируемыйДокумент;
рез=ложь;
прервать;
конецесли;
конецесли;
иначе
новыйхеш=ПолучитьХешДокумента(выб.КонтролируемыйДокумент);
если новыйхеш<>выб.ХешДокумента тогда
предок=выб.Ссылка;
текблок=выб.Ссылка;
пока не выб.Потомок.Пустая() цикл
текблок=текблок.Потомок;
если не текблок.Предок=предок тогда
строкарезультата="Обнаружено нарушение связи предок-потомок."+символы.ПС+
"Цепочка документов повреждена.";
элементы.ПроблемныйДокумент.Видимость=истина;
ПроблемныйДокумент=выб.КонтролируемыйДокумент;
рез=ложь;
прервать;
конецесли;
если текблок=выб.Ссылка тогда
строкарезультата="Обнаружены циклические ссылки."+символы.ПС+
"Цепочка документов повреждена.";
элементы.ПроблемныйДокумент.Видимость=истина;
ПроблемныйДокумент=выб.КонтролируемыйДокумент;
рез=ложь;
прервать;
конецесли;
предок=текблок;
конеццикла;
если не рез тогда
прервать;
конецесли;
если новыйхеш<>текблок.ХешДокумента тогда
ст=новый структура;
ст.Вставить("ссылка",выб.ссылка);
ст.Вставить("предок",текблок);
вжурнал.Добавить(ст);
конецесли;
конецесли;
конецесли;
конецесли;
ключ=выб.КлючКонечный;
конеццикла;
если рез и вжурнал.Количество()>0 тогда
для каждого ст из вжурнал цикл
новблок=документы.Докчейн.СоздатьДокумент();
новблок.Дата=текущаядата();
новблок.КонтролируемыйДокумент=ст.ссылка;
новблок.ХешДокумента=ПолучитьХешДокумента(ст.ссылка);
новблок.КлючНачальный=ключ;
новблок.Соль=ПолучитьСоль(новблок.КлючНачальный+новблок.ХешДокумента+строка(новблок.Дата)+ПолучитьПредка(выб.Предок));
новблок.КлючКонечный=ПолучитьКонечныйКлюч(новблок.КлючНачальный+новблок.ХешДокумента+строка(новблок.Дата)+ПолучитьПредка(выб.Предок)+новблок.соль);
новблок.Записать();
ключ=новблок.КлючКонечный;
конеццикла;
конецесли;
возврат рез;
КонецФункции
Теперь все изменения(удаления) попадают в конец журнала. Бухгалтеру остается только регулярно проверять журнал. Не только на предмет подмены, но и просматривать все записи за день:
Окончательный демо-вариант обработки в приложении. Можно скачать и поиграться.
Обработки тестировались на версии 8.3.10.2639, в оригинальной конфигурации.
Любой код из этой публикации можно использовать свободно без дополнительных условий.
Рабочую версию можно приобрести здесь //infostart.ru/public/717210/