Блог · Для розробників

Вхід на сайт за КЕП: як зробити авторизацію користувачів електронним підписом

Опубліковано

Без паролів, без СМС і без «забув пароль»: користувач підтверджує особу своїм ключем КЕП, а ваш бекенд отримує криптографічно перевірені ПІБ, РНОКПП та ЄДРПОУ. Розбираємо схему challenge–response крок за кроком — із готовим кодом інтеграції.

Опубліковано 7 серпня 2026


Коротка відповідь. Вхід за КЕП — це схема challenge–response: ваш бекенд бере в DSTUcrypt одноразовий challenge (POST /api/auth/challenge), користувач підписує його своїм ключем в auth-віджеті прямо у браузері, а бекенд віддає підпис на серверну перевірку (POST /api/auth/verify). У відповідь приходить криптографічно підтверджена особа: ПІБ, РНОКПП, ЄДРПОУ, дані сертифіката й ознака qualified — що це саме вхід за КЕП. Підпис неможливо підробити чи використати повторно, а ключ користувача ніколи не покидає його браузер.

Навіщо взагалі вхід за КЕП

Класична авторизація — логін, пароль, СМС-код — доводить лише те, що людина знає пароль або тримає в руках телефон. Вона нічого не каже про те, хто ця людина юридично. Для багатьох українських сервісів цього замало: особистих кабінетів із документами, фінансових сервісів, B2B-порталів, систем електронного документообігу, послуг для бізнесу й держпослуг.

Вхід за електронним підписом розв'язує обидві задачі одразу. Користувачу не треба реєструватися, вигадувати пароль і чекати СМС — він просто підтверджує особу ключем, який у нього вже є. А ви отримуєте не «якийсь email», а ідентифікацію рівня КЕП: повне ім'я, РНОКПП і код ЄДРПОУ з кваліфікованого сертифіката, перевіреного криптографічно. Це той самий рівень довіри, на якому будуються державні сервіси, — тепер доступний вашому продукту через один віджет і два серверні виклики.

Як це працює технічно: чотири кроки

В основі — схема challenge–response, яка унеможливлює і підробку, і повторне використання підпису:

  1. Бекенд бере одноразовий challenge. Ваш сервер робить POST https://dstucrypt.com.ua/api/auth/challenge і отримує { challenge, expiresIn }. Challenge живе 5 хвилин і згорає після першої перевірки — це захист від повторного використання.
  2. Користувач підписує challenge ключем у віджеті. На вашій сторінці auth-віджет DSTUcrypt відкриває модалку, користувач обирає свій ключ, вводить пароль — і підписує challenge. Уся криптографія виконується в браузері (WebAssembly) всередині iframe на нашому origin: ключ і пароль не бачить ні ваша сторінка, ні будь-який сервер.
  3. Сторінка передає підпис на ваш бекенд. Фронтенд надсилає пару { challenge, signature } на ваш власний endpoint входу.
  4. Бекенд віддає підпис на серверну криптоперевірку. Ваш сервер викликає POST https://dstucrypt.com.ua/api/auth/verify — і нативне криптоядро на сервері DSTUcrypt перевіряє підпис, цілісність, збіг вмісту з challenge і чинність сертифіката. У відповідь — { ok, subject{…} } з підтвердженою особою.

Технічна деталь: підпис, який формує auth-віджет, — це attached CAdES над challenge, за замовчуванням із гешем ДСТУ ГОСТ 34.311-95 (криптоядро підтримує й сучасну «Купину» за ДСТУ 7564:2014 — що це за геш-функція, ми детально розбирали в окремій статті).

Чому перевірку не можна робити в браузері

Спокуса зрозуміла: взяти JS-бібліотеку, перевірити підпис прямо на фронтенді й «зекономити» серверний виклик. Для авторизації це не production-безпечне рішення з двох причин.

Перша — принципова. Результат перевірки формується на боці клієнта, а браузер повністю контролює той, хто в ньому сидить. Атакуючому не потрібен жоден ключ: досить відкрити DevTools і підмінити «підпис дійсний» — і ваш фронтенд радісно повідомить бекенду, що вхід успішний. Рішення про допуск, ухвалене в браузері, за визначенням не варте нічого.

Друга — практична. У відкритих чисто-JS реалізаціях ДСТУ відомі цілі класи вразливостей: timing-атаки (криптографія без константного часу виконання) і слабка генерація випадкового k, що уможливлює відновлення приватного ключа з підписів. Ми розбирали ці атаки детально в статті про небезпеку чисто-JS криптографії.

Як це закриває DSTUcrypt. Підпис формується в ізольованому iframe на нашому origin (WASM, константний час), а перевірка виконується на нашому сервері нативним криптоядром — перевіреною C/C++-бібліотекою, що роками працює в українській PKI. Результат серверної перевірки неможливо підробити на клієнті: браузеру не довіряється нічого, крім самого підпису. У відповідь ви отримуєте підтверджену особу — ПІБ, РНОКПП, ЄДРПОУ, — а ключ користувача при цьому ніколи не покидає його браузер.

Код інтеграції auth-віджета

Серверна частина — два виклики. Спершу бекенд бере challenge (server-to-server, не з браузера), наприкінці — віддає підпис на перевірку:

бекенд: challenge і серверна перевірка

# 1) одноразовий challenge (живе 5 хвилин, згорає після першої перевірки)
curl -X POST https://dstucrypt.com.ua/api/auth/challenge
# → { "challenge": "dstucrypt-login-v1:…", "expiresIn": 300 }

# 4) серверна криптоперевірка підпису
curl -X POST https://dstucrypt.com.ua/api/auth/verify \
  -H "Content-Type: application/json" \
  -d '{ "challenge": "dstucrypt-login-v1:…", "signature": "<base64>" }'

На сторінці — auth-віджет: користувач підписує challenge, а підпис іде на ваш бекенд:

фронтенд: auth-віджет підписує challenge

import { embed } from 'https://dstucrypt.io/embed/dstucrypt-embed.mjs';

const auth = await embed('auth', { mount: 'modal' });
const { signature, subject, accredited } = await auth.login({ challenge });
//  subject/accredited тут — ЛИШЕ для UX (показати «Вітаємо, Іване!» / попередити,
//  що ключ не КЕП); довіряти можна тільки відповіді /api/auth/verify (крок 4)

await fetch('/api/my-backend/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ challenge, signature }),   //  signature — base64-рядок
});

Успішна відповідь /api/auth/verify містить ok: true, об'єкт subject, а також signatureFormat, signingTime, status (наприклад, TOTAL-VALID) і дві ключові ознаки: accredited — сертифікат від акредитованого надавача, та qualified — це вхід саме за КЕП (акредитований + дійсний). Що всередині subject:

Поле Що містить
fullName ПІБ підписанта («Іваненко Іван Іванович»)
taxId РНОКПП — стабільний ідентифікатор особи
orgCode код ЄДРПОУ організації
issuer АЦСК, що видав сертифікат
certSerial серійний номер сертифіката
validFrom / validTo строк дії сертифіката

Відмови теж передбачувані:

HTTP reason Причина
401 challenge_invalid_or_used challenge невідомий, прострочений або вже використаний
200 signature_invalid ( ok:false ) підпис не пройшов криптоперевірку, вміст ≠ challenge, або сертифікат нечинний
400 bad_request / bad_signature_encoding некоректне тіло запиту

Що робити з отриманою особою

Після ok: true ви створюєте власну сесію — свій cookie, JWT чи що завгодно: DSTUcrypt не керує вашими сесіями, він лише авторитетно відповідає «хто це».

  • Ідентифікатор акаунта — subject.taxId (РНОКПП). Це стабільний ідентифікатор особи: він не змінюється при перевипуску ключа, зміні надавача чи прізвища. Якщо акаунт із таким РНОКПП уже є — це повторний вхід; якщо ні — створюєте новий акаунт із заповненим ПІБ.
  • Для B2B — matching по orgCode (ЄДРПОУ). Якщо користувач входить ключем співробітника організації, ЄДРПОУ дозволяє автоматично прив'язати його до компанії у вашій системі — наприклад, пустити в кабінет контрагента без ручного підтвердження менеджером.
  • Вимагаєте саме КЕП — перевіряйте qualified . qualified: true означає вхід за КЕП; якщо для вашого сценарію потрібен лише КЕП — відхиляйте qualified: false.

