Блог · Безпека

Чому приватний ключ ніколи не повинен покидати браузер: архітектура клієнтської криптографії

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

Приватний ключ КЕП — це ваша юридична особа в цифровому світі. Розбираємо три архітектури електронного підпису — ключ на сервері, криптографія в JS сторінки та ізольований iframe-віджет — і пояснюємо, чому безпечна лише остання.

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


Коротка відповідь. Хто володіє приватним ключем і паролем до нього, той може підписати будь-що від імені власника — тому безпека електронного підпису починається з простого правила: ключ ніколи не повинен покидати браузер користувача. Передача ключа на сервер — найгірша з можливих архітектур, але й «просто в браузері» недостатньо: XSS на сторінці дістає і ключ, і пароль. Робоча модель — клієнтська криптографія в iframe на чужому origin: браузер сам, на рівні Same-Origin Policy, гарантує, що код сайту-хоста не бачить вміст віджета. Саме так побудовано DSTUcrypt: сайт передає дані для підпису — і отримує назад лише готовий signature.

Що насправді означає «завантажте ваш ключ» на сторонньому сайті

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

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

Ключ на сервері: найгірша модель

Найпростіша для розробника схема — прийняти файл ключа й пароль на бекенд і підписувати там. Вона ж — найнебезпечніша:

  • Одна точка масового витоку. Злам сервера (або бекапів, або логів, куди «випадково» потрапив пароль) означає компроментацію ключів одразу всіх користувачів — не одного.
  • Довіра людям, а не математиці. Адміністратор сервера, підрядник з доступом до бази, будь-хто в ланцюжку постачання може підписувати документи від імені користувачів — і жоден аудит логів не відрізнить такий підпис від справжнього, бо криптографічно він справжній.
  • Користувач втрачає контроль. Він фізично не бачить, що саме підписується його ключем і скільки разів.

Саме тому «ключ ЕЦП на сервері» — антипатерн незалежно від того, наскільки надійним здається сам сервер: архітектура, у якій витік можливий, рано чи пізно ним закінчується.

Чому «просто в браузері» — теж мало

Логічний наступний крок — виконувати криптографію на клієнті: підключити JS-бібліотеку прямо на свою сторінку й підписувати локально. Ключ на сервер уже не їде — і це прогрес. Але тепер ключ і пароль живуть у тому самому JS-контексті, що й увесь інший код сторінки: ваші скрипти, аналітика, чат-віджети, рекламні теги, залежності з npm.

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

Origin-ізоляція: криптографія в iframe чужого origin

У браузері вже є перевірений десятиліттями механізм ізоляції — Same-Origin Policy. Документи з різних origin-ів (схема + домен + порт) не мають доступу до DOM і даних одне одного. На цьому тримається вся безпека вебу: саме тому вкладка з випадковим сайтом не може прочитати вашу пошту в сусідній вкладці.

Клієнтська криптографія DSTUcrypt використовує цей механізм напряму: віджет підпису працює в iframe, завантаженому з origin dstucrypt.io — іншого, ніж сайт-хост. Користувач обирає файл ключа і вводить пароль усередині цього iframe, там-таки WebAssembly-ядро виконує всі обчислення. Жоден JS сайту-хоста — включно зі шкідливим, що потрапив туди через XSS, — не може зазирнути у вміст iframe: це гарантує не наш код і не «чесне слово», а сам браузер на рівні фундаментального механізму безпеки. Спілкування між сторінкою і віджетом іде лише через postMessage — вузький канал, яким ходять тільки дані операції та результат.

Чому файли віджета принципово не можна хостити в себе

Часте питання інтеграторів: «а можна скопіювати ваші файли й WASM на наш сервер?» Не можна — і це не комерційне обмеження, а принципова частина моделі безпеки. Уся описана вище ізоляція існує лише тому, що iframe завантажується з іншого origin. Якби файли віджета лежали на вашому сервері, iframe мав би ваш origin, межа origin-ів зникла б — і будь-який XSS на вашій сторінці знову читав би ключі й паролі користувачів, як у моделі «JS на самій сторінці».

Тому інтеграція зводиться до одного імпорту SDK з https://dstucrypt.io/embed/dstucrypt-embed.mjs: SDK сам будує адресу віджета зі свого origin, а ви нічого не хостите. Бонус цієї схеми — ви завжди на актуальній версії криптоядра без ручних оновлень: виправлення й нові можливості доїжджають до ваших користувачів автоматично.

