CryptoProBuilder — fluent-интерфейс для КриптоПро на PHP: подпись, шифрование и работа с контейнерами

от автора

Не так давно вышла статья, посвящённая работе с библиотекой 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

Если есть вопросы — пишите в комментариях, постараюсь помочь.


ссылка на оригинал статьи https://habr.com/ru/articles/924478/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *