埃及电子发票合规:PHP实现USB令牌CADES-BES哈希签名需求
使用PHP通过USB令牌为埃及电子发票哈希生成电子签名(无需Chilkat)
要实现USB令牌签名哈希,核心是利用PKCS#11标准(大多数USB令牌支持)结合PHP的OpenSSL扩展或系统命令工具完成。以下是两种可行方案:
方案一:纯PHP + OpenSSL PKCS#11引擎
此方案直接通过PHP的OpenSSL扩展调用PKCS#11引擎访问USB令牌私钥。
前置准备
- 确认PHP已启用OpenSSL扩展:执行
php -m | grep openssl验证 - 获取令牌厂商提供的PKCS#11驱动文件(如
.so或.dll) - 知晓令牌PIN码、私钥的别名/ID
代码实现
<?php // 配置参数,替换为你的实际值 $pkcs11Driver = "/usr/lib/libeTPkcs11.so"; // PKCS#11驱动路径 $tokenPin = "123456"; // 令牌PIN,禁止硬编码,建议从安全环境变量读取 $privateKeyAlias = "invoice_signature_key"; // 令牌内私钥的别名/ID $targetHash = "a1b2c3d4..."; // 需要签名的哈希值(十六进制格式) // 加载并初始化PKCS#11引擎 $pkcs11Engine = openssl_get_engine("pkcs11"); if (!$pkcs11Engine || !openssl_engine_init($pkcs11Engine)) { die("PKCS#11引擎加载/初始化失败: " . openssl_error_string()); } // 设置驱动路径 openssl_engine_ctrl($pkcs11Engine, ENGINE_CTRL_SET_CONFIG, 0, $pkcs11Driver); // 构建PKCS#11私钥路径 $privateKeyUri = sprintf("pkcs11:pin=%s;object=%s", $tokenPin, $privateKeyAlias); $privateKey = openssl_pkey_get_private($privateKeyUri); if (!$privateKey) { die("获取私钥失败: " . openssl_error_string()); } // 对哈希签名(算法需匹配埃及电子发票要求,示例用SHA-256) $signature = ''; $signSuccess = openssl_sign(hex2bin($targetHash), $signature, $privateKey, OPENSSL_ALGO_SHA256); if (!$signSuccess) { die("签名失败: " . openssl_error_string()); } // 转换为Base64格式以便后续提交 $base64Signature = base64_encode($signature); echo "生成的电子签名(Base64): " . $base64Signature . "\n"; // 释放资源 openssl_pkey_free($privateKey); openssl_engine_finish($pkcs11Engine); ?>
关键说明
- 若
openssl_get_engine("pkcs11")失败,需在OpenSSL配置文件中启用PKCS#11引擎,或手动调用openssl_engine_load加载 - 部分令牌需指定token名称,私钥URI格式可调整为:
pkcs11:token=MyTokenName;pin=xxx;object=xxx - 哈希值需为二进制格式,若传入的是十六进制字符串,必须用
hex2bin转换
方案二:PHP调用OpenSC命令行工具
如果PHP的PKCS#11引擎支持不佳,可通过调用pkcs11-tool(OpenSC工具包组件)完成签名,实现更稳定。
前置准备
- 安装OpenSC工具包:
apt install opensc(Debian/Ubuntu)或yum install opensc(CentOS/RHEL) - 确保Web服务器用户(如www-data)有权限访问USB设备和执行
pkcs11-tool
代码实现
<?php // 配置参数 $pkcs11Driver = "/usr/lib/libeTPkcs11.so"; $tokenPin = "123456"; $privateKeyId = "invoice_sign_key"; $targetHash = "a1b2c3d4..."; // 十六进制哈希值 // 创建临时文件存储哈希(避免命令行参数长度限制) $hashTempFile = tempnam(sys_get_temp_dir(), "invoice_hash"); $signatureTempFile = tempnam(sys_get_temp_dir(), "invoice_sig"); // 写入二进制哈希到临时文件 file_put_contents($hashTempFile, hex2bin($targetHash)); // 构建签名命令 $signCommand = sprintf( "pkcs11-tool --module %s --pin %s --sign --id '%s' --input %s --output %s --hash sha256", escapeshellarg($pkcs11Driver), escapeshellarg($tokenPin), escapeshellarg($privateKeyId), escapeshellarg($hashTempFile), escapeshellarg($signatureTempFile) ); // 执行命令并处理结果 $commandOutput = []; $returnCode = 0; exec($signCommand, $commandOutput, $returnCode); if ($returnCode !== 0) { die("签名命令执行失败: " . implode("\n", $commandOutput)); } // 读取并转换签名格式 $signature = file_get_contents($signatureTempFile); $base64Signature = base64_encode($signature); echo "生成的电子签名(Base64): " . $base64Signature . "\n"; // 清理临时文件 unlink($hashTempFile); unlink($signatureTempFile); ?>
关键说明
pkcs11-tool的参数可根据令牌型号调整,比如用--label代替--id指定私钥标签- 生产环境中需严格控制临时文件的权限,避免哈希或签名泄露
内容的提问来源于stack exchange,提问作者Amr Ahmed
相关产品推荐
相关产品推荐

