Plank help · updated 2026-09-10
Подпись ключом ЭЦП РК (без NCALayer)
Что на самом деле делает NCALayer, когда государственный портал РК просит подписать, почему это удобство десктопа, а не требование протокола, и как получить ту же подпись из скрипта — вместе с правилами обращения с ключом, которые обязательны всегда.
Agents: fetch the raw markdown of this page at /ru/help/kz-ecp-signing.md
Подпись ключом ЭЦП РК (без NCALayer)
Почти каждый государственный портал Казахстана закрывает действия на запись подписью ЭЦП, и почти каждый просит её через NCALayer — небольшое Java-приложение, которое пользователь ставит себе; оно открывает окно, спрашивает, каким ключом подписать, и возвращает подпись.
NCALayer — это удобство десктопа, а не свойство протокола. Он не хранит секрета, которому доверяет портал, и не выполняет ничего на стороне сервера. Это локальная служба на WebSocket, которая берёт документ, подписывает его файлом ключа, уже имеющимся у пользователя, и отдаёт подпись обратно в страницу. То же самое может сделать что угодно, умеющее читать .p12.
От этого различия зависит, автоматизируем ли портал. «Там нужен NCALayer» — не повод остановиться: именно так Подключение ЭСФ выписывает настоящие счета-фактуры в проде — без NCALayer и без QR на каждую операцию.
1. Схема всегда одна и та же
Любой портал, который подписывает таким способом, работает по одному сценарию:
1. запросить у сервера документ для подписи → он возвращает XML
2. подписать этот XML локально ключом → enveloped XMLDSig
3. отправить подписанный XML обратно → действие зафиксировано
Документ строит сервер, поэтому вы никогда не собираете его сами. Два живых примера:
| Портал | Шаг 1 | Шаг 3 |
|---|---|---|
| Tizilim (тема) | POST /api/auth/esign-auth-xml | POST /api/auth/login-check-esp {xml} |
| ЭСФ (тема) | AuthService.createAuthTicket → authTicketXml | SessionService.createSessionSigned {signedAuthTicket} |
| egov.kz (тема) | POST /v1/<SVC>/xml | POST /v1/<SVC>/signing/send-eds {signed_xml} |
Одно измеренное исключение: у входа на egov.kz шага 1 нет вовсе — документ там константа, которую клиент собирает сам. Двадцать восемь его услуг идут по схеме выше.
Как только эта схема опознана, найти её в новом портале — вопрос поиска по клиентскому бандлу: эндпоинт, который возвращает XML, и эндпоинт, который принимает {xml: …}.
Подпись ставится на действие, а не на сессию. Портал, который подписывает вход, обычно подписывает и каждую публикацию, каждый протокол, каждый отчёт. Закладывайте это в проект: ключ должен быть доступен весь прогон, а сорок опубликованных позиций — это сорок подписей.
2. О чём на самом деле просят NCALayer
Прочитайте запрос, который страница ему отправляет, — это полная спецификация подписи, которую вам нужно воспроизвести. Вот запрос Tizilim, дословно из его бандла:
{
"module": "kz.gov.pki.knca.basics",
"method": "sign",
"args": {
"allowedStorages": ["AKKaztokenStore", "PKCS12"],
"format": "xml",
"data": "<XML, который вернул сервер>",
"signingParams": {
"decode": false, "encapsulate": false, "digested": false, "tsaProfile": null
},
"signerParams": {
"extKeyUsageOids": ["1.3.6.1.5.5.7.3.2"],
"chain": ["-----BEGIN CERTIFICATE----- …корневые НУЦ РК… -----END CERTIFICATE-----"]
},
"locale": "ru"
}
}
В переводе это значит: enveloped XMLDSig по документу как есть — без предварительного base64-декодирования (decode: false), без обёртки (encapsulate: false), без предварительного хеширования (digested: false), без службы штампов времени (tsaProfile: null), ключом, в сертификате которого расширенное назначение 1.3.6.1.5.5.7.3.2, с цепочкой до корневых НУЦ РК, которые страница передаёт сама.
Три вещи, которые стоит знать, когда встречаете такой запрос:
- Сокет —
wss://127.0.0.1:13579/, протокол — JSON-сообщения по WebSocket. Из песочницы туда не достучаться: это loopback пользователя, а не ваш. - У этого API три поколения, и страницы откатываются по ним вниз:
kz.gov.pki.knca.basics/sign(текущее),kz.gov.pki.knca.commonUtils/signXml(старее, позиционные аргументы[storage, signType, xml, "", ""]) и голое{method: "signXml", args: [storage, "", "SIGNATURE", xml]}. Читайте самое новое — два других это та же подпись с худшим соглашением о вызове. - Имена хранилищ, которые встретятся:
PKCS12(файл ключа — обычный случай),AKKaztokenStore,AKKZIDCardStore,AKJaCartaStore,AKAKEYStore,AKEToken5110Store,AKEToken72KStore,JKS. Для автоматизации важен толькоPKCS12; остальное — аппаратные токены, которые должны быть физически воткнуты.
3. Ключ — теперь один файл, два только у старых
С 30 апреля 2024 года НУЦ РК выдаёт ОДИН ключ. Одно регистрационное свидетельство служит и для аутентификации, и для подписи, на алгоритме СТ РК ГОСТ Р 34.10-2015. До этой даты человек получал два невзаимозаменяемых файла; ранее выпущенные свидетельства действуют до конца срока — но срок этот год, так что сегодня у клиента почти наверняка универсальный ключ. Просите «ключ ЭЦП» в единственном числе, а два файла воспринимайте как исключение, которым они стали.
Порталы при этом не изменились, и им незачем: они по-прежнему фильтруют по расширенному назначению, и OID в запросе говорит, какое назначение требуется.
| OID | Значение | Запрашивается при | Эпоха двух ключей |
|---|---|---|---|
1.3.6.1.5.5.7.3.2 | clientAuth | входе | AUTH_RSA…, AUTH… |
1.3.6.1.5.5.7.3.4 | emailProtection | подписи документов | RSA…, GOST… |
egov.kz по-прежнему просит первый на входе и второй на каждой отправке — два явно разных вызова в одном бандле, измерено 10.09.2026 (тема §4).
Очевидный вывод отсюда — что одно универсальное свидетельство обязано нести оба OID — неверен, и в тот же день это было измерено. Настоящее универсальное ГОСТ-свидетельство (ключ первого руководителя ТОО, выпущен в июне 2026) несёт 1.3.6.1.5.5.7.3.4 и казахстанские OID 1.2.398.3.3.4.3.2, 1.2.398.3.3.4.1.2, 1.2.398.3.3.4.1.2.1 — и не несёт 1.3.6.1.5.5.7.3.2. Его подпись логин-документа egov всё равно приняли.
Поэтому читайте extKeyUsageOids как то, чем он является: подборщик ключа для десктопа, а не правило, которое проверяет сервер. Он говорит NCALayer, какой файл на диске предложить; сервер проверяет подпись, а не заявленное назначение сертификата. Универсального ключа хватает даже порталу, который просит clientAuth. Измерено только на входе egov — не считайте, что эндпоинт записи или другой портал так же снисходительны.
EKU всё равно прочитайте — он говорит, ключ какой эпохи у вас в руках:
openssl pkcs12 -info -nokeys -clcerts -in key.p12 -passin file:pw.txt \
| openssl x509 -noout -text | grep -A1 'Extended Key Usage'
-legacy обязателен, и его отсутствие не выглядит как забытый флаг. Все замеренные здесь .p12 НУЦ РК зашифрованы RC2-40-CBC, который OpenSSL 3 по умолчанию отвергает: вы получаете Algorithm (RC2-40-CBC : 0) … unsupported, и это читается как битый файл или неверный пароль, а не как политика по умолчанию. С -legacy тот же файл открывается. На ГОСТ-сертификате OpenSSL дальше разбирает расширения — строка EKU выше печатается, — но говорит Unable to load Public Key: это ожидаемо и безвредно, казахстанских кривых он не умеет. (Измерено 10.09.2026 на собственных тестовых ключах ЭСФ SDK, OpenSSL 3.0.13.)
Смена алгоритма важнее, и она сужает выбор подписанта до одного. Универсальный ключ — ГОСТ, а ГОСТ не подписывает ни одна стандартная библиотека XMLDSig: нужен провайдер с казахстанскими кривыми. Путь на чистом Python из §4 для любого ключа с апреля 2024 года мёртв; Kalkan (или то, что его в себе несёт) — уже не выбор по умолчанию, а единственная дорога. Алгоритм всё равно подтвердите, а не предполагайте:
# -nokeys обязателен: без него openssl печатает РАСШИФРОВАННЫЙ ПРИВАТНЫЙ КЛЮЧ
# в stdout, а в рабочем пространстве stdout — это история чата. Пароль тоже
# передавайте через -passin file:/env:, а не как -passin pass:… в командной строке.
openssl pkcs12 -info -nokeys -clcerts -in key.p12 -passin file:pw.txt \
| openssl x509 -noout -text | grep 'Public Key Algorithm'
4. Чем подписывать
Проверенный путь: Kalkan
Kalkan — собственный Java-криптопровайдер НУЦ РК, тот самый, на котором построен и сам NCALayer. Он умеет и RSA, и ГОСТ, не требует нативных библиотек, и Plank уже использует его в проде для ЭСФ. А поскольку универсальный ключ — ГОСТ (§3), для любого ключа после апреля 2024 это не просто выбор по умолчанию, а единственный вариант — и заодно единственный путь в этом репозитории, за которым стоят реальные доказательства.
Вся операция — четыре строки:
Security.addProvider(new KalkanProvider());
KncaXS.loadXMLSecurity();
String signedXml = XMLUtil.createXmlSignature(
new SigningEntity(privateKey, Arrays.asList(certificate)), // из .p12
xmlFromServer,
Security.getProvider(KalkanProvider.PROVIDER_NAME));
Это тот же самый вызов, которым клиент ЭСФ подписывает свой authTicket. Копируйте EsfClient.java из Подключения ЭСФ, а не выводите заново: загрузка учётных данных, регистрация провайдера и совместимость с Java 8 там уже сделаны правильно.
Практические замечания:
- Jar-файлы лежат внутри SDK ЭСФ.
esf-sdk-*.rarсhttps://kgd.gov.kz/sites/default/files/ftpdata/ESF/— публично, ~180 МБ. Если в рабочем пространстве уже естьscripts/esf/sdk/, используйте его и не качайте заново. В песочнице нетunrar; распаковывайте черезbsdtar -xf esf-sdk.rar -C <каталог>(libarchive читает RAR), а еслиbsdtarтоже нет —pip install --quiet patoolиpatoolib.extract_archive(...); оба варианта описаны в Подключении ЭСФ. - В образе песочницы нет JRE. Ставьте tarball Temurin без sudo в
~/.local/jdks/и переиспользуйте, если он уже там. - Для ЭСФ нужна именно JDK 8, потому что её SOAP-клиенту нужен JAX-WS, удалённый в Java 11+. У самой подписи такой зависимости нет, и теперь это измерено: 10.09.2026 Kalkan загрузил ГОСТ-ключ и выдал корректный enveloped XMLDSig (
SignatureMethod…xmldsig-more#gost34310-gost34311) на OpenJDK 17, при classpath изlib/того же SDK. За JDK 8 идите только тогда, когда нужен ещё и SOAP-клиент. - Только если ключ клиента RSA — выпущен до апреля 2024 и ещё не истёк — подписант на чистом Python (
signxml/xmlsec) вообще применим: без JDK и без 180 МБ. ГОСТ он не умеет, так что на универсальном ключе это не лёгкий путь, а тупик. И тоже не проверен ни на одном из этих порталов. Сверьтесь с §3, прежде чем потратить на это вечер. - NCANode — открытый HTTP-сервер подписи ровно для этой задачи, есть образ Docker. Очевидный кандидат, если хочется подпись как сервис, а не в процессе. Здесь тоже не проверен — если будете оценивать, зафиксируйте результат.
Когда ключ должен остаться на машине пользователя
Часть клиентов не отдаст .p12, и это разумная позиция. Скажите об этом прямо, а не уговаривайте: без доступного ключа автоматизация ограничена тем, что портал отдаёт без входа (для Tizilim это по-настоящему полезный публичный API на чтение). Промежуточный вариант — ассистент готовит всё, а пользователь подписывает у себя — предлагается в интеграции с ЭСФ и его стоит предложить; только честно скажите, что он стоит одного взаимодействия на каждое подписанное действие, а именно эту цену автоматизация и должна была убрать.
5. Докажите подпись до того, как строить на ней
Сделайте это первым делом, отдельно, до любой работы над функциональностью. Это один круг запросов, и он закрывает единственный вопрос, который имеет значение.
1. POST на эндпоинт портала «дай XML» → сохранить XML дословно
2. подписать → сохранить подписанный XML
3. POST на эндпоинт портала «проверь»
Портал, ответивший ролями пользователя, сессией или токеном, подпись принял — и вся интеграция разблокирована. Отказ — сигнал изменить ровно одну вещь и повторить.
Сохраните оба файла. Когда позже что-то сломается, вопрос «изменилась подпись или изменился портал» решается за секунды при наличии заведомо рабочей пары и не решается никак без неё.
6. Виды отказов и что каждый означает
- Порталы отвечают одним сообщением на любую ошибку подписи. Tizilim отвечает
422«Ошибка при обработке сертификата» и на кривую подпись, и на неверный тип ключа, и на недоверенную цепочку. Поэтому меняйте по одной переменной за раз: файл ключа, потом OID, потом передаётся ли цепочка, потом канонизация. - Не тот ключ из пары — самая вероятная причина отказа с первой попытки. Проверьте OID (§3) прежде, чем подозревать что-то тонкое.
- Пробелы — часть документа. Подписывайте байты сервера ровно как получили: не форматируйте, не пересериализуйте, не перекодируйте и не убирайте завершающий перевод строки. Одного прохода XML через парсер достаточно, чтобы сломать дайджест.
- Просроченный или отозванный сертификат. Эти ключи действуют год и заканчиваются тихо.
openssl pkcs12 -in key.p12 -nokeys -clcerts -passin file:pw.txt | openssl x509 -noout -enddateскажет вам об этом раньше, чем портал — и здесь-nokeysтоже обязателен. - «Принято» не значит «готово». ЭСФ возвращает
acceptedдля счетов-фактур, которые потом переводит вFAILED; считайте, что так может любой портал, и проверяйте статус объекта после отправки, а не доверяйте ответу на отправку.
7. Обращение с ключом
Это не рекомендации, и они действуют с момента, когда .p12 попал в рабочее пространство:
- Храните ключ и его пароль в защищённых учётных данных рабочего пространства — никогда в заметке, скрипте, коммите, строке лога или истории чата.
- Знайте, кто ещё его видит. Файл под
scripts/виден каждому участнику общего рабочего пространства;/home/coderприватен для одного пользователя..gitignoreна это никак не влияет, а ключ ЭЦП — это юридическая подпись, а не отзываемый API-токен. В общем рабочем пространстве кладите его осознанно и скажите владельцу, что станет видно. См. Скрипты и интеграции. - Добавьте
*.p12и файл учётных данных в.gitignoreрабочего пространства до того, как ключ появится, а не после. - Никогда не выводите содержимое ключа, пароль или приватную половину сертификата в транскрипт.
- Никогда не подписывайте без явного подтверждения то, что портал делает публичным или необратимым: опубликованную закупку, поданную заявку, сданный отчёт. Соберите, покажите, спросите, потом подписывайте.
- Скажите пользователю, как вас отключить: удаление ключа из рабочего пространства мгновенно закрывает доступ. Он должен знать это, не спрашивая.
8. Что проверено
- ✅ Работает в проде: вызов enveloped XMLDSig через Kalkan — против ЭСФ, для ИП: подписанная сессия и реально выписанный счёт-фактура.
- ✅ Прочитано с живого портала: запрос Tizilim к NCALayer из §2, его OID, список хранилищ и откат по трём поколениям API; а также весь REST-контур egov.kz, его константный логин-документ и оба OID (тема).
- ✅ Доказано на egov.kz (10.09.2026): enveloped XMLDSig от Kalkan, сделанный на OpenJDK 17 настоящим универсальным ГОСТ-ключом и без NCALayer, принимается эндпоинтом
/identity/v3/auth/eds—200и сессия. Это второй портал, принявший этот подписант, и первый ГОСТ-овый. - ✅ Также измерено:
.p12НУЦ РК требуют у OpenSSL флага-legacy; универсальное свидетельство не несёт OID clientAuth и всё равно принимается (§3); а собственные тестовые ключи ЭСФ SDK (пинQwerty12— публичная тестовая пара, лежащая в самом SDK) прогоняют подписант целиком вообще без ключа клиента. Проверяйте подписант на них, прежде чем просить ключ у клиента. - ❌ Не проверено: что подпись Kalkan принимает Tizilim; что эндпоинт записи так же снисходителен к EKU, как вход (§3); подписант на чистом Python для RSA; NCANode. Каждое — короткий эксперимент по §5: проведите его и обновите эту страницу.