Не так давно вышла статья, посвящённая работе с библиотекой PhpFluentConsole, которая позволяет удобно взаимодействовать с консольными утилитами в PHP. Теперь пришло время применить её на практике для решения реальных задач. Одной из таких задач является упрощение работы с электронными подписями и криптографическими операциями, которые часто выполняются через утилиты КриптоПро.
SDK КриптоПро, хотя и предоставляет мощные возможности, имеет довольно сложную документацию, что может стать препятствием для начинающих разработчиков. Кроме того, использование SDK требует определённой подготовки серверного окружения, включая установку и активацию расширения в PHP. Важно отметить, что сам SDK КриптоПро также является своего рода обёрткой над низкоуровневым API криптопровайдера.
Существует множество подходов к реализации серверной криптографии, каждый со своими преимуществами и недостатками. Мы сосредоточимся на одном из наиболее простых и доступных — использовании консольных утилит КриптоПро и парсинге их вывода. Для этого была разработана библиотека CryptoProBuilder, которая построена поверх PhpFluentConsole и предлагает текучий интерфейс (Fluent Interface) для удобного формирования и выполнения команд
Возможности библиотеки
Получение списка контейнеров и их проверка
Изменение, копирование и удаление контейнеров
Хэширование файлов и проверка хэшей
Подписание и проверка подписи документов
Шифрование и расшифровывание файлов
Установка, удаление и чтение сертификатов
Получение информации о сертификате по отпечатку
Гибкая настройка кастомных аргументов, кодировок, шаблонов парсинга и тд.
Установка
Для начала работы с CryptoProBuilder, вам необходимо установить её через Composer:
composer require mikhailovlab/crypto-pro-builder
Важное замечание: Убедитесь, что исполняемые файлы КриптоПро (например, csptest, cryptcp, certmgr и cpverify — обычно идут в комплекте с дистрибутивом КриптоПро) доступны в переменной окружения PATH вашего сервера или укажите полный путь к ним при инициализации класса CryptoPro.
Примеры использования
Для работы с библиотекой нам необязательно погружаться в документацию, мы можем использовать шаблонные решения.
По стандартной схеме, тестировать будем с использованием фреймворка Laravel 11, на операционной системе windows 10. Вы можете использовать любую другую конфигурацию (фреймворк, ОС и т.д.), главное — обеспечить корректную установку и доступность утилит КриптоПро.
Получить список контейнеров:
try{
dd(new CryptoPro()
->getContainers()
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:6 [▼ // app\Http\Controllers\TestController.php:23
0 => "\\.\FAT12_H\d58fe6c13-d917-2a53-8e9c-8c4b8158220"
1 => "\\.\FAT12_H\c3965c1c-56de-4ed2-a6bd-fcfe1f47f77f"
2 => "\\.\FAT12_H\469233b1-9ccd-4a74-9264-a9b4837ad3b5"
3 => "\\.\FAT12_H\e00b1f31-ecb7-4827-9c5b-f1460c682261"
4 => "\\.\FAT12_H\015b6fa9-e71d-4240-be9f-0462b40e0042"
5 => "\\.\FAT12_H\8ac2691d-c4d5-457e-8d47-3a52e5a2691a"
]
Протестировать контейнер:
try{
dd(new CryptoPro()
->checkContainer($container)
//->password($password) пароль, если требуется
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:23
"status" => "успешно"
]
Изменить пароль контейнера:
try{
dd(new CryptoPro()
->changeContainerPass($container, currentPass: '', newPass: '1234')
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:25
"status" => "успешно"
]
Скопировать контейнер:
try{
dd(new CryptoPro()
->copyContainer()
->contsrc($container1) //входной контейнер
->contdest($container2) //выходной контейнер
->pinsrc('1234') //пароль входного контейнера, если требуется
//->pindest() пароль выходного контейнера, если требуется
->silent() //не выводить окно с вводом пароля
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:25
"status" => "успешно"
]
Удалить контейнер:
try{
dd(new CryptoPro()
->deleteContainer($container)
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:25
"status" => "успешно"
]
Получить хэш файла:
try{
dd(new CryptoPro('cpverify') //либо полный путь
->hashFile('H:\csp\123.txt')
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:25
"hash" => "275BF35756C49E7A35810893777AC4F5E0E56D3D24A259502C56F2CFA5048014A7496908CDB177C3B939E5D38CC51299E5D364226C0B8BEB80030CE86F6A1762"
]
Проверить хэш:
try{
dd(new CryptoPro('cpverify')
->verifyHash(['H:\csp\123.txt', $hash]) //второй элемент хэш строка или путь к файлу
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:25
"status" => "File 'H:\csp\123.txt' has been verified"
]
Подписать документ:
try{
dd(new CryptoPro()
->signDocument()
->in('H:\csp\123.txt')
->out('H:\csp\123.txt.sig')
->password('1234') //пароль, если требуется
->my('00dad6c045c2ec4a01f20441daf2d8dd999aaf07') // Сертификат
->addsigtime() //добавить время подписи, если требуется
->base64() // base64, если требуется
->detached() //отсоединенная подпись, если требуется
->add() //добавить сертификат, если требуется
->silent() //не выводить окно с вводом пароля
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:25
"status" => "успешно"
]
Проверить подпись:
$array = [
'H:\csp\123.txt',
'H:\csp\123.txt.sig' //для присоединенной подписи указываем только 1 файл
];
try{
dd(new CryptoPro("cryptcp")
->encoding('866') // windows консоль выдаст кириллицу в 866
->verifySignature($array)
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:31
0 => "RU, Москва, Сидоров Иван Иванович, sidorov@mail.ru"
]
Зашифровать файл:
try{
dd(new CryptoPro("cryptcp")
->encryptDocument()
->thumbprint('00dad6c045c2ec4a01f20441daf2d8dd999aaf07')
->addKey('H:\csp\123.txt')
->addKey('H:\csp\123.txt.enc')
->silent()
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:26
"status" => "успешно"
]
Расшифровать файл:
try{
dd(new CryptoPro("cryptcp")
->decryptDocument()
->thumbprint('00dad6c045c2ec4a01f20441daf2d8dd999aaf07')
->pin(1234)
->addKey('H:\csp\123.txt.enc')
->addKey('H:\csp\123decr.txt')
->silent()
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:26
"status" => "успешно"
]
Установить сертификат:
try{
dd(new CryptoPro("certmgr.exe") //не путать со встроенной в windows утилитой
->certificatInstall()
->file('H:\csp\Cert.cer') //установить из файла
//->cont($container) //установить из контейнера
->store('uMy')
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:26
"status" => "успешно"
]
Получить список сертификатов:
try{
dd(new CryptoPro("certmgr.exe") //не путать со встроенной в windows утилитой
->getCertificates()
->encoding('866') // Windows консоль выдаст кириллицу в 866
->store('uMy')
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:12 [▼ // app\Http\Controllers\TestController.php:26
0 => array:5 [▼
"subject" => "Сидоров Иван Иванович"
"serialNumber" => "0x7C001F6C9ED2E51F47F4CB4020000D001F6C9E"
"sha1" => "00dad6c045c2ec4a01f20441daf2d8dd999aaf07"
"issued" => "30/05/2025 01:19:45 UTC"
"expires" => "04/07/2025 10:36:28 UTC"
]
1 => array:5 [▶]
2 => array:5 [▶]
3 => array:5 [▶]
4 => array:5 [▶]
5 => array:5 [▶]
6 => array:5 [▶]
7 => array:5 [▶]
8 => array:5 [▶]
9 => array:5 [▶]
10 => array:5 [▶]
11 => array:5 [▶]
]
Получить сертификат по отпечатку:
try{
dd(new CryptoPro("certmgr.exe") //не путать со встроенной в windows утилитой
->getCertificateByTp()
->encoding('866')
->store('uMy')
->thumbprint('00dad6c045c2ec4a01f20441daf2d8dd999aaf07')
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:5 [▼ // app\Http\Controllers\TestController.php:26
"subject" => "Сидоров Иван Иванович"
"serialNumber" => "0x7C001F6C9ED2E51F47F4CB4020000D001F6C9E"
"sha1" => "00dad6c045c2ec4a01f20441daf2d8dd999aaf07"
"issued" => "30/05/2025 01:19:45 UTC"
"expires" => "04/07/2025 10:36:28 UTC"
]
Удалить сертификат:
try{
dd(new CryptoPro("certmgr.exe") //не путать со встроенной в windows утилитой
->deleteCertificate()
->store('uMy')
->thumbprint('00dad6c045c2ec4a01f20441daf2d8dd999aaf07')
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Результат:
array:1 [▼ // app\Http\Controllers\TestController.php:26
"status" => "успешно"
]
Дополнительные методы, которые пригодятся для построения кастомных запросов:
try{
dd(new CryptoPro()
->registerMethods(['install', 'delete']) //зарегистрировать дополнительные методы
->addKey("-nochain") //пробросить кастомный аргумент или ключ
->encoding('866') //добавить кодировку, например из 866 в UTF-8 (для кириллицы в windows)
->decoding() //вернуть исходную кодировку, если отдаем вывод в консоль
->addPatterns(['patternname' => 'regexp']) //добавить кастомные паттерны для парсинга
->usePattern("patternname") //использовать паттерн для парсинга
->run()
);
}catch (Exception $e){
dd($e->getMessage());
}
Мы так же можем наследоваться от базового класса и создать свои собственные методы, для повторного использования в разных частях программы.
Заключение
Библиотека CryptoProBuilder значительно упрощает взаимодействие с консольными утилитами КриптоПро, предоставляя удобный и предсказуемый программный интерфейс. Она абстрагирует разработчика от необходимости вручную формировать сложные командные строки и парсить сырой консольный вывод, позволяя сосредоточиться на логике приложения. Это делает работу с криптографией на PHP более доступной и менее подверженной ошибкам, особенно для тех, кто не желает углубляться в детали низкоуровневых SDK или сталкивается с проблемами их настройки в серверном окружении.
Ознакомиться с документацией и исходным кодом можно на:
GitHub
Packagist
Если есть вопросы — пишите в комментариях, постараюсь помочь.
Комментарии (9)
C0ffeeMaker
03.07.2025 05:00SDK через COM компоненты работает, они давно устарели.
DmitriiMikhailov Автор
03.07.2025 05:00Тоже читал об этом. Вообще от задачи зависит: если нужно добавить базовый функционал для работы с электронными подписями - я бы использовал cli, что бы не тратить время, а если задача разработать систему ЭДО или сложную интеграцию - то SDK или отдельный сервис подписания на .NET, т.к. там лучше реализована работа с документами.
the2rkmen
возможно я что-то путаю(или это было более 5 лет назад), но для крипто про хозяйства нужно windows.
Это работает только под windows или под линуксами тоже норм?
DmitriiMikhailov Автор
Работать будет везде, где установлен КриптоПро с утилитами. Для linux тоже есть дистрибутив, хотя установка менее интуитивная, чем на windows. Посмотрите на официальном сайте КриптоПро в разделе с дистрибутивами.
https://support.cryptopro.ru/index.php?/Knowledgebase/Article/View/390
Akuma
Ну оно как бы есть под остальные платформы, но сам по себе криптопро это геморрой тот ещё. Даже лицензию купить - квест.
DmitriiMikhailov Автор
Я работал в фармацевтической компании - 5000+ рабочих мест. Как-то раз техническая поддержка решила саботировать работу аптек и отказалась сбрасывать пробные лицензии, которые не были закуплены на тот момент. А документооборот был 2 тысячи входящих документов в час, которые нужно вовремя подписывать. Я написал скрипт на php, который подключался ко всем аптекам через систему удаленного доступа и сбрасывал ключи в реестре, что бы дожить до закупки лицензий. На сброс всех рабочих мест уходило примерно 40 минут. Многие компании вообще не покупают лицензии, а просто сбрасывают ключи.
КриптоПро это еще терпимо, а вот VipNet - да.
aikus
У крипто про специфичная ЦА, им не так просто спиратить отечественный продукт, риски запредельные.
DmitriiMikhailov Автор
ЦА КриптоПро - любая организация и ИП, которая платит налоги и подает отчеты через онлайн кабинет. Не уверен, считается ли сброс ключей и использование пробной 3х месячной лицензии пиратством, но то что многие компании так работают - факт. Наша компания потратила почти 3млн рублей на пользовательские лицензии КриптоПро, до этого почти год сбрасывали ключи. Лицензии ставили всем подряд - юристам, менеджерам, стажерам, завхозу - хотя многие задачи можно было закрыть серверным подписанием и одной клиентской лицензией.
Кстати, одну лицензию часто накидывают на множество рабочих мест. В больнице где я работал, одна лицензия подрезанная с интернета была установлена на всех рабочих местах.
aikus
Да, в эту сторону я как то не подумал.