Artículo técnico

Cifrado PDF AES-256 en Delphi: configuración de HotPDF y errores habituales

El ticket de soporte decía: "Los extractos se abren bien en nuestros equipos, pero el sistema de gestión documental del cliente marca todos los ficheros como ilegibles". Los PDFs estaban cifrados con AES-256, exactamente como exigía el contrato. La causa raíz estaba en un único booleano: los documentos se habían escrito con la revisión de cifrado 6, la variante PDF 2.0 de ISO 32000-2, y la cadena de archivado del cliente solo entendía la revisión 5. Mismo algoritmo, misma longitud de clave, mismas contraseñas: un protocolo de derivación de clave distinto y un fallo duro que nunca apareció en ninguna máquina de desarrollo con Acrobat actualizado

El cifrado es una de las pocas funciones PDF en las que una configuración errónea no produce ningún síntoma visible en local y sí un fallo total en remoto. HotPDF, un componente PDF VCL nativo para Delphi y C++Builder, expone todo el modelo de protección de ISO 32000 mediante un puñado de propiedades; este artículo relaciona cada propiedad con la decisión que controla realmente

Qué prometen realmente las dos contraseñas

El cifrado PDF define dos credenciales con trabajos distintos, y confundirlas es el error de diseño más común en código de salida protegida. La contraseña de usuario gobierna el descifrado: sin ella, o sin la contraseña de propietario, un lector conforme no puede reconstruir la clave del fichero y el contenido resulta criptográficamente ilegible. La contraseña de propietario gobierna los permisos: un lector que la recibe concede acceso completo con independencia de los flags de restricción

Los permisos en sí, impresión, extracción de contenido, rellenado de formularios, son una promesa más débil. Son flags que un visor lee y acepta respetar (ISO 32000-2 §7.6.4). El cifrado protege los bytes; los flags de permiso se limitan a instruir al software conforme. Un usuario que abre legítimamente un documento con la contraseña de usuario tiene el contenido descifrado en memoria, así que "no copiar" y "no imprimir" son señales de política para visores bien comportados, no garantías criptográficas. Construya el modelo de amenaza alrededor de esa separación: la confidencialidad procede de la contraseña de usuario, mientras que los flags de permiso moldean el comportamiento en visores convencionales y nada más

Orden de configuración: todo antes de BeginDoc

HotPDF establece el diccionario de cifrado y la clave del fichero cuando se ejecuta BeginDoc. Por tanto, cada propiedad de protección debe asignarse antes, sobre todo CryptKeyLength, que selecciona el esquema mediante los valores THPDFKeyType k40, k128, aes128 y aes256. Asignarla después de BeginDoc no lanza ninguna excepción; el documento conserva simplemente los parámetros con los que empezó, justo el tipo de divergencia silenciosa que aparece meses después como hallazgo de cumplimiento

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'statement.pdf';
    Pdf.ActivateProtection := True;
    Pdf.CryptKeyLength := aes256;        // must be set before BeginDoc
    Pdf.UserPassword := 'open-secret';
    Pdf.OwnerPassword := 'admin-secret';
    Pdf.UseAES256R6 := False;            // R=5: widest viewer support
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Account statement, June 2026');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Las contraseñas son UTF-8 y tienen un límite de 127 bytes, igual que el límite de ISO 32000-2 para los esquemas AES-256. Si su política de contraseñas genera secretos más largos, trunque deliberadamente en su lado en lugar de dejar que la biblioteca y un visor futuro discrepen sobre dónde cae el corte

Revisión 5 o revisión 6: un booleano, dos ecosistemas

UseAES256R6 elige entre los dos protocolos AES-256. Con False, HotPDF escribe la revisión 5, el esquema AES-256 introducido como extensión de PDF 1.7 y soportado por aproximadamente quince años de versiones de visores. Con True, escribe la revisión 6, el algoritmo endurecido de derivación de clave normalizado en ISO 32000-2 para PDF 2.0, la variante que cierra la debilidad conocida del paso de verificación de contraseña en la revisión 5

La decisión de ingeniería enfrenta compatibilidad y normalización. La revisión 6 requiere visores construidos para PDF 1.7 Extension Level 3 o PDF 2.0; sistemas de archivado antiguos, renderizadores embebidos y herramientas de negocio sin mantenimiento no abren el fichero en absoluto, exactamente como en el ticket que abre este artículo. Salvo que una política de seguridad nombre explícitamente la revisión 6 de ISO 32000-2, entregue revisión 5 y deje la decisión documentada: es el valor por defecto más seguro, y podrá revisarlo cuando el consumidor que avanza más despacio se actualice

El mismo razonamiento aplica un nivel más abajo. THPDFKeyType todavía ofrece k40, k128 y aes128 por compatibilidad con cadenas antiguas, pero los tres pertenecen a trabajos de mantenimiento en sistemas heredados, no a diseños nuevos: RC4 de 40 bits se rompe con hardware corriente, y los esquemas de 128 bits son anteriores a las revisiones AES-256 que las revisiones de seguridad actuales esperan encontrar. Para cualquier documento producido en 2026, el espacio de decisión realista es AES-256 revisión 5 frente a revisión 6; los tipos de clave más antiguos existen para reproducir archivos históricos, no para escribir nuevos

