Artículo técnico

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

El ticket de soporte decía: "Los estados de cuenta abren bien en nuestras computadoras, pero el sistema de gestión de registros del cliente marca todos los archivos como ilegibles". Los PDF estaban cifrados con AES-256, exactamente como exigía el contrato. La causa raíz estaba en un solo booleano: los documentos se escribieron 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 intercambio de derivación de clave distinto y una falla dura que nunca apareció en ninguna máquina de desarrollo con Acrobat actual.

El cifrado es una de las pocas funciones de PDF donde una configuración incorrecta no produce ningún síntoma visible localmente y sí una falla total en remoto. HotPDF, un componente PDF VCL nativo para Delphi y C++Builder, expone el modelo completo de protección ISO 32000 mediante un conjunto pequeño de propiedades; este artículo asigna cada propiedad a la decisión que realmente controla.

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 el código de salida protegida. La contraseña de usuario controla el descifrado: sin ella, o sin la contraseña de propietario, un lector conforme no puede reconstruir la clave del archivo y el contenido es criptográficamente ilegible. La contraseña de propietario controla la configuración de permisos: un lector que la recibe concede acceso completo sin importar las banderas de restricción.

Los permisos en sí, impresión, extracción de contenido, llenado de formularios, son una promesa más débil. Son banderas que un visor lee y acepta respetar (ISO 32000-2 §7.6.4). El cifrado protege los bytes; las banderas de permiso solo instruyen 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. Construyan el modelo de amenaza alrededor de esa separación: la confidencialidad proviene de la contraseña de usuario, mientras que las banderas de permiso moldean el comportamiento en los visores principales y nada más.

Orden de configuración: todo antes de BeginDoc

HotPDF establece el diccionario de cifrado y la clave del archivo cuando se ejecuta BeginDoc. Por lo tanto, cada propiedad de protección debe asignarse primero, sobre todo CryptKeyLength, que selecciona el esquema mediante los valores THPDFKeyType k40, k128, aes128 y aes256. Asignarla después de BeginDoc no lanza excepción; el documento simplemente conserva los parámetros con los que empezó, exactamente el tipo de divergencia silenciosa que meses después aparece 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, en línea con el límite de ISO 32000-2 para los esquemas AES-256. Si su política de contraseñas genera secretos más largos, trunquen deliberadamente de su lado en lugar de permitir 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 intercambios 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 estandarizado en ISO 32000-2 para PDF 2.0, la variante que cierra la debilidad conocida en el paso de verificación de contraseña de la revisión 5.

El intercambio de ingeniería es compatibilidad frente a estandarización. La revisión 6 requiere visores creados para PDF 1.7 Extension Level 3 o PDF 2.0; sistemas de archivo antiguos, renderizadores embebidos y herramientas de línea de negocio sin mantenimiento no abren el archivo en absoluto, exactamente como el ticket del inicio. A menos que una política de seguridad nombre explícitamente ISO 32000-2 revisión 6, publiquen revisión 5 y documenten la decisión: es el valor predeterminado más seguro, y pueden revisarlo cuando el consumidor que avanza más lento se actualice.

El mismo razonamiento aplica un nivel más abajo. THPDFKeyType todavía ofrece k40, k128 y aes128 por compatibilidad con cadenas de herramientas antiguas, pero los tres pertenecen al mantenimiento de sistemas heredados, no a diseños nuevos: RC4 de 40 bits se rompe con hardware común, y los esquemas de 128 bits son anteriores a las revisiones AES-256 que las revisiones de seguridad actuales esperan ver. 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 antiguos existen para reproducir archivos históricos, no para escribir nuevos.

Banderas de permiso sin contraseña de apertura

Un requisito frecuente es lo opuesto al 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 contraseña abierta, con las operaciones permitidas listadas 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ía de asistencia, prModifyStructure, prEditAnnotations, prFillAnnotations y prAssemble. Dos merecen una recomendación específica. Mantengan prExtractContent habilitado en casi todos los perfiles: es el bit que permite a los lectores de pantalla y otras tecnologías de asistencia alcanzar el contenido, y revocarlo convierte una decisión de derechos en un defecto de accesibilidad. Y observen que prPrint sin prPrint12bit produce impresión degradada en algunos visores, algo que los usuarios finales reportan como un error de renderizado y no como un permiso.

La verificación es rápida y vale automatizarla en los controles de publicación: abran una muestra de la salida de cada perfil en Acrobat y lean la pestaña Security de Document Properties, que nombra el algoritmo ("AES 256-bit") y detalla una por una las operaciones permitidas. Luego repitan la apertura en el visor más antiguo que sus clientes realmente ejecutan. Los cinco minutos que cuesta esa segunda revisión son exactamente la brecha que permitió que el ticket de revisión 6 del inicio llegara a producción.

Quitar protección de archivos existentes

El descifrado es el mismo modelo de propiedades en reversa: cargar el documento con su credencial, apagar la protección y guardar 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 cual está bien para archivos ordinarios. Para entradas de cientos de megabytes hay un camino más barato: DecryptFile descifra durante una copia a nivel de archivo y toma una ruta directa de reescritura AES-256 que evita construir el árbol de objetos cuando la entrada lo permite. Pertenece a la Direct File API descrita en el artículo complementario sobre procesar PDF grandes desde Delphi.

Restricciones que interactúan con el cifrado

Los perfiles de archivo chocan de frente: ISO 19005 prohíbe el cifrado en archivos PDF/A, así que un flujo que cifra un documento y afirma conformidad PDF/A es inválido por construcción. Cuando existen ambos requisitos, entreguen dos artefactos, una copia de distribución cifrada y una copia de archivo sin cifrar, en lugar de intentar satisfacerlos en un solo archivo.

Tampoco hay ruta de recuperación. El cifrado PDF no tiene mecanismo de depósito: perder la contraseña de usuario en un archivo R5 o R6 significa fuerza bruta o nada. Traten 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 terminan en control de versiones y en la copia de trabajo de cada desarrollador.

FAQ: proteger PDF desde Delphi

¿Deshabilitar prInformationCopy impide que la gente copie 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 traten la bandera como guía de flujo, no como prevención de fuga de datos.

¿Debe un proyecto nuevo habilitar 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 cobertura de visores mucho más amplia, por eso es el valor predeterminado.

¿Puedo cambiar permisos en un PDF que no creé?

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

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