Uma flag de permissão não constitui um mecanismo de segurança. O bit que indica "cópia proibida" reside no mesmo dicionário /Encrypt da criptografia, o que lhe confere uma aparente capacidade de execução que na realidade não possui; no momento em que trata ambos como o mesmo elemento, a sua auditoria começa a produzir respostas erradas. A única questão que vale a pena colocar relativamente a um PDF não é se este "está encriptado", mas sim perguntas mais específicas e complexas: qual o algoritmo, qual a revisão do processador de segurança, qual das duas palavras-passe foi configurada, quais os bits de permissão declarados e que partes do ficheiro são efetivamente afetadas pela encriptação. Um ficheiro pode estar formalmente encriptado e, na prática, aberto. Pode recusar a leitura mas manter os metadados em texto limpo. Pode bloquear a impressão através de uma flag que qualquer visualizador é livre de ignorar. Auditar um PDF significa analisar todos estes aspetos separadamente, e o PDFlibPas, o motor PDF da losLab para Delphi e C++Builder com código-fonte disponível, expõe cada um deles tanto através de uma API plana de handles inteiros como de uma camada de classes tipada.
O que o dicionário /Encrypt realmente regista
A norma ISO 32000-1 §7.6 define a segurança dos documentos através de um conjunto de entradas de dicionário, e o PDFlibPas mapeia-as uma a uma no registo TPDFEncryption. A versão do filtro V e a revisão R selecionam a família de algoritmos. O campo Length define o tamanho da chave. Os bits de permissão residem em P, as strings de validação das palavras-passe do proprietário (owner) e do utilizador (user) situam-se em O e U (com OE e UE adicionados para AES-256), existindo ainda uma flag EncryptMetadata acompanhante, além de mais três campos que especificam os filtros criptográficos aplicados a strings, streams e ficheiros incorporados, respetivamente.
O valor deste registo está no facto de não interpretar nada por si. Devolve o dicionário em bruto e deixa que retire as conclusões necessárias, que é precisamente o que uma auditoria requer. O caso de texto limpo em documento encriptado manifesta-se em StringFilterIdentity e StreamFilterIdentity: se algum deles for verdadeiro, os dados correspondentes passam pelo filtro Identity inalterados, independentemente do estado de encriptação reportado pelo documento. Um analisador que pare na verificação de "presença de um dicionário /Encrypt" considerará esse ficheiro protegido quando as suas strings e streams estão em texto limpo. A mesma nuance aplica-se aos metadados. Se EncryptMetadata for falso, o pacote XMP mantém-se legível para qualquer indexador, embora o conteúdo da página não o esteja, o que é importante detetar se as suas regras de encaminhamento dependerem dos campos de título ou autor.
A short security probe with the flat API
Para a maioria dos fluxos, quatro chamadas planas respondem às perguntas frequentes. O método LoadFromFile devolve 1 em caso de sucesso, e, logo que o documento esteja aberto, os inspetores de encriptação reportam sobre o seu estado desencriptado:
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;
O método CheckPassword assume maior importância do que a sua assinatura de uma única linha sugere. O formato PDF define duas palavras-passe com privilégios desiguais. A palavra-passe do utilizador (user) é obrigatória para abrir o ficheiro. A palavra-passe do proprietário (owner) concede plenos direitos e sobrepõe-se a qualquer bit de permissão. Os bytes no disco são idênticos em ambos os casos, mas uma sessão aberta com a palavra-passe do proprietário permite realizar operações que a do utilizador não autoriza; por isso, uma auditoria que não registe qual a credencial apresentada regista apenas metade da verdade. A camada de classes permite consultar esta distinção. As propriedades TPDFDocument.HasUserPassword e HasOwnerPassword indicam o que o ficheiro exige, enquanto IsUserPassword e IsOwnerPassword indicam qual a palavra-passe que abriu a sessão atual. Registe esse facto. Nunca registe os valores das palavras-passe.
The Strength ladder, where "AES-256" means two things
As funções planas Encrypt e EncryptFile aceitam um inteiro Strength com cinco valores válidos: 0 para RC4 de 40 bits, 1 para RC4 de 128 bits, 2 para AES de 128 bits compatível com Acrobat 7, 3 para AES de 256 bits introduzido com o Acrobat 9, e 4 para AES de 256 bits exigido pelo Acrobat X e posterior.
O aspeto interessante é que tanto o nível 3 como o 4 são rotulados como AES-256, mas não utilizam o mesmo esquema. A força 3 corresponde à revisão 5 do processador de segurança, um design provisório que o Acrobat 9 integrou e a ISO nunca adotou. A força 4 corresponde à revisão 6, cuja função de derivação de chaves foi reforçada e normalizada na norma ISO 32000-2. Para um documento gerado hoje, não há justificação para optar pela versão 3 em detrimento da 4. Numa auditoria, esta diferença é decisiva: uma política que exija "AES-256 em conformidade com ISO 32000-2" só é satisfeita pela revisão R6, e um ficheiro R5 que se auto-intitule AES-256 falhará essa verificação, apesar de passar numa validação de força simplista. A camada de classes separa-as por nome: esAES256Bit para R5 e esAES256BitAcroX para R6, e a propriedade EncryptionAcroX responde à questão da revisão com um único booleano.
Permission bits and their key-length fine print
O método EncodePermissions agrupa oito sinalizações (flags) no valor inteiro que Encrypt e EncryptFile esperam. Impressão, cópia, alteração e adição de notas constituem o conjunto básico; preenchimento de campos, cópia para acessibilidade, montagem e impressão de alta qualidade compõem o conjunto expandido. A regra detalhada, declarada na própria demonstração de encriptação da biblioteca, é que as quatro opções do conjunto expandido apenas produzem efeito com chaves de força igual ou superior a 128 bits. A flag de impressão de alta qualidade segue a mesma norma: se a desmarcar para forçar a impressão de baixa resolução, um documento de 40 bits ignorará a restrição, uma vez que esta limitação também exige encriptação de 128 bits ou superior. Se codificar uma política de "apenas impressão de baixa resolução" num ficheiro de 40 bits, qualquer visualizador imprimirá em alta qualidade de qualquer forma.
A questão de fundo consiste em saber quem impõe o respeito por estes bits, e a resposta é: ninguém em quem possa confiar. As permissões são instruções direcionadas a leitores em conformidade e não restrições criptográficas. A chave de desencriptação é idêntica independentemente de a cópia ser autorizada ou negada, pelo que um conjunto restritivo de permissões apenas serve para manter os utilizadores bem-intencionados sob controlo. Um leitor que opte por ignorar as flags de permissão não enfrenta qualquer barreira criptográfica. Se o requisito for impedir a extração de dados e não apenas desincentivá-la, o ficheiro necessita de uma palavra-passe de utilizador e o fluxo de trabalho requer controlos ao nível do processo; além disso, um relatório de auditoria deve designar qual o regime em vigor em cada ficheiro, em vez de tratar uma flag de permissão como um cadeado.
Setting policy and proving it stuck
A aplicação de encriptação a ficheiros existentes não obriga a carregá-los na árvore de objetos. O método EncryptFile processa a entrada e gera a saída numa única chamada, e o ciclo de auditoria reabre o resultado para verificar o que foi gravado no disco. A demonstração de encriptação fornecida adota esta estrutura de gravação seguida de leitura de validação:
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;
As equipas que trabalham ao nível do documento realizam a mesma operação utilizando conjuntos tipados (typed sets) em vez de empacotamento de bits, o que simplifica significativamente a revisão de código:
if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
[ppCanPrint], [ppCanPrintFull]) then
raise Exception.Create('Encryption failed');
Seja qual for a abordagem, a validação de leitura não é uma mera formalidade. Deteta erros de implementação que de outro modo só se manifestariam meses mais tarde no sistema de um cliente: uma compilação antiga da biblioteca que reduz silenciosamente a força solicitada, um caminho de saída que não chegou a ser escrito por o diretório ser de leitura ou um inteiro de permissões cujos argumentos foram passados na ordem errada. Os três casos passam num teste básico local e falham em produção, mas a reabertura do ficheiro gerado converte cada uma destas situações numa exceção visível na execução em que ocorreu a gravação. O método GetEncryptionFingerprint devolve um valor resumido que pode arquivar junto do registo da tarefa, facilitando comparações futuras para saber se duas saídas partilham da mesma configuração de encriptação sem abrir nenhuma delas.
Audit false positives worth coding for
Alguns padrões levam frequentemente os scanners de segurança a conclusões erradas, resultando sempre da simplificação de uma questão complexa numa resposta sim/não. O filtro criptográfico Identity é o melhor exemplo. O dicionário /Encrypt está presente, o ficheiro é reportado como encriptado e, contudo, as strings e streams passam pelo filtro Identity inalteradas, pelo que o conteúdo real permanece em texto limpo. A solução passa por ler as propriedades StringFilterIdentity e StreamFilterIdentity antes de declarar o ficheiro como protegido.
A divisão de metadados é mais subtil. A flag EncryptMetadata pode discordar do resto do documento em ambos os sentidos, resultando num ficheiro encriptado com um pacote XMP legível ou, menos frequentemente, no inverso. Afirmar que "o ficheiro está encriptado" nada diz sobre a encriptação dos seus metadados, o que se torna relevante quando um indexador ou uma regra de encaminhamento procura o título. Os ficheiros incorporados acrescentam uma terceira vertente: o formato PDF autoriza um filtro de encriptação dedicado apenas para anexos, o que permite que estes sejam a única parte encriptada num documento aberto, ou a única parte em texto limpo num documento encriptado. Registe as três atribuições de filtros como campos separados para strings, streams e anexos e evitará todas estas armadilhas. Se guardar apenas um valor booleano, será apenas uma questão de tempo até ocorrer um erro.
Removing encryption, and choosing it for new files
Uma auditoria culmina frequentemente na decisão de remover a proteção e o obstáculo não reside na mecânica desse processo. O método DecryptFile(InputFileName, OutputFileName, Password) grava uma cópia desencriptada sem necessidade de um carregamento completo, e a função Decrypt do documento carregado realiza a mesma operação na memória quando o ficheiro já está aberto. Ambas exigem uma palavra-passe válida; nenhuma delas contorna as barreiras criptográficas. A verdadeira validação assenta na política definida e não no código; por isso, certifique-se de que as suas regras de admissão estabelecem explicitamente quando a remoção é autorizada e registe a categoria da palavra-passe que a permitiu, uma vez que a etapa técnica em si não deixa qualquer rasto.
A escolha para os novos ficheiros de saída é mais restrita do que os novos valores de Strength sugerem. Utilize a força 4, AES-256 revisão 6, a menos que necessite de abrir ficheiros em leitores anteriores ao Acrobat X. A força 2, AES-128, representa o limite mínimo prático para uma infraestrutura de visualizadores antigos que não possa ser atualizada. As opções RC4 em 0 e 1 existem apenas para permitir a leitura e auditoria de arquivos históricos, e não para gerar novos documentos; recorrer a elas num desenvolvimento recente indica que os requisitos do projeto estão desatualizados.
O estado da encriptação afeta diretamente as decisões de assinatura, visto que um painel de trabalho que valide e assine documentos exige a mesma disciplina de validação de leitura em que esta auditoria assenta. Este tema é abordado no artigo sobre o painel de conformidade e assinatura. Quando necessitar de aplicar EncryptFile a milhares de documentos volumosos, o guia de acesso direto a PDFs grandes demonstra como manter o consumo de memória estável durante o processamento. A referência completa da API de encriptação está disponível na página do produto PDFlibPas.