І головне правило довіри: рішення про вхід ухвалюється тільки за відповіддю /api/auth/verify (server-to-server). Поля subject і accredited, які повертає віджет на фронтенді, — best-effort дані виключно для UX: привітати користувача на ім'я чи одразу попередити, що його ключ не КЕП.

Безпека і приватність

Найчастіше питання від служб безпеки: «а куди подорожує ключ користувача?» Відповідь — нікуди. Ключ і пароль вводяться всередині iframe, що завантажується з origin dstucrypt.io: за Same-Origin Policy до них не має доступу навіть код вашої сторінки (у т.ч. при XSS), не кажучи вже про сервери. Уся криптографія виконується локально у браузері (WebAssembly), і назовні виходить лише результат — підпис challenge.

Сам протокол додає ще два рівні захисту. По-перше, challenge одноразовий і короткоживучий: перехоплений підпис не можна «переграти» — повторна перевірка поверне 401 challenge_invalid_or_used. По-друге, перевірка серверна: навіть повністю скомпрометований браузер користувача не може переконати ваш бекенд, що недійсний підпис — дійсний.

Типові сценарії

  • Особистий кабінет без реєстрації. Перший вхід за КЕП одразу створює акаунт із перевіреним ПІБ і РНОКПП — жодних форм, email-підтверджень і паролів.
  • Фінансові сервіси й страхування. Ідентифікація клієнта рівня КЕП перед доступом до договорів, виплат чи персональних даних.
  • B2B-портали й кабінети контрагентів. Автоматична прив'язка користувача до компанії за ЄДРПОУ з сертифіката — постачальник входить у портал закупівель без листування з адміністратором.
  • Документообіг. Той самий ключ, яким користувач входить, підписує й документи — вхід і підпис живуть в одній інтеграції DSTUcrypt.
  • Крок підтвердження критичних дій. Схему challenge–response можна використати не лише для входу, а й як «підпишіть, щоб підтвердити» перед чутливою операцією.

Поширені запитання

Чи бачить DSTUcrypt чи мій сервер ключ користувача під час входу?

Ні. Ключ і пароль вводяться всередині iframe-віджета на origin dstucrypt.io, а вся криптографія виконується у браузері (WebAssembly). Назовні виходить лише підпис challenge — ключ і пароль не потрапляють ні на ваш сервер, ні на сервери DSTUcrypt.

Чи можна використати перехоплений підпис для повторного входу?

Ні. Challenge одноразовий: він живе 5 хвилин і згорає після першої перевірки. Повторний запит із тим самим challenge отримає відмову 401 із reason challenge_invalid_or_used — тому перехоплений підпис не дає зловмиснику нічого.

Що означає qualified: false у відповіді перевірки?

Це означає, що вхід виконано дійсним підписом, але не КЕП: сертифікат не від акредитованого надавача або не пройшов усі перевірки. Якщо для вашого сценарію потрібен саме вхід за КЕП — відхиляйте відповіді з qualified: false.

За яким полем створювати акаунт користувача?

За subject.taxId — це РНОКПП, стабільний ідентифікатор особи, який не змінюється при перевипуску ключа чи зміні надавача. Для сценаріїв від імені організації додатково використовуйте subject.orgCode (ЄДРПОУ).

Чому не можна перевіряти підпис JS-бібліотекою в браузері?

Бо результат перевірки формується на боці клієнта: атакуючий контролює браузер і може підмінити «підпис дійсний» без жодного ключа. Крім того, у відкритих чисто-JS реалізаціях ДСТУ відомі класи вразливостей — timing-атаки та слабка генерація випадкового k, що уможливлює відновлення приватного ключа з підписів. Тому DSTUcrypt виконує перевірку на сервері нативним криптоядром.

Читайте також

Вхід за КЕП на вашому сайті — за один вечір

Готовий auth-віджет плюс два серверні виклики — і ваш бекенд отримує підтверджену особу: ПІБ, РНОКПП, ЄДРПОУ. Ключ і пароль не покидають браузер користувача. На кожному новому домені — 7 днів безкоштовно.

Живе демоЯк підключити

Спробуйте КЕП на своєму сайті

Готові віджети підпису, перевірки та входу за ДСТУ. На кожному новому домені — 7 днів безкоштовно.