Що отримує сайт-хост: лише готовий результат

Межу iframe перетинають лише дані, які у сайту і так є, та результат операції:

  • Сторінка → iframe: байти документа й параметри підпису (формат, геш).
  • iframe → сторінка: готовий підпис signature, ідентифікатор сертифіката підписанта signerCertId і ознака accredited — чи виданий сертифікат акредитованим надавачем, тобто чи є підпис КЕП.

Байти ключа й пароль цю межу не перетинають ніколи — ні в бік сайту-хоста, ні в бік серверів DSTUcrypt. Обмін іде всередині браузера через postMessage: байти передаються напряму (ArrayBuffer), без проміжних base64-рядків і без мережевих запитів.

Три архітектури поруч: порівняння

Критерій Ключ на сервері Криптографія в JS сторінки Ізольований iframe-віджет
Витік при зламі сервера масовий: витікають ключі всіх користувачів масовий: підмінений JS сторінки краде ключі користувачів ключів на серверах немає — красти нічого
Витік при XSS на сторінці так: скрипт перехоплює ключ і пароль при завантаженні так: ключ і пароль у тому ж JS-контексті ні: Same-Origin Policy закриває вміст iframe від коду сторінки
Хто оновлює крипто ваша команда, вручну ваша команда, вручну (ризик застарілої бібліотеки) DSTUcrypt, автоматично для всіх доменів

Як це виглядає в коді

Уся архітектура ховається за кількома рядками на сторінці-хості — бекенд для підпису не потрібен узагалі:

підпис у ізольованому iframe — сторінка бачить лише результат

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

// віджет відкривається в iframe на origin dstucrypt.io —
// ключ і пароль користувач вводить усередині нього
const signer = await embed('sign', { mount: 'modal' });

const { signature, signerCertId, accredited } = await signer.sign(fileBytes, {
  format: 'CAdES-T',
});

// на сторінку повертається ЛИШЕ готовий результат — байти підпису
signature.download('document.p7s');

Геш підпису теж під контролем: за замовчуванням це ДСТУ ГОСТ 34.311-95 (саме його приймають держвалідатори), а сучасну «Купину» (ДСТУ 7564:2014) вмикає одна опція digest:'kupyna-256' — усе так само всередині ізольованого iframe.

Що дає DSTUcrypt поверх origin-ізоляції. Криптографію виконує не саморобний JS, а перевірене нативне C/C++-ядро, скомпільоване у WebAssembly: константний час операцій проти timing-атак, коректний одноразовий k у кожному підписі, ентропія з CSPRNG браузера. Мітки часу TSP та OCSP-статуси ходять через вбудований проксі з коробки, а оновлення криптоядра доїжджають до всіх інтеграцій автоматично — без релізів на вашому боці.

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

Чи бачить DSTUcrypt мій ключ або пароль?

Ні. Уся криптографія виконується у браузері користувача (WebAssembly) всередині iframe. Ключ і пароль вводяться в iframe і не передаються ні на сервери DSTUcrypt, ні на сайт, у який вбудовано віджет.

Чому віджети не можна розмістити на власному сервері?

Бо ізоляція ключа тримається на межі origin-ів: віджет виконується на origin dstucrypt.io, і Same-Origin Policy браузера не пускає код вашої сторінки до його вмісту. Якби файли лежали у вас, межа origin-ів зникла б — і будь-який XSS на вашій сторінці читав би ключі користувачів. Бонус: ви завжди на актуальній версії без ручних оновлень.

Що саме отримує мій сайт після підпису?

Лише готовий результат: байти підпису (signature), ідентифікатор сертифіката підписанта (signerCertId) і ознаку accredited — чи виданий сертифікат акредитованим надавачем, тобто чи це КЕП. Байти ключа й пароль межу iframe не перетинають ніколи.

Чи безпечний режим «запам'ятати ключ»?

Так, він спроєктований так, щоб ключ і далі не покидав браузер: контейнер зберігається зашифрованим (AES-GCM-256, ключ шифрування виводиться з PIN через PBKDF2-SHA256 із 310 тисячами ітерацій) у localStorage origin dstucrypt.io і жорстко прив'язаний до домену-хоста — сайт А не дістане ключ, збережений на сайті Б. PIN ніде не зберігається.

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

КЕП на вашому сайті — без ключів на сервері

Готові віджети підпису, перевірки та шифрування за ДСТУ. Ключ і пароль не покидають браузер користувача. На кожному новому домені — 7 днів безкоштовно.

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

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

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