本文目录导读:

- 核心校验逻辑
- 方案一:使用 OpenSSL 命令行(最通用,适合验证PDF/OFD)
- 方案二:使用 PHP 加密扩展(如 phpseclib / OpenSSL PHP扩展)
- 方案三:调用第三方签名API(推荐商业级应用)
- 方案四:国密SM2/SM3签名校验(使用PHP扩展)
- 总结:如何选择?
- 关键提示(避坑指南)
在PHP项目中校验电子印章的有效性,通常涉及以下几个核心层面,由于PHP本身不直接处理复杂的加密图形(如国密SM2/SM3签名),通常需要借助扩展库或调用外部API/命令行工具来完成。
以下是几种主流的校验方案及其实现逻辑:
核心校验逻辑
一个“有效”的数字印章通常需要满足:
- 数字签名有效:印章数据(图片+签章时间+原文Hash)未被篡改。
- 证书有效:签名证书在有效期内,且未被吊销。
- 可信CA:证书颁发机构(CA)是合法、受信任的。
- 唯一性:印章数据未被重复使用或伪造。
使用 OpenSSL 命令行(最通用,适合验证PDF/OFD)
如果你的印章是嵌入在 PDF 或 OFD 中的数字签名,最可靠的办法是调用系统级的 openssl 或专用验证工具(如 pdfsig)。
步骤:
- 系统需安装 OpenSSL 或 Poppler(提供
pdfsig命令)。 - PHP 通过
exec()/shell_exec()执行命令。 - 解析返回结果判断有效性。
代码示例(校验PDF数字签名):
<?php
function verifyPdfSignature($pdfFilePath) {
// 使用 pdfsig 命令(来自 poppler-utils)
$command = "pdfsig \"" . $pdfFilePath . "\" 2>&1";
exec($command, $output, $returnCode);
$resultStr = implode("\n", $output);
// 解析结果
$isValid = false;
$details = [];
if (strpos($resultStr, 'Signature is VALID') !== false) {
$isValid = true;
}
// 提取签名者信息、时间等
if (preg_match('/Signer Name: (.+)/', $resultStr, $matches)) {
$details['signer'] = trim($matches[1]);
}
// 提取证书有效期
if (preg_match('/Signing Time: (.+)/', $resultStr, $matches)) {
$details['time'] = trim($matches[1]);
}
return [
'valid' => $isValid,
'details' => $details,
'raw' => $resultStr
];
}
// 使用
$result = verifyPdfSignature('/path/to/signed_document.pdf');
if ($result['valid']) {
echo "印章有效,签名者:" . $result['details']['signer'];
} else {
echo "印章无效或已被篡改";
}
?>
优点: 无需商业库,依赖系统命令,支持标准 PKCS#7 签名。
缺点: 需要服务器安装对应工具;解析文本输出不够优雅;不支持国密(除非OpenSSL编译了国密引擎)。
使用 PHP 加密扩展(如 phpseclib / OpenSSL PHP扩展)
适用于校验原始的 PKCS#7 签名数据(即从印章中提取出的签名值)。
流程:
- 从印章文件(如
.esl或自定义xml)中解析出:signedData(原始数据或原文Hash)signature(签名值)certificate(签名证书)
- 使用 PHP OpenSSL 或 phpseclib 进行验证。
代码示例(使用PHP内置OpenSSL):
<?php
/**
* 校验PKCS#7格式的签名数据
* @param string $data 原始数据(原文或原文Hash)
* @param string $signature 签名值(一般base64解码后)
* @param string $certificate 签名证书(PEM格式)
* @return bool
*/
function verifyPKCS7Signature($data, $signature, $certificate) {
// 将证书写入临时文件
$certFile = tempnam(sys_get_temp_dir(), 'cert_');
file_put_contents($certFile, $certificate);
// 将要签名的数据写入临时文件
$dataFile = tempnam(sys_get_temp_dir(), 'data_');
file_put_contents($dataFile, $data);
// 将签名写入临时文件
$sigFile = tempnam(sys_get_temp_dir(), 'sig_');
file_put_contents($sigFile, $signature);
// 使用openssl_pkcs7_verify验证
// 需要签名数据是PKCS#7格式
$ok = openssl_pkcs7_verify(
$sigFile, // 包含签名的文件
PKCS7_NOVERIFY, // 先不验证证书链,我们自己验证
$certFile, // 提取的证书
array(), // 中间CA
$dataFile, // 原始数据
$certFile // 输出证书
);
// 清理临时文件
unlink($certFile);
unlink($dataFile);
unlink($sigFile);
if ($ok) {
// 进一步校验证书有效期
$certInfo = openssl_x509_parse(file_get_contents($certFile));
$currentTime = time();
$validFrom = strtotime($certInfo['validFrom_time_t']);
$validTo = strtotime($certInfo['validTo_time_t']);
return ($currentTime >= $validFrom && $currentTime <= $validTo);
}
return false;
}
// 设备:通常这些数据从印章结构中提取
$data = "合同原文内容...";
$signature = base64_decode("base64编码的签名值...");
$certificate = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----";
if (verifyPKCS7Signature($data, $signature, $certificate)) {
echo "印章签名校验通过,证书有效。";
} else {
echo "印章无效!";
}
?>
优点: 纯PHP实现,不依赖外部命令。
缺点: 不支持国密;需要自己解析印章格式;openssl_pkcs7_verify 对数据格式要求严格。
调用第三方签名API(推荐商业级应用)
对于要求支持国密(SM2/SM3)、数据安全或大规模并发的场景,建议对接专业的电子签章平台API(如法大大、e签宝、契约锁、腾讯电子签等)。
流程:
- 将待验证的签署文件(如PDF/OFD)上传至平台文件托管。
- 调用平台的验证接口。
- 平台返回签章的真伪、签署人、时间戳等信息。
代码示例(假设使用e签宝API):
<?php
// 假设使用某个云签章平台的验证接口
function cloudSealVerify($filePath) {
$apiUrl = "https://api.signcloud.com/v2/seal/verify";
$appId = "your_app_id";
$secretKey = "your_secret_key";
// 构造请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// 上传文件
$postData = [
'file' => new CURLFile($filePath),
'appId' => $appId,
'timestamp' => time()
];
// 计算签名(具体按平台要求)
$postData['sign'] = md5($appId . $postData['timestamp'] . $secretKey);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
// 解析平台返回的结果
if ($result['code'] == 0 && $result['data']['verifyResult']) {
return [
'valid' => true,
'signers' => $result['data']['signers'],
'signedTime' => $result['data']['signedTime']
];
}
return ['valid' => false, 'message' => $result['msg']];
}
?>
优点: 支持国密、证书链自动验证、通常包含时间戳、免维护CA信任库。
缺点: 依赖网络、可能有费用、数据需要上传至第三方。
国密SM2/SM3签名校验(使用PHP扩展)
如果印章是使用国密算法生成的(国内电子签章强制标准 GM/T 0031-2014),需要使用支持国密的库。
可选库:
- php-gmssl:PHP的国密扩展(需要编译安装)。
- TASSL:国密版OpenSSL,通过命令行调用。
代码示例(假设装了php-gmssl扩展):
<?php
// 使用 php-gmssl 库 (伪代码)
// 注意:php-gmssl 的API可能随版本变化
function gmVerifySignature($publicKeyPem, $data, $signatureBase64) {
// 1. 生成SM2公钥对象
$sm2pubKey = \GMSSL\SM2\PublicKey::fromPem($publicKeyPem);
// 2. 计算SM3摘要
$sm3Hash = \GMSSL\SM3::hash($data);
// 3. 验证SM2签名
$signature = base64_decode($signatureBase64);
return \GMSSL\SM2::verify($sm2pubKey, $sm3Hash, $signature);
}
// 使用
$pubKey = file_get_contents('ca_public_key.pem');
$originalData = '合同原文';
$signature = 'AGSJDCJKD...'; // 从印章中提取的Base64签名
if (gmVerifySignature($pubKey, $originalData, $signature)) {
echo "国密印章有效";
}
?>
如何选择?
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 你使用第三方电子签平台 (如法大大、e签宝) | 调用平台API | 最简单、最合规、支持国密和区块链存证 |
| 自建签章系统,文件是PDF | OpenSSL命令行 | 稳定,原生支持PDF签名验证 |
| 自建系统,校验自定义签名数据 | PHP OpenSSL扩展 | 纯代码实现,无需外部依赖(仅限RSA) |
| 必须支持国密SM2/SM3 | 国密扩展 或 方案三(API) | 国内合规要求,纯PHP目前无成熟方案直接支持SM2 |
| 需要验证证书吊销状态 | 方案三 API 或 自行实现 OCSP/CRL 检查 | 需要网络请求和CA支持 |
关键提示(避坑指南)
- 时间戳(TSP):大多数有效印章会包含可信时间戳,仅校验签名是不够的,还需要校验时间戳是否由权威机构(国家授时中心等)签发,可以调用
openssl ts -verify或平台API来验。 - 证书吊销列表(CRL):即使证书在有效期内,也可能已被CA吊销,需要定期下载CRL或使用
openssl crl -verify检查。 - 原始数据完整性:校验时必须使用签署时的原始数据(原文的Hash值),光验图片(印章图像)是无法确认原合同内容是否被篡改的。
- 开发环境 vs 生产环境:生产环境强烈建议使用商业级SDK或API,因为你可以把复杂的国密+CRL+时间戳+证书链验证交给专业平台,PHP仅仅做调用和业务逻辑。