Usted cifra un reporte con una máscara pensada para permitir todo, $FFFFFFF0, y la pestaña Security de Acrobat lista imprimir y cambiar el documento como no permitidos. /P es una palabra de flags de 32 bits sin signo numerada desde el bit 1, así que esa máscara limpia print (bit 3) y modify (bit 4); el valor que lo permite todo es $FFFFFFFC, el punto de partida de TPdfEncryptOptions en PDFium Component
Cómo se organiza la palabra de flags /P
Solo ocho posiciones llevan significado, el resto está reservado y el conteo arranca en 1. ISO 32000-1 §7.6.3.2 dice que P "debe interpretarse como una cantidad de 32 bits sin signo", numerada desde 1 en el extremo bajo hasta 32 en el alto, y pide a los lectores conformes ignorar todo bit distinto de 3, 4, 5, 6, 9, 10, 11 y 12. ISO 32000-2 §7.6.4.2 corrige la Tabla 22 y detalla las posiciones reservadas: los bits 1–2 deben ser 0, los bits 7–8 deben ser 1 y, para la revisión 3 y posteriores, los bits 13–32 deben ser 1. PDF 2.0 además retira el bit 10, que los lectores ya ignoran y los escritores siempre fijan en 1 para lectores viejos; la excepción de accesibilidad se muda al bit 5, así que un lector debe actuar como si copiar estuviera permitido cuando el contenido va a tecnología de asistencia. Una fórmula cubre cada fila de abajo: la máscara del bit n es 1 shl (n - 1) — y TPdfPermission se mapea sobre la palabra de la misma manera
| Bit | Máscara | Significado (revisión 3 y posteriores) | TPdfPermission |
|---|---|---|---|
| 1–2 | $00000003 | Reservado, debe ser 0 | ninguno |
| 3 | $00000004 | Print, con una calidad que depende del bit 12 | permPrint |
| 4 | $00000008 | Modificar contenido por operaciones distintas a las de los bits 6, 9 y 11 | permModify |
| 5 | $00000010 | Copiar o extraer texto y gráficos | permCopy |
| 6 | $00000020 | Añadir o modificar anotaciones, llenar campos; con el bit 4, crear campos | permAnnotate |
| 7–8 | $000000C0 | Reservado, debe ser 1 | ninguno |
| 9 | $00000100 | Llenar campos de formulario existentes, incluidos los de firma, aun con el bit 6 limpio | permFillForms |
| 10 | $00000200 | Extracción de accesibilidad; sin uso en PDF 2.0, siempre escrito como 1 | permAccessibility |
| 11 | $00000400 | Assemble: insertar, rotar o borrar páginas, crear items de outline y miniaturas | permAssemble |
| 12 | $00000800 | Print a una representación fiel y de alta calidad | permPrintHighQuality |
| 13–32 | $FFFFF000 | Reservado, debe ser 1 (ISO/TS 32004 asigna el bit 13, ver abajo) | ninguno |
La máscara $FFFFFFF0 nace de leer "los bits bajos están reservados" y limpiar un nibble entero para irse a lo seguro. La región reservada era solo los bits 1–2, máscara $3; limpiar $F también se lleva por delante $4 y $8, que son print y modify, y cualquier visor que respete los flags rechazará ambos. El error espejo es armar la máscara hacia arriba desde cero: haga OR de $4, $10 y $800 y obtiene un /P positivo pequeño con los bits 7–8 y 13–32 limpios. La mayoría de los lectores abre ese archivo sin comentario, porque la especificación les dice que ignoren los bits reservados, pero el valor es no conforme para la revisión 3 y posteriores y un verificador estricto tiene derecho a quejarse. Una tercera variante aparece en código que cuenta bits desde cero, donde "bit 3" se vuelve $8 y el programa concede modify cuando quería print. Ninguna produce error al guardar: los permisos simplemente salen distintos de los que el autor tenía en mente
¿Es /P -4 el mismo valor que /P 4294967292?
Sí. Los mismos 32 bits, dos grafías. Los objetos enteros PDF van con signo, y la nota justo encima de la Tabla 22 apunta que, como los bits altos reservados son 1, el entero se escribe como un número negativo; -4 es el valor convencional de todo permitido para la revisión 3 y posteriores. Los productores que mantienen el valor en una variable sin signo escriben 4294967292 en cambio, y ambas grafías aparecen en archivos reales. La especificación dirime qué lectura es la correcta en su propia derivación de clave: el paso (d) del Algorithm 2 en ISO 32000-1 §7.6.3.3 convierte P a un número binario de 32 bits sin signo y pasa los cuatro bytes a MD5 con el byte bajo primero, así que para las revisiones 2 a 4 los valores -3904 y 4294963392 producen la misma file key. Un parser que guarda /P en un Integer de 32 bits con signo rechaza la grafía sin signo como overflow; cambie la variable a Cardinal y la que falla es la grafía negativa, mientras que un código que compara el texto del token contra un valor almacenado acaba tratando un documento como dos. Lea el token en un Int64, acepte cualquier cosa de Low(Integer) a High(Cardinal) y aplique máscara a 32 bits antes de cualquier comparación. El propio código de descifrado de PDFium Component lee /P así para las revisiones 2 a 7, y su escritor emite la forma con signo que muestra la nota de la especificación. El reporte de bits reservados del snippet se detiene en el bit 14, porque un bit 13 limpio es como ISO/TS 32004 marca un archivo que lleva un PDF MAC
uses
System.SysUtils;
// Normalice el texto del token /P desde su propio parser de diccionarios
function TryNormalizePermissions(const Token: string;
out Mask: Cardinal): Boolean;
var
Value: Int64;
begin
Result := TryStrToInt64(Token, Value) and
(Value >= Low(Integer)) and (Value <= High(Cardinal));
if Result then
Mask := Cardinal(Value and $FFFFFFFF);
end;
function DescribeReservedBits(Mask: Cardinal): string;
begin
Result := '';
if (Mask and $00000003) <> 0 then
Result := Result + 'bits 1-2 set; ';
if (Mask and $000000C0) <> $000000C0 then
Result := Result + 'bits 7-8 clear; ';
// El bit 13 ($1000) se deja fuera: ISO/TS 32004 lo limpia para exigir un PDF MAC
if (Mask and $FFFFE000) <> $FFFFE000 then
Result := Result + 'bits 14-32 not all set; ';
end;
// Esperado: ambas llamadas producen $FFFFFFFC y un reporte de bits reservados vacío
// TryNormalizePermissions('-4', M1);
// TryNormalizePermissions('4294967292', M2);
Construir una máscara desde un set TPdfPermissions
Arranque con todo bit reservado en su valor exigido y haga OR solo con lo que concede, de modo que un permiso olvidado solo pueda volverse una denegación. TPdfEncryptOptions.Default en la unidad FPdfEncrypt ya trae $FFFFFFFC, que lo permite todo, y limpiarle uno o dos bits funciona bien para una restricción rápida. Una política que lista lo que los receptores pueden hacer se lee mejor como un set, y el arreglo constante de abajo se alinea con las posiciones de bit que TPdf usa para decodificar la palabra. Fuerce el bit 10 sin importar lo que pase el llamador: ISO 32000-2 le dice a los escritores que siempre lo fijen, los lectores PDF 2.0 lo ignoran de todos modos, y dejarlo limpio solo arriesga bloquear lectores de pantalla en software viejo
uses
System.SysUtils, PDFium, FPdfEncrypt;
const
PermissionBit: array[TPdfPermission] of Cardinal = (
$0004, // permPrint bit 3
$0008, // permModify bit 4
$0010, // permCopy bit 5
$0020, // permAnnotate bit 6
$0100, // permFillForms bit 9
$0200, // permAccessibility bit 10
$0400, // permAssemble bit 11
$0800); // permPrintHighQuality bit 12
// Bits 7-8 y 13-32 activos; bits 1-2 y todos los bits de permisos limpios
ReservedOnes = Cardinal($FFFFF0C0);
function PermissionsToMask(Allowed: TPdfPermissions): Cardinal;
var
P: TPdfPermission;
begin
Result := ReservedOnes or PermissionBit[permAccessibility];
for P in Allowed do
Result := Result or PermissionBit[P];
end;
procedure SaveFillAndPrintCopy(const Source, Target: string;
const OwnerPassword: AnsiString);
var
Pdf: TPdf;
Opts: TPdfEncryptOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := Source;
Pdf.Active := True;
if not Pdf.Active then
raise Exception.CreateFmt('Could not open %s', [Source]);
Opts := TPdfEncryptOptions.Default; // Revision = erR5 aquí
Opts.UserPassword := ''; // cualquiera puede abrir el archivo
Opts.OwnerPassword := OwnerPassword; // protege el set de permisos
Opts.Revision := erR6;
Opts.Permissions := PermissionsToMask(
[permPrint, permPrintHighQuality, permFillForms]);
// Esperado: /P -1084 = 4294966212 ($FFFFFBC4)
Writeln('/P ', Integer(Opts.Permissions), ' = ', Opts.Permissions);
if not Pdf.SaveAsEncrypted(Target, Opts) then
raise Exception.Create('Encrypted save failed');
finally
Pdf.Free;
end;
end;
Esperado: el diccionario Encrypt de la salida lleva /P -1084, y un visor que respeta los flags ofrece imprimir a calidad completa y llenar formularios mientras deja en gris la edición de contenido, la copia, los comentarios y el ensamblado de páginas. El bit 13 es una posición que el escritor maneja por usted: con EnableIntegrityProtection en False, SaveAsEncrypted fija el bit 13 diga lo que diga su máscara; con ella en True, el escritor limpia el bit 13 tanto en /P como en /Perms. Los demás bits reservados siguen siendo cosa suya — de ahí el punto de partida $FFFFF0C0. El ejemplo elige erR6 porque R6 es la revisión AES-256 que ISO 32000-2 estandariza; el record por defecto trae erR5
/Perms, la copia cifrada de /P en archivos AES-256
En las revisiones 2 a 4 la palabra de permisos está soldada a la clave: cambie un bit de /P y la entrada de MD5 cambia, la clave derivada queda mala y nada se descifra. Los archivos AES-256 rompen ese vínculo. El Algorithm 2.A en ISO 32000-2 §7.6.4.3.3 desenvuelve la file encryption key desde /UE o /OE, y P no participa en esa derivación, así que por sí solo un /P en claro podría editarse libremente. /Perms cierra la brecha. El Algorithm 10 (§7.6.4.4.9) llena un bloque de 16 bytes y lo cifra con la file key usando AES-256 en modo ECB:
- bytes 0–3: la palabra de permisos, byte bajo primero, extendida a 64 bits por los bytes 4–7, que son todos
$FF - byte 8: ASCII
ToF, en correspondencia con/EncryptMetadata - bytes 9–11: el marcador ASCII
adb - bytes 12–15: datos aleatorios que los lectores ignoran
El paso (f) del Algorithm 2.A descifra el bloque, verifica adb y exige que los bytes 0–3 coincidan con P — shall, no should. El Algorithm 13 (§7.6.4.4.12) repite las verificaciones de marcador y permisos con el should más suave y añade una más: el byte 8 contra EncryptMetadata. PDFium Component lo lee de la manera estricta dondequiera que su propio código abre la capa de cifrado: descifrado AESV4, validación de PDF MAC y guardados incrementales en archivos cifrados. Después de que la contraseña de usuario o de propietario libera la file key, ese código compara los cuatro bytes de permisos contra el /P normalizado, revisa el relleno $FF, el byte T/F y el marcador, y hace fallar la carga con "PDF encryption permissions validation failed" ante cualquier diferencia, porque un desajuste significa que alguien editó el diccionario después del cifrado y ninguna de las dos copias puede tomarse al pie de la letra
La comparación de EncryptMetadata merece cuidado en cualquier lector que usted escriba. Un atajo como Pos('/EncryptMetadata false', Dict) > 0 se pierde un /EncryptMetadata false con dos espacios, un salto de línea entre clave y valor, o un comentario en el medio, todo sintaxis PDF legal. El lector asume entonces el defecto true, encuentra F en un /Perms perfectamente válido y rechaza el archivo. Un archivo válido, rechazado. Parsee la clave y su valor como tokens, y trate un valor no booleano como malformado en lugar de asignarle el defecto
ISO/TS 32004 le da un trabajo al bit 13: cuando está limpio, el documento lleva un PDF MAC que los lectores deben verificar. El token MAC vive en el diccionario /AuthCode del trailer, que no está cifrado, así que una verificación que solo busca /AuthCode concluye que un archivo con él borrado nunca estuvo protegido en integridad. Borrar también el /KDFSalt no esconde el requisito, porque el bit limpio vive en /P y el /P está autenticado por /Perms. Por eso el descifrado AESV4 de PDFium Component trata un bit 13 limpio, o un /KDFSalt sobreviviente, sin un /AuthCode como un MAC arrancado y falla, y cuando la protección de integridad está apagada el escritor fija el bit 13 para que su salida nunca anuncie un MAC que no tiene. La jerarquía de claves y el byte range detrás de ese token pertenecen al cifrado AES-GCM y el MAC ISO/TS 32004 en PDF 2.0, y el hash de contraseña iterado que libera la file key antes de que todo esto corra está cubierto en el hash de contraseña R6 y su conteo de rondas
¿Permissions o UserPermissions: cuál debe leer su UI?
Lea Permissions para decidir qué puede hacer la sesión actual, y UserPermissions para reportar lo que el archivo concede a un receptor ordinario. TPdf.Permissions envuelve el FPDF_GetDocPermissions de PDFium y describe el acceso que se concedió a este documento abierto, lo que depende de la contraseña que lo liberó. TPdf.UserPermissions envuelve FPDF_GetDocUserPermissions y sigue reportando la concesión de acceso de usuario del /P sin importar qué contraseña se usó, así que ambos pueden diferir cuando la contraseña de propietario abrió el archivo. Ambos son LongWord; GrantedPermissions y UserGrantedPermissions los decodifican en sets TPdfPermissions, y HasPermission y UserHasPermission prueban un solo miembro. SecurityHandlerRevision le dice si el archivo tiene un security handler. La rutina de abajo imprime ambas vistas enteras de la palabra cruda junto al set decodificado, una manera rápida de ver por qué un archivo "no imprime"
uses
System.SysUtils, System.TypInfo, PDFium;
procedure DumpPermissions(const FileName, Password: string);
var
Pdf: TPdf;
Mask: LongWord;
Granted: TPdfPermissions;
P: TPdfPermission;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Password := Password; // fijar antes de Active
Pdf.Active := True;
if not Pdf.Active then
raise Exception.CreateFmt('Could not open %s', [FileName]);
if Pdf.SecurityHandlerRevision <= 0 then
begin
Writeln('Not encrypted: no /P to decode');
Exit;
end;
Mask := Pdf.UserPermissions;
Granted := Pdf.UserGrantedPermissions;
Writeln(Format('R%d /P as Integer %d, as Cardinal %u ($%.8x)',
[Pdf.SecurityHandlerRevision, Integer(Mask), Mask, Mask]));
for P := Low(TPdfPermission) to High(TPdfPermission) do
Writeln(Format(' %-22s %s',
[GetEnumName(TypeInfo(TPdfPermission), Ord(P)),
BoolToStr(P in Granted, True)]));
if not Pdf.HasPermission(permCopy) then
Writeln(' this session may not copy text');
finally
Pdf.Free;
end;
end;
// Esperado para el archivo escrito antes:
// R6 /P as Integer -1084, as Cardinal 4294966212 ($FFFFFBC4)
// permPrint True, permModify False, permCopy False, ...
La aplicación de políticas pertenece a la aplicación anfitriona
PDFium renderiza, extrae texto y rasteriza páginas digan lo que digan los flags. El motor reporta permisos y deja la política al llamador. Quien tenga la contraseña de usuario tiene el contenido descifrado — con una contraseña de usuario vacía, eso es todo el mundo — así que la confidencialidad solo puede venir de una contraseña de usuario que el lector no tenga. Ponga la verificación dentro de cada manejador de comando (copiar, exportar, borrar páginas, imprimir) en lugar de esconder botones de la barra, porque los atajos de teclado, los menús contextuales y la automatización se saltan una barra, la misma estratificación que usa un panel de vista previa PDF seguro para apps Delphi. Un comando de impresión, por ejemplo, prueba permPrint antes de poner nada en la cola
En un archivo AES-256, un -1084 en texto plano no prueba nada hasta que los primeros cuatro bytes descifrados de /Perms lean C4 FB FF FF. Esa comparación byte a byte corre dentro del código AESV4, PDF MAC y de guardado incremental cifrado de PDFium Component