phpcfdi / credentials
用于使用SAT的eFirma(fiel)和CSD(sellos)的库
Requires
- php: >=7.3
- ext-mbstring: *
- ext-openssl: *
- eclipxe/enum: ^0.2.0
Requires (Dev)
- ext-json: *
- phpunit/phpunit: ^9.5
README
用于使用SAT的eFirma(fiel)和CSD(sellos)的库
🇺🇸 该项目的文档使用西班牙语,因为这是目标受众的自然语言。
🇲🇽 该项目的文档使用西班牙语,因为这是主要用户语言。
这个库是为了能够处理SAT生成的CSD和FIEL文件而创建的。这样,签名、验证签名以及从证书文件和公钥中获取特定数据的过程得到了简化。
-
CSD(数字印章证书)用于签署数字发票。
-
FIEL(或eFirma)用于电子签名文档(通常使用XML-SEC),并被墨西哥政府认可为自然人或法人法律签名的有效方式。
使用这个库,不需要将SAT生成的文件转换为其他格式,可以直接使用。
安装
使用 composer
composer require phpcfdi/credentials
基本使用示例
<?php declare(strict_types=1); $cerFile = 'fiel/certificado.cer'; $pemKeyFile = 'fiel/private-key.key'; $passPhrase = '12345678a'; // contraseña para abrir la llave privada $fiel = PhpCfdi\Credentials\Credential::openFiles($cerFile, $pemKeyFile, $passPhrase); $sourceString = 'texto a firmar'; // alias de privateKey/sign/verify $signature = $fiel->sign($sourceString); echo base64_encode($signature), PHP_EOL; // alias de certificado/publicKey/verify $verify = $fiel->verify($sourceString, $signature); var_dump($verify); // bool(true) // objeto certificado $certificado = $fiel->certificate(); echo $certificado->rfc(), PHP_EOL; // el RFC del certificado echo $certificado->legalName(), PHP_EOL; // el nombre del propietario del certificado echo $certificado->branchName(), PHP_EOL; // el nombre de la sucursal (en CSD, en FIEL está vacía) echo $certificado->serialNumber()->bytes(), PHP_EOL; // número de serie del certificado
关于证书和私钥文件
证书文件以 X.509 DER
格式提供,私钥文件以 PKCS#8 DER
格式提供。这两种格式不能直接在PHP中(使用 ext-openssl
)解析,但可以在兼容格式 PEM
中解析。
这个库具有内部进行这种转换的能力(无需 openssl
),因为它只需要将数据编码为 base64
,并使用特定于证书和私钥的行尾。
因此,对于SAT提供的 AAA010101AAA.cer
证书或 AAA010101AAA.key
私钥,不需要使用 openssl
转换,库可以正确地检测它们。
创建证书对象 Certificate
如果对象包含无效数据,则不会创建 Certificate
对象。
SAT 以 X.509 DER
格式提供证书,因此可以在内部将其转换为 X.509 PEM
格式。也经常使用 X.509 DER base64
格式,例如在 Comprobante@Certificado
属性或XML签名中,因此创建 Certificate
对象支持的格式是 X.509 DER
、X.509 DER base64
和 X.509 PEM
。
- 使用本地文件打开:
$certificate = Certificate::openFile($filename);
- 使用字符串打开:
$certificate = new Certificate($content);
- 如果
$content
是带有相应头部的X.509 PEM
格式证书,则使用该头部。 - 如果
$content
完全为base64
,则将其解释为X.509 DER base64
并将其格式化为X.509 PEM
。 - 在其他情况下,将其解释为
X.509 DER
格式,并将其格式化为X.509 PEM
。
- 如果
创建私钥对象 PrivateKey
如果对象包含无效数据,则不会创建 PrivateKey
对象。
在SAT中,密钥以PKCS#8 DER
格式提供,因此可以将其内部转换为PKCS#8 PEM
(使用相同的密码)并在PHP中使用。
一旦打开了密钥,也可以更改或删除密码,从而创建一个新的PrivateKey
对象。
- 使用本地文件打开:
$key = PrivateKey::openFile($filename, $passPhrase);
- 使用字符串打开:
$key = new PrivateKey($content, $passPhrase);
- 如果
$content
是格式为PEM
(PKCS#8
或PKCS#5
)的私钥,则使用它。 - 否则,将其解释为
PKCS#8 DER
格式,因此将其格式化为PKCS#8 PEM
。
- 如果
处理DER
文件的说明
- 将
PKCS#8 DER
转换为PKCS#8 PEM
时,会确定是否设置了密码。如果设置了密码,则将其视为加密密钥;如果没有设置密码,则将其视为未加密的密钥。 - 无法自动识别文件是否为
PKCS#5 DER
,因此在使用之前需要手动将此类密钥转换为手动,其头为RSA PRIVATE KEY
。 - 与可以解释为
DER base64
格式的证书不同,私钥的读取不进行这种区分。如果希望使用无特殊字符的格式,请使用PEM
。
有关更多私钥格式信息,请参阅以下链接:https://github.com/kjur/jsrsasign/wiki/Tutorial-for-PKCS5-and-PKCS8-PEM-private-key-formats-differences
关于序列号
证书包含一个用十六进制表示的序列号,例如,序列号27 2B
表示十进制中的证书号10027
。
然而,对于SAT,它不是以十六进制标准的形式识别序列号。SAT要求表示的序列号是转换后的十六进制ASCII表示
。因此,序列号为3330303031303030303030333030303233373038
的证书被标识为30001000000300023708
。
SAT的这种做法不是标准做法,通常也不被观察到。然而,它已决定这样解释其颁发的证书中提到的“序列号”数据,例如在Comprobante@NoCertificado
属性中。
作为对比的例子:在用于大量下载服务的Web服务中使用的XML文档签名中,确实使用了十进制表示法(十六进制转换为十进制),而不是字节表示法。
字节表示法有问题,因为并非所有字符都是可打印的或具有图形表示。十六进制表示法略有问题,因为它有许多变体,如使用大写字母和小写字母或前缀0x
。十进制表示法没有问题,它只是一个非常大的整数,所以必须将其处理为一个字符串。
希望SAT在未来重新考虑并使用十进制表示法来引用序列号。
读取和导出PFX文件
这个库支持从PFX(PKCS #12)文件获取Credential
对象,反之亦然。
导出PFX文件
<?php declare(strict_types=1); use PhpCfdi\Credentials\Pfx\PfxExporter; $credential = PhpCfdi\Credentials\Credential::openFiles( 'certificate/certificado.cer', 'certificate/private-key.key', 'password' ); $pfxExporter = new PfxExporter($credential); // crea el binary string usando la contraseña dada $pfxContents = $pfxExporter->export('pfx-passphrase'); // guarda el archivo pfx a la ruta local dada usando la contraseña dada $pfxExporter->exportToFile('credential.pfx', 'pfx-passphrase');
读取PFX文件并获取Credential
对象
<?php declare(strict_types=1); use PhpCfdi\Credentials\Pfx\PfxReader; $pfxReader = new PfxReader(); // crea un objeto Credential dado el contenido de un archivo pfx $credential = $pfxReader->createCredentialFromContents('contenido-del-archivo', 'pfx-passphrase'); // crea un objeto Credential dada la ruta local de un archivo pfx $credential = $pfxReader->createCredentialsFromFile('pfxFilePath', 'pfx-passphrase');
兼容性
此库将保持与最新PHP活跃版本的兼容性。
我们还使用语义版本2.0.0进行版本控制,因此您可以使用这个库而不用担心破坏您的应用程序。
贡献
欢迎贡献力量。请阅读CONTRIBUTING获取更多详细信息,并记得查看待办事项文件TODO和变更日志文件CHANGELOG。
版权和许可
phpcfdi/credentials
库的版权©由PhpCfdi所有,并授权使用MIT许可证(MIT)。有关更多信息,请参阅LICENSE。