Short answer. To create a QES with the Kupyna hash (kupyna-256, DSTU 7564:2014) right in the browser, all you need is one import of the DSTUcrypt SDK and one option in the signing call: signer.sign(fileBytes, { format: 'CAdES-T', digest: 'kupyna-256' }). All the cryptography runs in the user's browser (WebAssembly); the key and password never leave it. One caveat: government validators (Diia, the central CA / CZO) currently accept the "DSTU 4145 + GOST 34.311" pair — so for documents headed to government systems keep the default hash, and enable Kupyna where the receiving party supports it.
What Kupyna is — in a minute
Kupyna is a Ukrainian cryptographic hash function, standardized as DSTU 7564:2014. In a signature it plays a preparatory role: it compresses a document of any size into a short digest (256 bits for Kupyna-256), and it is this digest that gets signed with your DSTU 4145 key. In other words, a "DSTU 7564 signature" is really an ordinary DSTU 4145 signature in which the GOST 34.311-95 hash function has been replaced with the modern Kupyna.
An important consequence: no new key is needed — the hash function is "clipped on" to your existing key at every signing. A detailed breakdown of the standard itself, its construction, and a comparison with GOST is in the article "Kupyna (DSTU 7564:2014): what this hash function is and why it replaces GOST 34.311-95".
When to choose Kupyna and when GOST 34.311
The main practical rule: look at who will verify the signature. Government validators — the Diia application and the central certification authority — currently accept the pair "DSTU 4145 signature + DSTU GOST 34.311-95 hash"; Diia rejects Kupyna-in-CMS. That is why the default hash in DSTUcrypt is 'gost-34311': a signature made without the digest option is guaranteed to be accepted by government systems.
- The document goes to Diia, the central CA (CZO), or government registers — sign with GOST 34.311 (the default, or explicitly
digest: 'gost-34311'). - Internal document workflow, B2B exchange, your own verification system — enable Kupyna explicitly:
digest: 'kupyna-256'. It is the modern national standard with a solid cryptographic strength margin. - Not sure — set
digestexplicitly in both cases: an explicitly specified hash always takes priority, and your integration's behavior becomes predictable.
When government systems switch to Kupyna, changing the default in your code is a one-line edit — no rebuilds or updates.
Step-by-step guide: a Kupyna QES in four steps
All you need is a page on your website and the user's key file (PKCS#12/PFX, JKS, PKCS#8, or Key-6.dat). There is nothing to host yourself — the SDK loads the widget and the WASM core from its own origin.
Step 1. Import the SDK with a single line. Step 2. Create the signing widget — embed('sign'). Step 3. Call sign() with the digest: 'kupyna-256' option — the user picks their key and enters the password inside the widget's iframe. Step 4. Collect the result: signature.download() for the user, or signature.base64 / signature.bytes for your backend.
a CAdES-T signature with the Kupyna hash — full example
import { embed } from 'https://dstucrypt.io/embed/dstucrypt-embed.mjs';
const signer = await embed('sign', { mount: 'modal' });
const { signature, accredited } = await signer.sign(fileBytes, {
format: 'CAdES-T', // with a TSP timestamp
digest: 'kupyna-256', // Kupyna, DSTU 7564; without this option — GOST 34.311
});
signature.download('document.p7s'); // or signature.base64 / .bytes / .blob()
fileBytes can be a File, Blob, ArrayBuffer, Uint8Array, or a string — no Base64 handling needed in your code. The accredited field tells you whether the signer's certificate was issued by an accredited provider (i.e. whether this is indeed a QES). The timestamp for CAdES-T is applied by built-in TSP servers with automatic fallbacks — nothing to configure.
The same hash works in the other format families too — for example, an XML signature with Kupyna:
XAdES with Kupyna-256 — a rarity on the web
const { signature } = await signer.sign(xmlBytes, {
format: 'XAdES-B-T', // XML-DSig: dstu4145-dstu7564-256
digest: 'kupyna-256',
fileName: 'contract.xml',
});
signature.download('contract.xml');
Kupyna across the signature formats
The digest option applies to all 16 supported formats — from bare CMS to archival levels with timestamps. The only difference is in the level names: CAdES uses the older nomenclature (CAdES-T), while XAdES and PAdES use the baseline form (XAdES-B-T, PAdES-B-T).
| Family | format values | File | Kupyna via digest:'kupyna-256' |
|---|---|---|---|
| CAdES (CMS) | CAdES-BES · CAdES-T · CAdES-C · CAdES-XL | .p7s | yes; a detached mode is available too |
| PAdES (PDF) | PAdES-B-B · PAdES-B-T · PAdES-B-LT · PAdES-B-LTA | yes — the signature is embedded right in the PDF | |
| XAdES (XML) | XAdES-BES · XAdES-B-T · XAdES-B-LT · XAdES-B-LTA | .xml | yes — DSTU 4145 + Kupyna-256 in XML-DSig, a rarity on the web |
| ASiC-S / ASiC-E | ASiC-S-BES · ASiC-S-T · ASiC-E-BES · ASiC-E-T | .asics / .asice | yes — an "original + signature" ZIP container |
Levels with a timestamp (-T and above) require online access to a TSP — the SDK reaches it through the built-in proxy automatically. Which format to choose for your task is covered separately in the article on the CAdES, PAdES, XAdES, and ASiC formats.
Why this is secure and why it is rare. The key and password are entered inside an iframe on the dstucrypt.io origin and never leave the user's browser — they are inaccessible both to your page's code (even under XSS) and to any servers. The crypto core is a proven native C/C++ library compiled to WebAssembly. It is the full stack of the updated national standards — Kupyna, Kalyna, DSTU 4145 — across 16 signature formats, right on the web.
How to verify what you signed
Verification uses the same kind of widget: const v = await embed('verify'), then v.verify(signatureBytes, null) for an attached signature (pass the data only for detached). The result contains valid and a signers array — with the name, RNOKPP/EDRPOU, signing time, format, and the accredited field, which distinguishes a QES from a merely valid signature. The format is detected automatically from the contents — CAdES, PAdES, XAdES, or ASiC.
To eyeball a signature quickly without code, use the free tool at /verify. And when it is your backend that decides whether to accept a document — use the authoritative server-side POST /api/verify: the same verification, but by the native core on a server the frontend cannot reach.
Common questions and errors
The most frequent "error" with Kupyna is actually not an error: the signature is valid, but the receiving party (for example, Diia) expects GOST 34.311. Check the recipient's requirements before choosing the hash. Among the technical codes: CANCELLED — the user closed the modal, TSP_QUOTA — the daily timestamp quota is exhausted, not_licensed — the domain has no active subscription or trial (the loader shows a tidy modal on its own).
Do I need a separate key to sign with Kupyna?
No. A QES key is a DSTU 4145 signing key, and Kupyna merely replaces the hash function paired with it. Just add the digest:'kupyna-256' option to the sign() call — no key reissuance whatsoever.
Why does Diia reject my signature with Kupyna?
Government validators (Diia, the central CA / CZO) currently accept the pair "DSTU 4145 signature + DSTU GOST 34.311-95 hash" — Diia rejects Kupyna-in-CMS. For documents headed to government systems, sign with digest:'gost-34311' (which is the default), and enable Kupyna where the receiving party supports it.
Which hash is used if I omit the digest option?
Without the digest option the signature is hashed with DSTU GOST 34.311-95 — exactly the pair government validators accept today. For predictability we recommend setting digest explicitly: 'gost-34311' or 'kupyna-256'. An explicitly specified digest always takes priority and applies to all formats.
Does Kupyna work in PDF and XML signatures?
Yes. The digest:'kupyna-256' option applies to all formats: CAdES, PAdES, XAdES, and ASiC. In particular, XAdES with Ukrainian DSTU 4145 keys supports both GOST 34.311 and Kupyna-256 — a rare capability on the web.
Read also
- Kupyna (DSTU 7564:2014): what this hash function is and why it replaces GOST 34.311-95
- Electronic signature formats: CAdES, PAdES, XAdES, ASiC — which to choose
- How to add QES to your website in 10 minutes: an iframe widget with no backend
Kupyna signing on your website — in 10 minutes
Ready-made DSTU widgets for signing, verification, and encryption. The key and password never leave the user's browser. Every new domain gets 7 days free.
Live demoHow to connect