Firma electrónica en pdf (acrobat)

Cuando se firma electrónicamente un pdf , una de las maneras de hacerlo, que es la que más acostumnbrados estamos a ver, es aquella en la que el visor pdf nos muestra un banner indicando que esl documento está firmado, y además, nos muestra los datos del certificado con el que se ha firmado, y la posibilidad de validarlo.

Simplemente, lo que hace es insertar o incrustar la firma y los datos del certificado firmante en el propio documento pdf. Trataremos de comprobar realmente cómo se hace.

Tanto el hash cifrado (lo que se entiende formalmente como la firma) como el certificado digital son incorporados a una estructura de datos específica del fichero PDF, conocida técnicamente como “diccionario de firma”. Esta estructura puede contener información opcional, como por ejemplo un sellado de tiempo (timestamp en inglés). La adecuación de la firma al formato PDF, de acuerdo con el estándar ISO 32000-1, se conoce como PAdES.

Toda la información la podemos encontrar en la documentación de Acrobat:

https://www.adobe.com/devnet-docs/etk_deprecated/tools/DigSig/Acrobat_DigitalSignatures_in_PDF.pdf

Las siguientes figuras, extraídas de la documentación de acrobat, muestran claramente el proceso. Lo que se hace es crear un nuevo archivo pdf donde se inserta una estructura que contiene el certificado del firmante, y la firma (hash firmado, en la figura 3 lo indicado como Signed message digest). El hash que se firma no es el del documento pdf original, sino el del nuevo, pero solo de la parte correspondiente al resto de la estructura del pdf, excluyendo la parte de la firma que se está incrustando.

El «hueco» donde se inserta el objeto firma electrónica se especifica con el parámetro Byterange, que es un array con los valores de la posición de los bytes del documento pdf creado. En la figura 4 anterior, los valores 0 y 840 corresponden a la posición (offset) del byte inicial del pdf (0), a la posición del byte donde se encuentra el símbolo «<« tras el parámetro /Contents. El número 960 corresponde al último byte (offset) donde se ha incrustado el objeto firma, el cual acaba en un símbolo «>» que no se incluye en este cálculo. Y por último, la longitud o tamaño en bytes de lo que queda del archivo.

Por tanto, lo que hace el proceso de firma es lo siguiente:

  • calcula el hash (SHA256) del contenido binario del nuevo pdf creado, pero excluyendo el objeto firma, indicado en el ByteRange, es decir, en el caso de la figura 4, calcula el hash del conjunto de valores binarios entre los bytes 0 y 840 y 960 y 1200.
  • Ese hash se cifra o encripta con la clave privada del firmante (tenemos lo que realmente e la firma), y se crea un objeto codificado en hexadecimal, del tipo PKCS#7, que incluye además del hash firmado,el certificado del firmante (clave pública) y otros datos, y se incrusta en el hueco que se ha dejado (en el ejemplo, entre los bytes 841 y 959), lo que falta se rellena con «0», delimitado por el valor «<» en el offset 840 y el valor «>» en el offset 960.

Es necesario tener claro como se calculan los valores de ByteRange para insertar el objeto firma y calcular el hash a firmar. Pues el objeto firma va a incluir los delimitadores «<» y «>», aunque el objeto en sí mismo no los tiene, quizá esta figura lo muestra más claramente:

estructura del archivo pdf binario y valores de ByteRange

Comprobación de la firma

Por ejemplo, partiendo de un pdf firmado (este tiene una pequeña advertencia dada la caducidad ya de la firma con el DNI electrónico, pero no es ningún problema)

pfd firmado, propiedades de la firma electrónica incrustada

