Firmar a escala, sin concesiones
Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8Spec: ETSI EN 319 142-1ETSI EN 319 142-1Spec: RFC 5652, §5.1RFC 5652 §5.1
De un vistazo
Sección titulada «De un vistazo»Firmar un documento es una operación criptográfica. Firmar cien mil con un plazo encima es la misma operación, repetida, donde el fallo peligroso ya no es «fue lento», sino «uno de ellos salió sin firmar y nadie se dio cuenta». Esta página trata de hacer lo segundo sin renunciar a lo primero: firma masiva y concurrente donde cada firma sigue siendo correcta, la ejecución se niega a emitir un archivo que no pudo firmar y un trabajo grande se reanuda en lugar de volver a empezar.
Por qué esto importa
Sección titulada «Por qué esto importa»Una firma es un hecho por documento. Su resumen se calcula sobre un rango de bytes declarado que excluye el propio valor de la firma (Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8), de modo que no hay forma honesta de firmar mil documentos «como un lote» de una sola vez: cada uno lleva su propio CMS SignedData sobre sus propios bytes (Spec: RFC 5652, §5.1RFC 5652 §5.1). La escala, por tanto, multiplica las posibilidades de que exactamente una cosa falle en silencio: un identificador de clave que falló brevemente, una autoridad de sellado de tiempo que agotó su plazo, un trabajador que murió sosteniendo un archivo escrito a medias.
El resultado caro no es una caída. Una caída es ruidosa y se reintenta. El resultado caro es uno silencioso: un PDF sin firmar que parece terminado, guardado en un archivo, descubierto meses después por el validador de un auditor. A volumen, «mayormente firmado» es indistinguible de «firmado» hasta el momento en que se comprueba justo el que importa. El sentido entero de firmar a escala es hacer ese resultado estructuralmente imposible, no estadísticamente raro.
La versión corta
Sección titulada «La versión corta»- Cada documento se firma individualmente, sobre su propio rango de bytes. Lote es una palabra de planificación, no una criptográfica. No hay firma compartida.
- El nivel es un contrato, no una sugerencia. Se nombra un nivel de base PAdES y el motor produce exactamente ese nivel para cada documento, o falla ese documento de forma ruidosa (Spec: ETSI EN 319 142-1ETSI EN 319 142-1).
- La canalización es fail-closed. Un documento que no se puede firmar correctamente no pasa adelante como bytes en claro. Se retiene, no se entrega.
- La concurrencia es por documento, y segura por construcción. Las unidades de firma no comparten estado mutable, de modo que dos trabajadores no pueden corromper la salida del otro.
- Las ejecuciones grandes son duraderas. La salida confirmada no se vuelve a emitir al reanudar; una ejecución caída continúa desde su último punto de control en lugar de volver a firmar todo.
Cómo lo aborda NextPDF
Sección titulada «Cómo lo aborda NextPDF»El diseño descansa sobre una separación: producir la firma es un paso pequeño, determinista y por documento; ejecutar miles de ellos con seguridad es un paso de orquestación. Mantenerlos separados es lo que permite que cada uno siga siendo simple.
El paso de firma es el que nunca debe ceder. Se pide un nivel —un caso del enum
SignatureLevel, nunca una cadena que el motor tenga que interpretar— y ese
nivel se trata como un contrato para ese documento. El motor produce el nivel
solicitado o se detiene con un error procesable; no firma en silencio a un nivel
inferior dejando que un registro afirme uno superior. La corrección no se relaja
porque haya más documentos detrás de este. La firma número cien mil se calcula
con exactamente el mismo cuidado que la primera.
La regla fail-closed es lo que hace eso digno de confianza a volumen. La ruta de
firma de NextPDF se niega a emitir un artefacto que parece plausible pero está sin
firmar en lugar del que se pidió. La vía de aplicación admitida es la API de alto
nivel de Document: se configura la firma con Document::setSignature() y luego se
piden los bytes con Document::getPdfData() (o save() / output()), y esa
única pasada de escritura o bien emite un PDF correctamente firmado o lanza una
excepción antes de devolver bytes, nunca un archivo sin firmar que quien lo
llama cree firmado. Aplicada a todo un lote, esta es la regla que convierte «uno
se coló sin firmar» de un defecto latente y silencioso en un único trabajo
fallido y reintentable.
- Calentar el material de firma una vezAl arrancar el trabajador, se abre la fuente de clave/certificado y el cliente de sellado de tiempo. Este coste se paga una vez por trabajador, no una vez por documento.
- Encolar los documentosUna cola contiene los trabajos por documento. La cola es el dial de rendimiento: los trabajadores de firma escalan horizontalmente tras ella.
- Representar y firmar un documentoUna unidad desechable representa el documento y luego lo firma sobre su propio rango de bytes al nivel PAdES solicitado. No se comparte nada con el documento siguiente.
- Confirmar si hay éxito, retener si hay falloUn archivo correctamente firmado se confirma una vez. Un documento que no se pudo firmar se da por fallido y se reintenta, nunca se emite como bytes sin firmar.
- Puntos de control, y reanudar tras una caídaUna ejecución duradera registra lo que se ha confirmado. Tras una caída continúa desde el último punto de control en lugar de volver a firmar todo el lote.
Core te da la corrección criptográfica: firma CMS por software y PAdES B-B (con B-T a través del cliente de sellado de tiempo) donde cada documento se firma individualmente y fail-closed. La orquestación que hace que una ejecución grande sea duradera, concurrente y exactamente-una-vez —el motor de representación sin efectos secundarios más el confirmador, los almacenes de puntos de control, idempotencia y mensajes muertos— es el módulo Stream en las ediciones avanzadas; la firma respaldada por hardware mediante un HSM o un KMS en la nube es asimismo una junta de las ediciones avanzadas. Core demuestra que cada firma es correcta; las ediciones avanzadas hacen que un millón de ellas sean sobrevivibles.
Ejemplo práctico
Sección titulada «Ejemplo práctico»La forma de abajo es la unidad de firma por documento dentro de un bucle de lote. Cada iteración firma un documento a un nivel nombrado y o bien produce un resultado correctamente firmado o falla ese único trabajo; nunca devuelve bytes sin firmar disfrazados de resultado.
<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Security\Signature\CertificateInfo;use NextPDF\Security\Signature\SignatureLevel;use NextPDF\Exception\SignatureException;use Psr\Log\LoggerInterface;
/** * One signing-batch iteration: render, sign at a named level, commit or fail. * * The factory and the certificate source ($certInfo, the warmed signing * material) are process-lifetime singletons; the document is disposable. A * document that cannot be signed at the requested level fails this job loudly — * it is never committed unsigned. * * @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs */function signBatch( DocumentFactoryInterface $factory, CertificateInfo $certInfo, LoggerInterface $logger, iterable $jobs,): void { // The level is an explicit, ordered contract — not a flag we hope is honoured. $level = SignatureLevel::PAdES_B_T;
foreach ($jobs as $jobId => $build) { // Fresh, disposable unit — shares the warmed signing material only. $doc = $factory->create(); $doc = $build($doc);
try { // Sign over this document's own byte range, at exactly $level, // or throw. There is no "signed lower, reported higher" path. $doc->setSignature(certInfo: $certInfo, level: $level); $signed = $doc->getPdfData(); } catch (SignatureException $e) { // Fail-closed: this document does NOT continue as unsigned bytes. // The job is failed and left for retry / dead-letter handling. $logger->error('pdf.sign.failed', ['job_id' => $jobId, 'reason' => $e->getMessage()]); continue; }
// Only a correctly-signed result reaches the commit step. commitSignedOutput($jobId, $signed); unset($doc, $signed); // release per-document state before the next iteration
$logger->info('pdf.sign.committed', ['job_id' => $jobId, 'level' => $level->value]); }}El catch es la línea que soporta el peso. Es la diferencia entre una ejecución
que retiene los documentos que no pudo firmar y una ejecución que los entrega de
todos modos. El continue no disimula el fallo: el trabajo se registra y se deja
para reintento, de modo que el lote termina con una lista conocida y completa de
qué se firmó y qué no, nunca con un hueco silencioso.
Concepto erróneo habitual
Sección titulada «Concepto erróneo habitual»El primer concepto erróneo es que «firmar por lotes» significa una sola firma aplicada a muchos archivos. No es así, y cualquier sistema que lo afirme no está produciendo firmas PAdES válidas: el resumen de cada documento está ligado a sus propios bytes (Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8). El lote trata puramente de cuántos y con qué rapidez, nunca de compartir la unidad criptográfica.
El segundo es que la concurrencia significa relajar la corrección a cambio de velocidad —que un firmante rápido tiene que recortar un detalle que el cuidadoso no recorta—. No es así. Como las unidades de firma no comparten estado mutable, ejecutarlas en paralelo cambia la planificación, no los bytes. Cada firma paralela se calcula con el mismo rigor que una sola; el paralelismo está en la orquestación que las rodea.
El tercero es que la durabilidad es algo que se añade después de la primera ejecución nocturna fallida. Para entonces ya se ha perdido la ejecución. Una canalización reanudable tiene que saber, por documento, qué se confirmó y qué no antes de la caída, que es exactamente lo que existen para registrar los almacenes de puntos de control e idempotencia.
Límites y fronteras
Sección titulada «Límites y fronteras»- Cada firma es por documento y está ligada a la norma; no hay atajo de lote. El volumen cambia la planificación, no la unidad criptográfica. NextPDF firma cada documento sobre su propio rango de bytes.
- Core hace firma CMS por software y PAdES B-B (B-T mediante un cliente de sellado de tiempo). El motor duradero, concurrente y exactamente-una-vez de representación y firma es el módulo Stream en las ediciones avanzadas; la custodia de claves respaldada por HSM/KMS es una junta de las ediciones avanzadas. Esta página no reclama esa orquestación como parte de Core.
- Fail-closed es el comportamiento del motor, no una garantía sobre tu
cableado. NextPDF se niega a emitir un archivo sin firmar pero creído firmado
y expone la ruta de firma admitida. Una canalización que captura el error
resultante y confirma de todos modos ha elegido derrotar la garantía: el
planteamiento que el
catch/continuedel ejemplo existe para impedir. - El nivel PAdES se aplica por documento, no se certifica para la ejecución. El motor produce el nivel de base solicitado o falla; eso es una aplicación estructural, no un veredicto de conformidad de un tercero para los archivos producidos. La progresión de niveles en sí se trata en Perfiles de base PAdES.
- La cola, la custodia de claves, la autoridad de sellado de tiempo y el almacén de objetos son tuyos. NextPDF aporta la corrección de firma por documento y, en las ediciones avanzadas, las primitivas de orquestación duradera. No ejecuta tu infraestructura ni responde por tu TSA.
| Edition | Availability |
|---|---|
| Core | Firma CMS por software por documento, PAdES B-B (B-T con un cliente de sellado de tiempo), firmada individualmente sobre el propio rango de bytes de cada documento, fail-closed frente a la salida sin firmar en silencio. La firma sencilla por documento no necesita ningún nivel comercial. |
| Pro | Añade el módulo Stream: un motor de representación sin efectos secundarios más un confirmador duradero y almacenes de puntos de control, idempotencia y mensajes muertos: ejecuciones de lote concurrentes, a prueba de caídas y exactamente-una-vez que se reanudan en lugar de reiniciarse. |
| Enterprise | Añade custodia de claves respaldada por hardware (HSM mediante PKCS#11, o un KMS en la nube) para que la clave privada nunca abandone el dispositivo, y los niveles PAdES a largo plazo (B-LT, B-LTA) que mantienen un archivo de alto volumen verificable durante décadas. |
Documentos relacionados
Sección titulada «Documentos relacionados»- Generación de documentos de alto volumen: el modelo de lote encolado y de memoria acotada sobre el que esta página firma; léelo primero para conocer la disciplina de rendimiento y medición.
- Perfiles de base PAdES: qué añade cada nivel (de B-B a B-LTA), para firmar al nivel que la obligación necesita.
- Cómo se integra una firma en un PDF: la base de rango de bytes y diccionario que hace que una firma sea por documento.
- Firma respaldada por HSM: dónde se sitúa la frontera de la clave privada cuando el material de firma vive en hardware.
- Stream (Pro): el motor de representación duradero, concurrente y exactamente-una-vez que convierte una sola unidad de firma en una ejecución reanudable.
Glosario
Sección titulada «Glosario»- Firma por lotes: firmar muchos documentos según una planificación. Un concepto de planificación; cada documento se sigue firmando individualmente sobre sus propios bytes.
- Fail-closed: ante un fallo que de otro modo produciría una salida sin firmar o errónea, la canalización retiene el documento e informa, en lugar de pasarlo adelante como bytes en claro.
- Confirmación exactamente-una-vez: una propiedad de canalización duradera por la que una salida correctamente firmada se publica una vez y no se vuelve a emitir cuando una ejecución caída se reanuda.
- Punto de control: registro duradero por documento de lo que se ha confirmado, para que una ejecución pueda continuar desde donde se detuvo en lugar de volver a firmar todo.
- CMS SignedData: el contenedor criptográfico para firmas sobre contenido (puede llevar varios firmantes); esta canalización produce la firma PDF de un firmante por documento, la unidad por documento que produce un lote.
- PAdES: PDF Advanced Electronic Signatures, la familia de perfiles ETSI EN 319 142 para la firma de PDF; sus niveles de base van de B-B a B-LTA.