Реалізація верифікації підпису на сайті
Верифікація підпису — це перевірка того, що документ підписаний конкретною особою та не був змінений після підписування. Задача різна для різних типів підписів: для рисованої підписи — перевірка цілісності документа через хеш, для КЕП — криптографічна перевірка сертифіката та ланцюга довіри.
Верифікація за хешем документа
Для простої ЕП (рисована підпись, SMS-підтвердження) верифікація побудована на збереженні хеша документа в момент підписування:
// При підписуванні — зберігаємо хеш
async function recordSignature(documentId, signerId, signatureData) {
const documentBytes = await getDocumentBytes(documentId);
const documentHash = crypto.createHash('sha256').update(documentBytes).digest('hex');
await db.signatures.create({
documentId,
signerId,
documentHash, // SHA-256 від змісту документа
signatureData, // base64 зображення підпису або тип 'sms'
signedAt: new Date(),
signerIp: request.ip,
signerUserAgent: request.headers['user-agent'],
});
}
// При верифікації — порівнюємо хеші
async function verifyDocumentIntegrity(documentId) {
const signatures = await db.signatures.findAll({ documentId });
const currentDocumentBytes = await getDocumentBytes(documentId);
const currentHash = crypto.createHash('sha256').update(currentDocumentBytes).digest('hex');
return signatures.map(sig => ({
signer: sig.signer,
signedAt: sig.signedAt,
isIntact: sig.documentHash === currentHash, // false = документ змінений після підписування
signerIp: sig.signerIp,
}));
}
Верифікація КЕП через CryptoPro
// Клієнтська верифікація через Browser Plugin
async function verifyCadesSignature(documentBase64, signatureBase64) {
const plugin = await cadesplugin;
const signedData = await plugin.CreateObjectAsync('CAdESCOM.CadesSignedData');
await signedData.propset_ContentEncoding(plugin.CADESCOM_BASE64_TO_BINARY);
await signedData.propset_Content(documentBase64);
try {
await signedData.VerifyCades(
signatureBase64,
plugin.CADESCOM_CADES_BES,
true // відділена підпись
);
} catch (e) {
return { valid: false, error: e.message };
}
const signers = await signedData.Signers;
const signer = await signers.Item(1);
const cert = await signer.Certificate;
return {
valid: true,
signer: {
name: await cert.GetInfo(plugin.CAPICOM_CERT_INFO_SUBJECT_SIMPLE_NAME),
issuer: await cert.GetInfo(plugin.CAPICOM_CERT_INFO_ISSUER_SIMPLE_NAME),
validFrom: await cert.ValidFromDate,
validTo: await cert.ValidToDate,
thumbprint: await cert.Thumbprint,
},
signedAt: await signer.SigningTime,
certValid: await cert.IsValid().Result,
};
}
Серверна верифікація через CryptoPro CA API
Для серверної верифікації без плагіна — CryptoPro Web Service API або StampDE:
// PHP: верифікація через CryptoPro Web Service
class CryptoProVerificationService {
public function verifySignature(string $documentBase64, string $signatureBase64): array {
$client = new SoapClient('https://www.cryptopro.ru/ocsp/ocsp.php?wsdl');
$result = $client->VerifyHash([
'Signature' => $signatureBase64,
'Content' => $documentBase64,
'Type' => 'CAdES-BES',
'IsDetached' => true,
]);
return [
'valid' => $result->IsValid,
'signerName' => $result->SignerName,
'signedAt' => $result->SigningTime,
'certSerial' => $result->CertSerialNumber,
];
}
}
Публічна сторінка верифікації
Для зовнішніх користувачів (контрагент перевіряє договір) — публічна сторінка без авторизації:
// GET /verify/:documentId/:signatureId
async function VerificationPage({ params }) {
const result = await verifyDocumentSignature(params.documentId, params.signatureId);
return (
<div className="max-w-2xl mx-auto p-8">
<div className={`rounded-xl p-6 ${result.valid ? 'bg-green-50' : 'bg-red-50'}`}>
<div className="flex items-center gap-3">
{result.valid ? <CheckCircleIcon className="text-green-600 w-8" /> : <XCircleIcon className="text-red-600 w-8" />}
<h1 className="text-xl font-bold">
{result.valid ? 'Підпис дійсний' : 'Підпис недійсний'}
</h1>
</div>
{result.valid && (
<dl className="mt-4 grid grid-cols-2 gap-4 text-sm">
<div><dt className="text-gray-500">Підписант</dt><dd>{result.signerName}</dd></div>
<div><dt className="text-gray-500">Дата підписування</dt><dd>{formatDate(result.signedAt)}</dd></div>
<div><dt className="text-gray-500">Документ не змінений</dt><dd>Так</dd></div>
<div><dt className="text-gray-500">Сертифікат</dt><dd>{result.certSerial}</dd></div>
</dl>
)}
</div>
</div>
);
}
QR-код для верифікації
На підписаному документі розміщується QR-код, що ведення на сторінку верифікації:
import QRCode from 'qrcode';
const verificationUrl = `${process.env.APP_URL}/verify/${documentId}/${signatureId}`;
const qrDataUrl = await QRCode.toDataURL(verificationUrl, {
width: 100,
margin: 1,
errorCorrectionLevel: 'M',
});
Терміни
Верифікація за хешем з публічною сторінкою та QR-кодом — 2–3 дні. Верифікація КЕП через CryptoPro Browser Plugin — 3–4 дні. Серверна верифікація через CryptoPro Web Service — 3–5 днів.