Si abrimos ester archivo en un editor hexadecimal (para ver el archivo binario tal cual), por ejemplo HxD (https://mh-nexus.de/en/), y lo configuremos para que use los indicadores de posición (offsets) en valor decimal, entonces comprobamos lo que la documentación de Acrobat nos indica, unos metadatos con el tipo de firma, el parámetro ByteRange y el objeto Firma:

editor binario del pdf, parte inicio del archivo

En la figura anterior se muestran claramente los valores de los parámetros y los datos, en este caso, una firma de tipo detached y pkcs7 y unos valores de ByteRange 0, 256, 54258 y 860197. Por lo que el objeto firma comienza en el byte número (offset) 256, cuyo valor inmediatamente siguiente es justo el delimitador «<«.

El último byte del objeto firma, cuyo valor inmediatamente anterior ha de corresponderse con el delimitador «>» ha de ser el 54258, como comprobamos en el editor hexadecimal avanzando en el archivo (y a su vez, comprobamos el relleno con ceros):

Parte final del objeto firma

Ahora podemos extraer esa porción de código binario (en HxD simplemente cortando y pegando) y así tendremos dos archivos binarios más, uno el pdf con el contenido, cuyo hash (sha-256) es lo que se firma, y otro el objeto firma, el cual tendremos codificado en hexadecimal, y delimitado por los valores «<» y «>» que tendremos que eliminar previamente para obtener el archivo binario.

Sin más que seleccionar el rango de bytes y «cortarlo» y pegarlo en un archivo nuevo en el propio HxD, o bien en un editor de texto.lo tenemos:

Seleccionando el bloque del objeto firma

El pdf original quedará «reducido», y es el archivo sobre el que se calcula el hash para firmarlo.

pdf sin el objeto firma, sobre el que se calcula el hash que se firma

En lo obtenido del objeto firma hay que quitar (borrar) los delimitadores «<» y «>» (que además no forman parte del set de caracteres hexadecimal), y lo tenemos que convertir, pues es una cadena de texto que se corresponde a los bytes de un nuevo archivo binario (pkcs7), Esto puede hacerse de múltiples formas,

borrar delimitador al inicio
borrar delimitador final

Podemos simplemente usar un conversor online (hex string to file), por ejemplo https://tomeko.net/online_tools/hex_to_file.php?lang=en , o bien extraer desde el pdf inicial el archivo, usando alguna pequeña herramienta o script, por ejemplo el siguiente en php, extraído de https://newbedev.com/how-to-retrieve-digital-signature-information-from-pdf-with-php, pero con alguna pequeña corrección en la expresión regular para sacar el ByteRange:

texto hexadecimal a archivo binario online
<?php

 $content = file_get_contents('test.pdf');

 $regexp = '#ByteRange \[\s*(\d+) (\d+) (\d+)#'; // subexpressions are used to extract b and c

 $result = [];
 preg_match_all($regexp, $content, $result);

echo $result[1][0]."\n";
echo $result[2][0]."\n";
echo $result[3][0]."\n";
echo "\n";

 // $result[2][0] and $result[3][0] are b and c
 if (isset($result[2]) && isset($result[3]) && isset($result[2][0]) && isset($result[3][0]))
 {

     $start = $result[2][0];
     echo " STart" . $start;
     $end = $result[3][0];
     if ($stream = fopen('test.pdf', 'rb')) {
         $signature = stream_get_contents($stream, $end - $start - 2, $start + 1); // because we need to exclude < and > from start and end

         fclose($stream);
     }

     file_put_contents('signature.pkcs7', hex2bin($signature));
     file_put_contents('signature.hex',$signature);
}

?>

O también muy sencillo con el comando xxd de linux (opción -r reverse).

En cualquier caso el resultado es el mismo, y si abrimos con el editor de texto el archivo binario recién extraido, ya pueden observarse cadenas de texto legibles con datos del certificado usado en este caso (DNI electrónico)

Mostrando el archivo pkcs7 en el editor de texto

Y para ver realmente el contenido de este archivo de firma pkcs#7 y, teniendo en cuenta el formato ASN1, basta con usar las herramientas openssl, pasándole como argumento el archivo que acabamos de extraer y por ejemplo volcando el resultado a un archivo de texto:

openssl asn1parse -inform der -in myfile.dat > datos_pkcs7.txt

Aunque también puede hacerse de modo online con un decodificador ASN1 (https://lapo.it/asn1js/#)

decodificar el archivo binario pkcs#7

Por último, buscamos el hash que se ha firmado, que en el archivo pkcs#7 ya se obtiene directamente (pues al disponer de la clave pública del certificado firmante puede hacerse), que se encuentra por messageDigest:

hash que se ha firmado, contenido en el archivo pkcs#7, incrustado en el pdf

El valor del hash 39AAB1BA621BF6E480F283488DBBD95DB36BF736E552616BD9A07D4C7C329271 podemos comprobarlo con el resto del archivo pdf al que quitamos el objeto firma, que efectivamente coincide (este archivo resultante no puede abrirse como pdf)

hash del pdf firmado al extraerle y borrar el objeto firma (bytes)

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.