Signature Verification on Website

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Implementation of Signature Verification on Website

Signature verification is checking that a document was signed by a specific person and was not changed after signing. The task differs for different signature types: for drawn signature — document integrity check via hash, for QES — cryptographic certificate and trust chain verification.

Hash-Based Verification

For simple e-signature (drawn signature, SMS confirmation) verification is based on storing document hash at signing moment:

// During signing — save hash
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 of document content
    signatureData,      // base64 signature image or type 'sms'
    signedAt: new Date(),
    signerIp: request.ip,
    signerUserAgent: request.headers['user-agent'],
  });
}

// During verification — compare hashes
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 = document changed after signing
    signerIp: sig.signerIp,
  }));
}

QES Verification via CryptoPro

// Client-side verification via 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  // detached signature
    );
  } 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,
  };
}

Server-Side Verification via CryptoPro CA API

For server-side verification without plugin — CryptoPro Web Service API or StampDE:

// PHP: verification via 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,
        ];
    }
}

Public Verification Page

For external users (counterparty checking contract) — public page without authentication:

// 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 ? 'Signature is valid' : 'Signature is invalid'}
          </h1>
        </div>

        {result.valid && (
          <dl className="mt-4 grid grid-cols-2 gap-4 text-sm">
            <div><dt className="text-gray-500">Signer</dt><dd>{result.signerName}</dd></div>
            <div><dt className="text-gray-500">Signed</dt><dd>{formatDate(result.signedAt)}</dd></div>
            <div><dt className="text-gray-500">Document not changed</dt><dd>Yes</dd></div>
            <div><dt className="text-gray-500">Certificate</dt><dd>{result.certSerial}</dd></div>
          </dl>
        )}
      </div>
    </div>
  );
}

QR Code for Verification

A QR code is placed on signed document, leading to verification page:

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',
});

Timeline

Hash-based verification with public page and QR code — 2–3 days. QES verification via CryptoPro Browser Plugin — 3–4 days. Server-side verification via CryptoPro Web Service — 3–5 days.