phpcfdi/credentials

用于使用SAT的eFirma(fiel)和CSD(sellos)的库

v1.2.2 2024-06-07 00:37 UTC

This package is auto-updated.

Last update: 2024-09-07 01:13:01 UTC


README

Source Code Packagist PHP Version Support Discord Latest Version Software License Build Status Reliability Maintainability Code Coverage Violations Total Downloads

用于使用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 DERX.509 DER base64X.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是格式为PEMPKCS#8PKCS#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