Artículo técnico

Auditoría de cifrado y permisos PDF en Delphi con PDFlibPas

Una canalización de recepción de documentos puso una vez en cuarentena un lote completo de escritos legales como "cifrado", aunque todos los archivos se abrían en Adobe Acrobat sin pedir contraseña. La sonda había reducido el estado de seguridad a un solo booleano. Los archivos sí tenían un diccionario /Encrypt — pero sus filtros criptográficos estaban configurados como Identity, lo que significa que las cadenas y los streams estaban almacenados en texto claro. Formalmente cifrados, prácticamente abiertos, y bloqueados durante dos días por una bandera mal leída. Ese incidente es una buena definición de lo que debe ser una auditoría real de cifrado: no "está cifrado", sino qué algoritmo, qué revisión, qué contraseñas, qué permisos y qué partes del archivo toca realmente el cifrado. PDFlibPas, el motor PDF de losLab para Delphi y C++Builder, expone todas esas respuestas mediante una API plana y una capa de clases tipada.

Qué registra realmente el diccionario /Encrypt

ISO 32000-1 §7.6 define la seguridad del documento mediante unas cuantas entradas de diccionario, y PDFlibPas las refleja una por una en el registro TPDFEncryption: la versión de filtro V y la revisión R que seleccionan la familia de algoritmo, Length para el tamaño de clave, los bits de permiso en P, las cadenas de validación de contraseña de propietario y usuario O y U (más OE/UE para AES-256), una bandera EncryptMetadata y los nombres de los filtros criptográficos aplicados a cadenas, streams y archivos incrustados.

El registro es aliado del auditor precisamente porque no interpreta. El incidente de recepción anterior se vuelve visible en campos como StringFilterIdentity y StreamFilterIdentity: cuando son true, los datos correspondientes no se transforman en absoluto, sin importar lo que afirme el estado cifrado del documento. De forma similar, EncryptMetadata = False indica que los metadatos XMP son legibles por cualquier indexador aunque el contenido de página no lo sea — relevante cuando las reglas de ruteo dependen de campos de título o autor.

Una sonda de seguridad de diez líneas con la API plana

Para la mayoría de canalizaciones, cuatro llamadas planas responden las preguntas cotidianas. LoadFromFile devuelve 1 cuando tiene éxito; después de eso, los inspectores de nivel documento trabajan contra el estado descifrado:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // decrypted / encrypted / unknown
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 vs AES family
    Writeln('strength  : ', PDF.EncryptionStrength);   // key length class
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

CheckPassword importa más de lo que parece. PDF distingue una contraseña de usuario (requerida para abrir) de una contraseña de propietario (otorga derechos completos y anula permisos), y un archivo abierto con la contraseña de usuario se comporta de forma muy distinta a uno abierto con la de propietario — mismos bytes, derechos distintos. La capa de clases hace explícita la distinción: TPDFDocument.HasUserPassword y HasOwnerPassword informan qué está configurado, mientras que IsUserPassword y IsOwnerPassword informan cuál abrió realmente la sesión actual. Un registro de auditoría debe guardar esa distinción, nunca los valores de contraseña.

La escala Strength: de RC4 de 40 bits a AES-256 revisión 6

Las funciones planas Encrypt y EncryptFile aceptan un entero Strength con cinco valores significativos: 0 para RC4 de 40 bits, 1 para RC4 de 128 bits, 2 para AES de 128 bits (legible desde Acrobat 7), 3 para AES de 256 bits como se introdujo con Acrobat 9, y 4 para AES de 256 bits según lo requieren Acrobat X y posteriores.

Los valores 3 y 4 merecen una mirada más cercana, porque "AES-256" son dos esquemas distintos. Strength 3 corresponde a la revisión 5 del security handler, un diseño intermedio que llegó con Acrobat 9 y nunca fue adoptado por ISO. Strength 4 corresponde a la revisión 6, el esquema con la función reforzada de derivación de clave que estandariza ISO 32000-2. Para documentos nuevos no hay razón para elegir 3; para auditorías, la diferencia importa porque una política de cumplimiento que diga "AES-256 según ISO 32000-2" solo se satisface con R6. En lectura, la capa de clases separa ambos como esAES256Bit frente a esAES256BitAcroX, y la propiedad EncryptionAcroX responde directamente la pregunta R5 o R6.

Bits de permiso y la letra chica de la longitud de clave

