- Регистрация
- 12.04.17
- Сообщения
- 19.095
- Реакции
- 107
- Репутация
- 0
Я продемонстрирую как с помощью JavaScript прямо в браузере можно извлечь и проанализировать данные из QR кодов содержащихся в документах сформированных порталами электронного правительства Республики Казахстан (к примеру
В электронных документах присутствует следующая формулировка:
На сколько мне известно, готовых инструментов для извлечения и анализа данных в QR кодах не существует.
Конечную цель ставлю следующую: извлечь подписанные данные и подпись, проверить целостность подписанных данных. О проверке цифровой подписи в этой заметке речи идти не будет, только о проверке хеша. Детали проверки цифровых подписей могут быть описаны в будущем, в том случае, если публика проявит интерес к этой тематике.
Важно: эта заметка не описывает методик взлома и не помогает получать несанкционированного доступа к данным, речь будет идти о конвертации данных из одних представлений в другие.
Я покажу как обрабатывать оригинальные PDF файлы которые формируют и предоставляют для скачивания порталы электронного правительства РК. Эти PDF файлы содержат QR коды как отдельные внедренные изображения.
Экспериментировать я буду на справке об отсутствии судимости.
Я воспользуюсь следующими библиотеками:
0. Считывание PDF файла в ArrayBuffer
Получить доступ к PDF файлу возможно стандартными средствами HTML с помощью тега и его атрибута
В современных браузерах получить содержимое файла в виде ArrayBuffer можно следующим образом:
const fileContents = await fileInput.files[0].arrayBuffer();
1. Извлечение изображений из PDF документа
Библиотеку PDF.js необходимо инициализировать перед началом работы, примеры приведены в документации
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
В PDF.js объекты описываются с точки зрения выполняемых над ними операций. Так как меня интересуют изображения, то нужно искать следующие операции:
const ops = [
pdfjsLib.OPS.paintJpegXObject,
pdfjsLib.OPS.paintImageXObject,
];
Реализация извлечения изображений со всех страниц PDF документа:
const loadingTask = pdfjsLib.getDocument(fileContents);
const pdf = await loadingTask.promise;
const objIDs = [];
const images = [];
await (async function () {
for (let pageIndex = 1; pageIndex / Страница содержит набор операторов, нужно найти интересующие.
const operators = await page.getOperatorList();
for (let i = 0; i < operators.fnArray.length; i++) {
const fn = operators.fnArray;
if (ops.indexOf(fn) !== -1) {
// По индексу оператора можно получить его параметры, первый параметр - идентификатор объекта.
const objID = operators.argsArray[0];
// Над одним и тем же объектом могут выполняться несколько операций, дубликаты не нужны.
if (objIDs.indexOf(objID) === -1) {
objIDs.push(objID);
// Объект изображения можно получить по его идентификатору.
try {
const imageInfo = page.objs.get(objID);
images.push(imageInfo);
} catch (err) {
console.log(err);
}
}
}
}
}
})()
2. Декодирование QR кодов
Библиотека jsQR поддерживает изображения только в
function extractRGBAData(image) {
if (image.kind === 3) { // ImageKind.RGBA_32BPP из
return image.data;
}
if (image.kind !== 2) { // ImageKind.RGB_24BPP из
throw new Error(`Image kind "${image.kind}" is not supported.`);
}
const data = new Uint8ClampedArray(image.width * image.height * 4);
let destPosition = 0;
for (let srcPosition = 0; srcPosition < image.data.length
{
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = 255;
}
return data;
}
Попробую декодировать все полученные изображения:
const qrCodes = [];
images.forEach((image) => {
if (image.data) {
const data = extractRGBAData(image);
try {
const code = jsQR(data, image.width, image.height);
console.log(code);
qrCodes.push(code);
} catch (err) {
console.log(err);
}
}
});
В результате в консоль браузера выведено 7 сторк — по одной на каждый QR код на странице. Одна из строк содержит URL документа — QR код с ней размещен в верхней правой части документов, она меня не интересует. Остальные 6 строк содержат XML следующего формата (персональные данные удалены):
...
...
1
6
...
Из этого меня интересуют следующие теги:
3. Извлечение частей данных
Для распределения частей данных по соответствующим позициям я воспользуюсь следующей функцией:
const qrCodesBlocks = [];
function addQRCodeBlock(code) {
if (!code || !code.data) {
return;
}
// Получу общее количество частей.
const elementsAmountRegexp = /((.|\r|\n)+?)elementsAmount>/;
const elementsAmountResult = elementsAmountRegexp.exec(code.data);
if (!elementsAmountResult || elementsAmountResult.length ');
}
// При обработке первой части нужно инициализировать массив.
if (qrCodesBlocks.length === 0) {
for (let i = 0; i < elementsAmount; i++) {
qrCodesBlocks.push('');
}
} else {
if (qrCodesBlocks.length !== elementsAmount) {
throw new Error(`В разных QR кодах указано разное общее количество QR кодов: "${qrCodesBlocks.length}" и "${elementsAmount}"`);
}
}
// Получу индекс части.
const elementNumberRegexp = /((.|\r|\n)+?)elementNumber>/;
const elementNumberResult = elementNumberRegexp.exec(code.data);
if (!elementNumberResult || elementNumberResult.length < 2) {
throw new Error(`В QR коде отсутствует ""`);
}
const elementNumber = +elementNumberResult[1];
if (!Number.isSafeInteger(elementNumber)) {
throw new Error(`"" в QR коде не является числом`);
}
// Защита от внештатных ситуаций.
if (elementNumber > elementsAmount) {
throw new Error(`Индекс QR кода "${elementNumber}" больше общего количества QR кодов "${elementsAmount}"`);
}
if (qrCodesBlocks[elementNumber - 1] !== '') {
throw new Error(`Индекс QR кода "${elementNumber}" обнаружен более одного раза`);
}
// Помещу часть в соответствую позицию.
const elementDataRegexp = /((.|\r|\n)+?)elementData>/;
const elementDataResult = elementDataRegexp.exec(code.data);
if (!elementDataResult || elementDataResult.length < 2) {
throw new Error('В QR коде отсутствует ""');
}
const elementData = elementDataResult[1];
qrCodesBlocks[elementNumber - 1] = elementData;
}
Осталось получить части и проверить что распределение прошло успешно.
qrCodes.forEach(addQRCodeBlock);
if (qrCodesBlocks.length === 0) {
throw new Error('Ошибка при извлечении данных из QR кодов: не обнаружено ни одного QR кода с поддерживаемыми данными');
}
const foundBlocks = qrCodesBlocks.filter((block) => !!block);
if (qrCodesBlocks.length !== foundBlocks.length) {
throw new Error('Ошибка при извлечении данных из QR кодов: не удалось получить данные всех QR кодов');
}
4. Восстановление данных
Анализ частей данных показал что это ZIP архив разрезанный на части каждая из которых закодирована в Base64.
В первую очередь нужно декодировать части из Base64:
const zippedParts = qrCodesBlocks.map(block => new Uint8Array(gostCrypto.coding.Base64.decode(block)));
Далее соединить их:
const totalLength = zippedParts.reduce((accumulator, part) => accumulator + part.length, 0);
const zippedData = new Uint8Array(totalLength);
let zippedDataIndex = 0;
zippedParts.forEach((part) => {
zippedData.set(part, zippedDataIndex);
zippedDataIndex += part.length;
});
И распаковать архив:
const zip = await JSZip.loadAsync(zippedData, { checkCRC32: true });
В архиве находится единственный файл с именем one, его содержимое меня и интересует:
const file = zip.file('one');
if (!file) {
throw new Error('В архиве отсутствует ожидаемый файл "one"');
}
const recoveredContents = await file.async("string");
5. Подготовка данных к анализу
Восстановленные данные — это XML следующего формата (персональные данные удалены):
...
...
`1`
Запрос обработан
Запрос обработан
...
UNJUDGED
...
...
...
...
...
...
...
...
...
...
...
...
...
...
Из всего перечисленного меня интересует только digiSign — это еще один XML закодированный в Base64. На остальные данные я не обращаю внимания, так как они будут продублированы глубже.
Извлеку и декодирую внутренний XML:
const regexp = /((.|\r|\n)+?)digiSign>/;
const regexpResult = regexp.exec(recoveredContents);
if (!regexpResult && regexpResult.length !== 2) {
throw new Error('В XML отсутствует ""');
}
const digiSignBytes = gostCrypto.coding.Base64.decode(regexpResult[1]);
const xmlDataAndSignature = gostCrypto.coding.Chars.encode(digiSignBytes, 'utf8');
6. Анализ данных
Содержимое внутреннего XML выглядит следующим образом (персональные данные удалены):
UNJUDGED
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
Это
....
7. Проверка целостности подписанных данных
Проверка целостности данных будет заключаться в сравнении приведенного в XML подписи значения хеша с вычисленным.
Создание объекта XML документа:
const xml = XmlDSigJs.Parse(xmlDataAndSignature);
Значение хеша приведено в теге ...:
const xmlSignatures = XmlDSigJs.Select(xml, "//*[local-name(.)='Signature' and namespace-uri(.)='
if (xmlSignatures.length === 0) {
throw new Error(`В распакованных данных отсутствует цифровая подпись (тег ""): "${xmlDataAndSignature}"`);
}
if (xmlSignatures.length > 1) {
throw new Error(`В распакованных данных присутствует несколько цифровых подписей (тег ""): "${xmlDataAndSignature}"`);
}
const hashElementsInSignature = XmlDSigJs.Select(xmlSignatures[0], "//*[local-name(.)='DigestValue']");
if (hashElementsInSignature.length === 0) {
throw new Error(`В XML подписи отсутствует хеш (тег ""): "${xmlDataAndSignature}"`);
}
if (hashElementsInSignature.length > 1) {
throw new Error(`В XML подписи присутствует несколько хешей (тег ""): "${xmlDataAndSignature}"`);
}
const hashInSignature = hashElementsInSignature[0].textContent;
Данные необходимо подготовить к хешированию — выполнить над ними трансформации приведенные в теге ... XML подписи:
const xmlDsigEnvelopedSignatureTransform = new XmlDSigJs.XmlDsigEnvelopedSignatureTransform();
xmlDsigEnvelopedSignatureTransform.LoadInnerXml(xml.documentElement);
xmlDsigEnvelopedSignatureTransform.GetOutput();
const xmlDsigC14NWithCommentsTransform = new XmlDSigJs.XmlDsigC14NWithCommentsTransform();
xmlDsigC14NWithCommentsTransform.LoadInnerXml(xml.documentElement);
const signedDataXML = xmlDsigC14NWithCommentsTransform.GetOutput();
const dataToHash = gostCrypto.coding.Chars.decode(signedDataXML, 'utf8');
В подписи указан алгоритм хеширования "
Вычисление значения хеша:
const hashBytes = await gostCrypto.subtle.digest({name: 'GOST R 34.11-94', version: 1994, sBox: 'D-TEST'}, dataToHash);
const signedDataXMLHash = gostCrypto.coding.Base64.encode(hashBytes);
Проверка целостности данных в моем документе прошла корректно:
if (signedDataXMLHash !== hashInSignature) {
throw new Error(`Хеш вычисленный из данных XML документа "${signedDataXMLHash}" не соответствует значению в подписи "${hashInSignature}"`);
}
Заключение
Эксперимент завершился успешно, поставленная цель достигнута. Но нужно упомянуть о том, что без проверки цифровой подписи говорить о целостности данных несколько лукаво — нет уверенности в том, что значение хеша данных в XML подписи не было изменено.
Еще одним нюансом оказалось то, что разные типы документов электронного правительства РК имеют разную структуру: в некоторых случаях восстановленный документ сразу является XML подписью, в других в digiSign подпись не закодирована в Base64, а вместо этого представлена в
Полезные ссылки:
You must be registered for see links
).В электронных документах присутствует следующая формулировка:
*штрих-код содержит данные, полученные из информационной системы ГБД РН и подписанные электронно-цифровой подписью Филиала НАО «Государственная корпорация «Правительство для граждан».
На сколько мне известно, готовых инструментов для извлечения и анализа данных в QR кодах не существует.
Конечную цель ставлю следующую: извлечь подписанные данные и подпись, проверить целостность подписанных данных. О проверке цифровой подписи в этой заметке речи идти не будет, только о проверке хеша. Детали проверки цифровых подписей могут быть описаны в будущем, в том случае, если публика проявит интерес к этой тематике.
Важно: эта заметка не описывает методик взлома и не помогает получать несанкционированного доступа к данным, речь будет идти о конвертации данных из одних представлений в другие.
Я покажу как обрабатывать оригинальные PDF файлы которые формируют и предоставляют для скачивания порталы электронного правительства РК. Эти PDF файлы содержат QR коды как отдельные внедренные изображения.
Экспериментировать я буду на справке об отсутствии судимости.
Я воспользуюсь следующими библиотеками:
-
You must be registered for see linksдля извлечения изображений из PDF документа;
-
You must be registered for see linksдля декодирования QR кодов;
-
You must be registered for see linksдля распаковки ZIP файлов;
-
You must be registered for see linksдля обработки XML;
-
You must be registered for see linksдля вычисления хешей и кодирования/декодирования данных.
0. Считывание PDF файла в ArrayBuffer
Получить доступ к PDF файлу возможно стандартными средствами HTML с помощью тега и его атрибута
You must be registered for see links
.В современных браузерах получить содержимое файла в виде ArrayBuffer можно следующим образом:
const fileContents = await fileInput.files[0].arrayBuffer();
1. Извлечение изображений из PDF документа
Библиотеку PDF.js необходимо инициализировать перед началом работы, примеры приведены в документации
You must be registered for see links
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
В PDF.js объекты описываются с точки зрения выполняемых над ними операций. Так как меня интересуют изображения, то нужно искать следующие операции:
const ops = [
pdfjsLib.OPS.paintJpegXObject,
pdfjsLib.OPS.paintImageXObject,
];
Реализация извлечения изображений со всех страниц PDF документа:
const loadingTask = pdfjsLib.getDocument(fileContents);
const pdf = await loadingTask.promise;
const objIDs = [];
const images = [];
await (async function () {
for (let pageIndex = 1; pageIndex / Страница содержит набор операторов, нужно найти интересующие.
const operators = await page.getOperatorList();
for (let i = 0; i < operators.fnArray.length; i++) {
const fn = operators.fnArray;
if (ops.indexOf(fn) !== -1) {
// По индексу оператора можно получить его параметры, первый параметр - идентификатор объекта.
const objID = operators.argsArray[0];
// Над одним и тем же объектом могут выполняться несколько операций, дубликаты не нужны.
if (objIDs.indexOf(objID) === -1) {
objIDs.push(objID);
// Объект изображения можно получить по его идентификатору.
try {
const imageInfo = page.objs.get(objID);
images.push(imageInfo);
} catch (err) {
console.log(err);
}
}
}
}
}
})()
2. Декодирование QR кодов
Библиотека jsQR поддерживает изображения только в
You must be registered for see links
в то время как в PDF файлы они могут быть внедрены и как
You must be registered for see links
, потребуется функция приводящая RGB к RGBA:function extractRGBAData(image) {
if (image.kind === 3) { // ImageKind.RGBA_32BPP из
You must be registered for see links
return image.data;
}
if (image.kind !== 2) { // ImageKind.RGB_24BPP из
You must be registered for see links
throw new Error(`Image kind "${image.kind}" is not supported.`);
}
const data = new Uint8ClampedArray(image.width * image.height * 4);
let destPosition = 0;
for (let srcPosition = 0; srcPosition < image.data.length
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = image.data[srcPosition++];
data[destPosition++] = 255;
}
return data;
}
Попробую декодировать все полученные изображения:
const qrCodes = [];
images.forEach((image) => {
if (image.data) {
const data = extractRGBAData(image);
try {
const code = jsQR(data, image.width, image.height);
console.log(code);
qrCodes.push(code);
} catch (err) {
console.log(err);
}
}
});
В результате в консоль браузера выведено 7 сторк — по одной на каждый QR код на странице. Одна из строк содержит URL документа — QR код с ней размещен в верхней правой части документов, она меня не интересует. Остальные 6 строк содержат XML следующего формата (персональные данные удалены):
...
...
1
6
...
Из этого меня интересуют следующие теги:
- ... — часть данных
- 1 — индекс текущей части
- 6 — общее количество частей на которые разделены данные
3. Извлечение частей данных
Для распределения частей данных по соответствующим позициям я воспользуюсь следующей функцией:
const qrCodesBlocks = [];
function addQRCodeBlock(code) {
if (!code || !code.data) {
return;
}
// Получу общее количество частей.
const elementsAmountRegexp = /((.|\r|\n)+?)elementsAmount>/;
const elementsAmountResult = elementsAmountRegexp.exec(code.data);
if (!elementsAmountResult || elementsAmountResult.length ');
}
// При обработке первой части нужно инициализировать массив.
if (qrCodesBlocks.length === 0) {
for (let i = 0; i < elementsAmount; i++) {
qrCodesBlocks.push('');
}
} else {
if (qrCodesBlocks.length !== elementsAmount) {
throw new Error(`В разных QR кодах указано разное общее количество QR кодов: "${qrCodesBlocks.length}" и "${elementsAmount}"`);
}
}
// Получу индекс части.
const elementNumberRegexp = /((.|\r|\n)+?)elementNumber>/;
const elementNumberResult = elementNumberRegexp.exec(code.data);
if (!elementNumberResult || elementNumberResult.length < 2) {
throw new Error(`В QR коде отсутствует ""`);
}
const elementNumber = +elementNumberResult[1];
if (!Number.isSafeInteger(elementNumber)) {
throw new Error(`"" в QR коде не является числом`);
}
// Защита от внештатных ситуаций.
if (elementNumber > elementsAmount) {
throw new Error(`Индекс QR кода "${elementNumber}" больше общего количества QR кодов "${elementsAmount}"`);
}
if (qrCodesBlocks[elementNumber - 1] !== '') {
throw new Error(`Индекс QR кода "${elementNumber}" обнаружен более одного раза`);
}
// Помещу часть в соответствую позицию.
const elementDataRegexp = /((.|\r|\n)+?)elementData>/;
const elementDataResult = elementDataRegexp.exec(code.data);
if (!elementDataResult || elementDataResult.length < 2) {
throw new Error('В QR коде отсутствует ""');
}
const elementData = elementDataResult[1];
qrCodesBlocks[elementNumber - 1] = elementData;
}
Осталось получить части и проверить что распределение прошло успешно.
qrCodes.forEach(addQRCodeBlock);
if (qrCodesBlocks.length === 0) {
throw new Error('Ошибка при извлечении данных из QR кодов: не обнаружено ни одного QR кода с поддерживаемыми данными');
}
const foundBlocks = qrCodesBlocks.filter((block) => !!block);
if (qrCodesBlocks.length !== foundBlocks.length) {
throw new Error('Ошибка при извлечении данных из QR кодов: не удалось получить данные всех QR кодов');
}
4. Восстановление данных
Анализ частей данных показал что это ZIP архив разрезанный на части каждая из которых закодирована в Base64.
В первую очередь нужно декодировать части из Base64:
const zippedParts = qrCodesBlocks.map(block => new Uint8Array(gostCrypto.coding.Base64.decode(block)));
Далее соединить их:
const totalLength = zippedParts.reduce((accumulator, part) => accumulator + part.length, 0);
const zippedData = new Uint8Array(totalLength);
let zippedDataIndex = 0;
zippedParts.forEach((part) => {
zippedData.set(part, zippedDataIndex);
zippedDataIndex += part.length;
});
И распаковать архив:
const zip = await JSZip.loadAsync(zippedData, { checkCRC32: true });
В архиве находится единственный файл с именем one, его содержимое меня и интересует:
const file = zip.file('one');
if (!file) {
throw new Error('В архиве отсутствует ожидаемый файл "one"');
}
const recoveredContents = await file.async("string");
5. Подготовка данных к анализу
Восстановленные данные — это XML следующего формата (персональные данные удалены):
...
...
`1`
Запрос обработан
Запрос обработан
...
UNJUDGED
...
...
...
...
...
...
...
...
...
...
...
...
...
...
Из всего перечисленного меня интересует только digiSign — это еще один XML закодированный в Base64. На остальные данные я не обращаю внимания, так как они будут продублированы глубже.
Извлеку и декодирую внутренний XML:
const regexp = /((.|\r|\n)+?)digiSign>/;
const regexpResult = regexp.exec(recoveredContents);
if (!regexpResult && regexpResult.length !== 2) {
throw new Error('В XML отсутствует ""');
}
const digiSignBytes = gostCrypto.coding.Base64.decode(regexpResult[1]);
const xmlDataAndSignature = gostCrypto.coding.Chars.encode(digiSignBytes, 'utf8');
6. Анализ данных
Содержимое внутреннего XML выглядит следующим образом (персональные данные удалены):
UNJUDGED
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
Это
You must be registered for see links
с внедренными данными в которых, судя по всему, указано что судимостей у субъекта нет UNJUDGED, а так же приведены данные для идентификации субъекта в теге ....
7. Проверка целостности подписанных данных
Проверка целостности данных будет заключаться в сравнении приведенного в XML подписи значения хеша с вычисленным.
Создание объекта XML документа:
const xml = XmlDSigJs.Parse(xmlDataAndSignature);
Значение хеша приведено в теге ...:
const xmlSignatures = XmlDSigJs.Select(xml, "//*[local-name(.)='Signature' and namespace-uri(.)='
You must be registered for see links
']");if (xmlSignatures.length === 0) {
throw new Error(`В распакованных данных отсутствует цифровая подпись (тег ""): "${xmlDataAndSignature}"`);
}
if (xmlSignatures.length > 1) {
throw new Error(`В распакованных данных присутствует несколько цифровых подписей (тег ""): "${xmlDataAndSignature}"`);
}
const hashElementsInSignature = XmlDSigJs.Select(xmlSignatures[0], "//*[local-name(.)='DigestValue']");
if (hashElementsInSignature.length === 0) {
throw new Error(`В XML подписи отсутствует хеш (тег ""): "${xmlDataAndSignature}"`);
}
if (hashElementsInSignature.length > 1) {
throw new Error(`В XML подписи присутствует несколько хешей (тег ""): "${xmlDataAndSignature}"`);
}
const hashInSignature = hashElementsInSignature[0].textContent;
Данные необходимо подготовить к хешированию — выполнить над ними трансформации приведенные в теге ... XML подписи:
const xmlDsigEnvelopedSignatureTransform = new XmlDSigJs.XmlDsigEnvelopedSignatureTransform();
xmlDsigEnvelopedSignatureTransform.LoadInnerXml(xml.documentElement);
xmlDsigEnvelopedSignatureTransform.GetOutput();
const xmlDsigC14NWithCommentsTransform = new XmlDSigJs.XmlDsigC14NWithCommentsTransform();
xmlDsigC14NWithCommentsTransform.LoadInnerXml(xml.documentElement);
const signedDataXML = xmlDsigC14NWithCommentsTransform.GetOutput();
const dataToHash = gostCrypto.coding.Chars.decode(signedDataXML, 'utf8');
В подписи указан алгоритм хеширования "
You must be registered for see links
", это ГОСТ 34.311-95 что аналогично GOST R 34.11-94 в библиотеке gostCrypto. В РК совместно с ним обычно используют набор параметров D-TEST.Вычисление значения хеша:
const hashBytes = await gostCrypto.subtle.digest({name: 'GOST R 34.11-94', version: 1994, sBox: 'D-TEST'}, dataToHash);
const signedDataXMLHash = gostCrypto.coding.Base64.encode(hashBytes);
Проверка целостности данных в моем документе прошла корректно:
if (signedDataXMLHash !== hashInSignature) {
throw new Error(`Хеш вычисленный из данных XML документа "${signedDataXMLHash}" не соответствует значению в подписи "${hashInSignature}"`);
}
Заключение
Эксперимент завершился успешно, поставленная цель достигнута. Но нужно упомянуть о том, что без проверки цифровой подписи говорить о целостности данных несколько лукаво — нет уверенности в том, что значение хеша данных в XML подписи не было изменено.
Еще одним нюансом оказалось то, что разные типы документов электронного правительства РК имеют разную структуру: в некоторых случаях восстановленный документ сразу является XML подписью, в других в digiSign подпись не закодирована в Base64, а вместо этого представлена в
You must be registered for see links
, бывают и другие варианты. В связи с чем приведенный выше алгоритм не является универсальным и требует доработки под разные типы документов.Полезные ссылки:
-
You must be registered for see links
-
You must be registered for see links
-
You must be registered for see links