Flags de permiso sin contraseña de apertura

Un requisito frecuente es lo contrario del secreto: cualquiera puede leer el documento, pero la impresión o la extracción deben restringirse. La configuración es una contraseña de usuario vacía más una contraseña de propietario no vacía, modo de apertura sin contraseña, con las operaciones permitidas enumeradas en ProtectOptions

Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.UserPassword := '';                      // anyone can open the file
Pdf.OwnerPassword := 'rotate-me-quarterly';  // guards the permission set
Pdf.ProtectOptions := [prPrint, prPrint12bit, prExtractContent];
Pdf.BeginDoc;
// ... page content ...
Pdf.EndDoc;

El conjunto THPDFProtectOptions cubre los bits de permiso ISO: prPrint, prPrint12bit para impresión de alta resolución, prInformationCopy para copia y extracción general, prExtractContent para extracción por tecnologías de asistencia, prModifyStructure, prEditAnnotations, prFillAnnotations y prAssemble. Dos merecen una recomendación específica. Mantenga prExtractContent activado en casi todos los perfiles: es el bit que permite a lectores de pantalla y otras tecnologías de asistencia acceder al contenido, y retirarlo convierte una decisión de derechos en un defecto de accesibilidad. Y tenga en cuenta que prPrint sin prPrint12bit produce impresión degradada en algunos visores, algo que los usuarios finales comunican como fallo de renderizado y no como permiso

La verificación es rápida y merece automatizarse dentro de las comprobaciones de release: abra una muestra de la salida de cada perfil en Acrobat y lea la pestaña Seguridad de Propiedades del documento, que nombra el algoritmo ("AES 256-bit") y enumera las operaciones permitidas una a una. Repita después la apertura en el visor más antiguo que sus clientes usen realmente. Los cinco minutos que cuesta esa segunda comprobación son exactamente la brecha que permitió que el ticket de revisión 6 del inicio llegara a producción

Eliminar la protección de ficheros existentes

El descifrado es el mismo modelo de propiedades en sentido inverso: cargue el documento con su credencial, desactive la protección y guarde el resultado

var
  Pdf: THotPDF;
  PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('encrypted.pdf', 'open-secret');
    if PageCount > 0 then
    begin
      Pdf.ActivateProtection := False;   // drop encryption on save
      Pdf.SaveLoadedDocument('plain.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Esta ruta analiza el documento completo, lo que es correcto para ficheros ordinarios. Para entradas de varios cientos de megabytes hay un camino más barato: DecryptFile descifra durante una copia a nivel de fichero, tomando una ruta directa de reescritura AES-256 que evita construir el árbol de objetos siempre que la entrada lo permita. Pertenece a la Direct File API descrita en el artículo complementario sobre procesar PDFs grandes desde Delphi

Restricciones que interactúan con el cifrado

Los perfiles de archivado chocan frontalmente: ISO 19005 prohíbe el cifrado en ficheros PDF/A, así que un flujo que cifra un documento y a la vez declara conformidad PDF/A es inválido por construcción. Cuando existan ambos requisitos, entregue dos artefactos, una copia cifrada de distribución y una copia de archivo sin cifrar, en lugar de intentar satisfacerlos en un solo fichero

Tampoco existe una vía de recuperación. El cifrado PDF no tiene mecanismo de custodia: perder la contraseña de usuario en un fichero R5 o R6 significa fuerza bruta o nada. Trate los secretos de propietario y usuario como credenciales de producción, generadas, guardadas en bóveda y rotadas, nunca como constantes en una unidad, donde acaban en control de versiones y en la copia de trabajo de cada desarrollador

FAQ: proteger PDFs desde Delphi

¿Desactivar prInformationCopy impide copiar texto?

Impide que los visores conformes ofrezcan comandos de copia. Cualquiera que pueda abrir el documento ya tiene el texto plano en memoria, así que trate el flag como guía de flujo, no como prevención de fuga de datos

¿Debe un proyecto nuevo activar UseAES256R6?

Solo cuando se haya verificado que todos los consumidores manejan cifrado PDF 2.0. La revisión 5 ofrece el mismo cifrado de contenido AES-256 con una cobertura de visores mucho más amplia, por eso es el valor por defecto

¿Puedo cambiar permisos en un PDF que no he creado?

Sí: cárguelo con su contraseña mediante LoadFromFile, ajuste ProtectOptions o las contraseñas, y escriba el resultado con SaveLoadedDocument, exactamente como en el ejemplo de descifrado anterior

Las propiedades de protección mostradas aquí forman parte del HotPDF Component estándar para Delphi y C++Builder; la página del producto incluye la referencia completa de cifrado, incluida la enumeración completa de permisos