Criptografar um PDF de 2 GB parece um problema de streaming: abrir o ficheiro, passar dois gigabytes pelo AES-256, escrever o resultado. Esse modelo mental está errado de uma forma que decide todo o orçamento de desempenho. A ISO 32000-1 §7.6 define a granularidade da criptografia de PDF no objeto individual — cada fluxo e cada string são criptografados separadamente, cada um com seu próprio vetor de arranque e seu próprio preenchimento. Um ficheiro compactado de 2 GB com 500.000 objetos são 500.000 pequenas operações CBC, não uma longa passagem, e nessa escala o custo fixo em torno de cada operação importa mais do que a aritmética AES dentro dela
Este artigo é sobre esse custo fixo: para onde vai o tempo quando o código Delphi aplica o AES-256 em documentos muito grandes e como recuperá-lo. Para o lado da configuração — palavras-passe, sinalizadores de permissão, a chamada de compatibilidade da revisão 5 versus 6 — consulte o artigo complementar sobre configurar criptografia AES-256 no HotPDF; nada disso se repete aqui
Meio milhão de operações CBC, não uma passagem
O esqueleto do ficheiro permanece em texto simples. Tabelas de referência cruzada, números de objetos, chaves de dicionário, a árvore de páginas: nada disso é criptografado, e é assim que um leitor pode localizar objetos antes de validar uma palavra-passe. O que o padrão criptografa é o conteúdo — dados de fluxo, como descrições de página, imagens, fontes e anexos, além de strings como valores de metadados e texto de anotação. Sob o filtro de criptografia AES-256, cada um é processado por conta própria: um novo IV aleatório de 16 bytes, CBC sobre os bytes, preenchimento de bloco para um limite de 16 bytes, e o IV gravado de forma clara à frente do texto cifrado
Duas consequências se seguem. Primeiro, o texto cifrado é sempre mais longo que o texto simples: o IV adiciona 16 bytes e o preenchimento adiciona mais de 1 a 16, portanto, uma string de 100 bytes ocupa 128 bytes no disco e um fluxo vazio ainda produz 32. O código que dimensiona o buffer de saída para o comprimento da entrada ou reescreve apenas tantos bytes quanto leu produz ficheiros que falham na descriptografia no último bloco de cada objeto. Segundo, o custo acompanha a contagem de objetos, não apenas a contagem de bytes. Um ficheiro compactado concentra seus bytes em alguns grandes fluxos de imagem, mas carrega centenas de milhares de fluxos curtos e strings pequenas onde a sobrecarga por operação, não o AES, é a conta principal
A única misericórdia no design do AES-256 é o manuseio de chaves. Manipuladores de segurança até a revisão 4 derivavam uma chave distinta para cada objeto combinando a chave do ficheiro com os números do objeto e da geração, forçando um novo cronograma de chave a cada vez. Os esquemas /V 5 abandonaram a derivação por objeto: uma chave de ficheiro aleatória de 256 bits criptografa cada objeto no documento. Esse fato autoriza todas as otimizações abaixo — o caro estado criptográfico pode ser construído uma vez por ficheiro, não uma vez por objeto
O dicionário R6 /Encrypt: uma abertura lenta, objetos baratos
Um documento da revisão 6 declara seu esquema no dicionário /Encrypt do trailer, e as entradas que importam cabem em algumas linhas:
__CODE_BLOCK_/V 5 seleciona a arquitetura de chave de 256 bits e /R 6 o handshake fortalecido da ISO 32000-2. /CF define o filtro de criptografia nomeado — /AESV3 significa AES-256 no modo CBC com o IV prefixado — e /StmF e /StrF atribuem esse filtro a fluxos e strings, respectivamente. /O, /U, /OE e /UE contêm o material de verificação de palavra-passe e empacotamento de chave, e /Perms carrega uma cópia criptografada por AES dos bits de permissão para que um editor hostil não possa inverter /P silenciosamente
A estrutura de custo se esconde em /OE e /UE. Desempacotar a chave do ficheiro deles executa o Algoritmo 2.B, uma função de derivação de chave iterada encadeando rodadas SHA-256, SHA-384 e SHA-512 — pelo menos 64 delas, com uma regra de parada dependente de dados — construída deliberadamente lenta para que a adivinhação de palavras-passe continue cara. Esse preço é pago uma vez quando o gravador produz o ficheiro e uma vez quando um leitor o abre, milissegundos de um dígito cada. Em um ficheiro de meio milhão de objetos, o KDF é ruído, e se um salvamento for lento, o Algoritmo 2.B não é o suspeito; o loop por objeto é
Reutilize o handle da chave, reutilize o buffer de rascunho
A implementação ingênua é uma função utilitária organizada: um ajudante EncryptAes256Cbc que abre o provedor CNG do Windows, seleciona CBC, gera o objeto de chave, criptografa um buffer e destrói tudo. Correto, testável por unidade e desastroso dentro de um loop de 500.000 iterações. A documentação da Microsoft sinaliza BCryptOpenAlgorithmProvider como caro e recomenda fazer o cache do handle, e BCryptGenerateSymmetricKey executa o cronograma de chave AES completo e aloca o estado do provedor — puro desperdício quando a chave nunca muda em todo o documento
A RTL do Delphi não vem com nenhuma unidade de importação bcrypt, portanto, declare os pontos de entrada diretamente. A classe abaixo constrói todo o estado criptográfico uma vez e depois criptografa qualquer número de objetos sem alocação em estado estacionário:
__CODE_BLOCK_Três detalhes são fundamentais. A consulta de tamanho — a primeira chamada de BCryptEncrypt, com um buffer de saída nulo — retorna o comprimento do texto cifrado preenchido, nunca igual ao comprimento de entrada; o preenchimento é determinístico, então você pode computar ((Len div 16) + 1) * 16 sozinho e reduzir pela metade a contagem de chamadas, mas a consulta é o contrato documentado. Segundo, BCryptEncrypt avança o buffer IV no local enquanto é encadeado, de forma que uma cópia de trabalho vai para cada chamada e o IV original vai para a saída. Terceiro, FScratch apenas cresce, até o maior objeto do ficheiro, após o qual o loop não aloca nada
O que vale a pena reutilizar um handle, medido
O ficheiro que forçou este exercício foi um ficheiro de empréstimo digitalizado de 1,8 GB: 412.000 objetos criptografados carregando 1.710 MB de carga útil depois que a estrutura de texto simples é subtraída. Mesma máquina, mesmo ficheiro, armazenamento NVMe, uma thread:
- Configuração por chamada (provedor aberto e chave gerada dentro do auxiliar): fase de criptografia 71,3 s — 1.710 MB ÷ 71,3 s ≈ 24 MB/s
- Estado elevado (a classe acima): 9,6 s — 1.710 MB ÷ 9,6 s ≈ 178 MB/s
A diferença é de 61,7 s em 412.000 chamadas, ou cerca de 150 µs por chamada gasto abrindo um provedor, definindo um modo de encadeamento e reconstruindo um cronograma de chave para uma chave que nunca mudou. Nada disso era criptografia. Com o AES-NI, a criptografia CBC de grandes buffers roda perto de 1,4 GB/s em um núcleo, então a aritmética AES em si é responsável por cerca de 1,2 s dos 9,6; a maior parte do restante são as duas transições de modo de utilizador BCryptEncrypt por objeto mais a geração de IV por objeto. O processamento em lote dos IVs — uma chamada de BCryptGenRandom preenchendo 4.096 deles — reduziu a execução para 8,9 s. Passado isso, você está no limite por objeto da API, e a alavanca restante é o paralelismo: os objetos /V 5 são independentes sob a chave de ficheiro compartilhada, portanto, quatro threads de trabalho com um objeto de chave cada levaram a fase para 3,1 s antes que o gravador de saída se tornasse o ponto de serialização
Reescrita completa versus salvamento incremental
A granularidade também decide quanto custa um salvamento. Adicionar criptografia a um documento de texto simples existente reescreve cada objeto por definição: cada fluxo e string altera o conteúdo e o comprimento, cada deslocamento de referência cruzada se move e não existe um caminho incremental. Planeje isso como uma reescrita sequencial completa e grave em um ficheiro temporário que seja renomeado sobre o alvo, porque, de outra forma, uma falha na metade da criptografia deixa um ficheiro semi-cifrado que nenhuma palavra-passe abrirá
A direção oposta é a barata. Depois que um ficheiro é criptografado, uma atualização incremental anexa novos objetos criptografados com a mesma chave de ficheiro e deixa cada byte original intocado. Carimbar uma anotação de aprovação em um ficheiro criptografado de 2 GB custa kilobytes de saída anexada, não uma reescrita de 2 GB. O corolário do pipeline: criptografe uma vez, como o último passo do trabalho, e deixe que os toques subsequentes aproveitem salvamentos incrementais. Uma rotação de palavra-passe que também rotaciona a chave do ficheiro é uma reescrita completa novamente — programe-a como tal
Medindo o rendimento sem se enganar
As alegações de rendimento de criptografia tendem a estar erradas no numerador, no denominador ou em ambos. O numerador deve ser os bytes de carga útil: a soma dos comprimentos de fluxo e string efetivamente passados pelo AES, após a compactação, que o gravador pode totalizar à medida que avança. O tamanho do ficheiro superestima — o ficheiro compactado acima tem 1,8 GB em disco, mas apenas 1.710 MB dele tocam a cifra. O denominador deve ser apenas a fase de criptografia, delimitada com TStopwatch de System.Diagnostics, com a análise sintática, deflate e I/O de disco fora dos delimitadores. Se você incluir isso, o mesmo código de criptografia será medido várias vezes mais lento em um ficheiro que simplesmente compacta de maneira pior. Os números acima são comparáveis precisamente porque ambos os lados da divisão são apenas criptografia
Nada disso precisa ser código de sua propriedade. O HotPDF envolve a mesma engenharia em propriedades do componente — ActivateProtection, CryptKeyLength, UseAES256R6 — na altitude certa para aplicações VCL interativas, com as armadilhas da ordem de atribuição abordadas no artigo AES-256 do HotPDF. Para pipelines não supervisionados, o PDFlibPas aplica a revisão 6 do AES-256 a ficheiros existentes em uma única chamada EncryptFile na Força 4 e verifica depois o que parou no disco, um fluxo de trabalho detalhado no artigo de auditoria de criptografia PDFlibPas
Os caminhos de criptografia descritos aqui acompanham o HotPDF Component para Delphi e C++Builder e na biblioteca PDFlibPas; ambas as páginas de produtos trazem a referência completa de criptografia