EncodePermissions empaqueta ocho banderas en el entero que esperan Encrypt y EncryptFile: imprimir, copiar, cambiar y agregar notas forman el conjunto básico, mientras que llenar campos, copiar para accesibilidad, ensamblar e imprimir con calidad completa forman el conjunto extendido. La letra chica, explicada en la propia demo de cifrado de la biblioteca, es que las cuatro extendidas solo se respetan con fuerza de 128 bits o superior — y lo mismo ocurre al degradar la calidad de impresión poniendo en 0 la bandera de impresión de calidad completa. Codifiquen una política de "solo impresión de baja resolución" en un documento de 40 bits y los visores simplemente imprimirán con calidad completa.

La advertencia más profunda es quién aplica esos bits. Los permisos en PDF son instrucciones para lectores conformes, no restricciones criptográficas: la clave de descifrado es la misma esté o no permitido copiar. Un conjunto de permisos bloqueado mantiene honestos a los visores honestos. Si su obligación es impedir extracción en lugar de desalentarla, eso requiere una contraseña de usuario y controles de proceso, y el informe de auditoría debe ser explícito sobre cuál de los dos regímenes cubre el archivo.

Definir la política y probar que quedó aplicada

Aplicar cifrado a archivos existentes no requiere cargarlos en el árbol de objetos. EncryptFile procesa entrada a salida en una sola llamada, y el ciclo de auditoría reabre el resultado para verificar el desenlace — un patrón tomado casi literalmente de la demo incluida:

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // print allowed; copy/change/notes denied
                            0, 0, 0, 1));  // extended set: full-quality print only
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

Los equipos que trabajan en la capa de documento obtienen la misma operación con conjuntos tipados en lugar de empaquetar bits, lo que se lee mejor en revisión de código:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

De cualquier modo, el paso de lectura posterior no es ceremonia opcional. Detecta los errores clásicos de despliegue — una versión antigua de la biblioteca que degrada silenciosamente la fuerza solicitada, una ruta de salida que nunca se escribió, un entero de permisos armado con argumentos en orden incorrecto — en el momento en que ocurren en lugar de en el escritorio del cliente. GetEncryptionFingerprint les da un valor compacto para almacenar con el registro del trabajo y comparar más tarde.

Falsos positivos de auditoría que conviene codificar

Tres patrones producen conclusiones erróneas una y otra vez en escáneres de seguridad. Primero, el caso de filtro criptográfico Identity del inicio: un diccionario /Encrypt presente, contenido real intacto — revisen las banderas Identity por filtro antes de declarar datos protegidos. Segundo, la división de metadatos: EncryptMetadata puede diferir del resto del archivo en ambos sentidos, así que "el archivo está cifrado" no dice nada sobre si el paquete XMP lo está. Tercero, archivos incrustados: PDF permite un filtro criptográfico dedicado para adjuntos, por lo que los adjuntos pueden ser la única parte cifrada de un documento por lo demás abierto, o la única parte en texto claro de uno cifrado. Un registro de auditoría que capture por separado las tres asignaciones de filtro — cadenas, streams, archivos incrustados — es inmune a las tres trampas; uno que guarda un booleano se equivoca con puntualidad.

Preguntas que los auditores sí hacen

¿Puedo quitar el cifrado de un archivo cuando tengo la contraseña? Sí — DecryptFile(InputFileName, OutputFileName, Password) lo hace sin carga completa, y Decrypt sobre documento cargado hace lo mismo en memoria. Si deben hacerlo o no es una pregunta de política que las reglas de recepción deben responder explícitamente.

¿Qué fuerza deben usar los documentos nuevos? Strength 4, AES-256 revisión 6, salvo que deban soportar visores anteriores a Acrobat X. Strength 2 (AES-128) sigue siendo el piso pragmático para flotas de visores muy antiguas; las opciones RC4 existen para auditoría de compatibilidad, no para salida nueva.

¿Las banderas de permiso detienen a un usuario decidido que quiere copiar texto? No. Las respetan los visores conformes, e ISO 32000 las plantea como permisos de acceso que los lectores deben aplicar, no como criptografía. Combínenlas con una contraseña de usuario cuando la confidencialidad sea el requisito real.

Lecturas adicionales

El estado de cifrado alimenta directamente las decisiones de firma — un banco de trabajo que valida y firma documentos necesita la misma disciplina de lectura posterior, como se explica en el artículo sobre banco de trabajo de cumplimiento y firmas. Para canalizaciones por lotes que aplican EncryptFile sobre miles de documentos grandes, la guía de acceso directo para PDFs grandes muestra cómo mantener plana la memoria mientras lo hacen.

La referencia completa de la API de cifrado está en la página del producto PDFlibPas.