Criptografar um PDF e restringir permissões
Visão geral
Seção intitulada “Visão geral”Esta receita criptografa um documento com o handler de segurança padrão do Advanced Encryption Standard (AES)-256. Você define uma senha de usuário (necessária para abrir), uma senha de proprietário (acesso total) e uma bitmask de permissões que restringe operações. Trate essas permissões como dependentes da cooperação do leitor: a criptografia fornece confidencialidade, não integridade, e apenas softwares cooperativos respeitam os bits de permissão. A receita segue examples/22-protection.php.
Fronteira de confiança (considere isto em toda afirmação sobre permissões). A criptografia de PDF protege a confidencialidade do conteúdo contra partes sem a senha (ISO 32000-2 §7.6). Ela não protege a integridade: não detecta nem impede modificação. A entrada de permissão
Pé um conjunto de flags de 32 bits sem sinal que solicita aos leitores compatíveis que respeitem as restrições; ela não é um controle de acesso. Uma ferramenta não compatível, ou qualquer ferramenta usada com a senha de proprietário, pode realizar todas as operações “negadas”. Não descreva um PDF criptografado como “seguro”, “à prova de adulteração” ou “protegido contra cópia”.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3Habilite a extensão PHP openssl. O criptografador AES-256 a usa para a cifra e a derivação de chave.
Visão geral conceitual
Seção intitulada “Visão geral conceitual”Os códigos V/R do dicionário de criptografia selecionam o handler de segurança padrão (ISO 32000-2 §7.6). O Aes256Encryptor do NextPDF implementa o crypt filter AESV3 na revisão 6 do handler de segurança (V=5/R=6). Ele usa uma chave aleatória de criptografia de arquivo de 256 bits, derivação de chave por hash iterativo com salt (Algoritmo 2.B) e criptografia AES-256-CBC por objeto com um vetor de inicialização aleatório. O Cipher Block Chaining (CBC) é um modo de confidencialidade (NIST SP 800-38A). Seus vetores de inicialização devem ser imprevisíveis.
O vetor de inicialização é renovado para cada objeto e a cada execução; por isso, os bytes brutos diferem de uma execução para outra. O perfil de reprodutibilidade é, portanto, structural. Antes de comparar duas execuções, o harness canonicaliza o IV de criptografia, a ordem dos objetos e o /ID do trailer. Esse perfil é mais estrito do que o perfil de uma receita que omite a criptografia.
A bitmask de permissões define a entrada P. O bit 3 concede impressão, e o bit 6 concede annotation/form-fill. O valor é a quantidade documentada de 32 bits sem sinal.
Superfície da API
Seção intitulada “Superfície da API”NextPDF\Core\Concerns\HasSecurity (incluído em Document):
setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static— configura a criptografia AES-256 do handler padrão.permissions = -1concede todas as permissões. QuandoownerPasswordestá vazia, a senha de usuário é reutilizada como senha de proprietário. Chame antes deaddPage().getEncryptor(): ?Aes256Encryptor— retorna o criptografador configurado, ounull.useAesGcm(?bool $enabled = true): static— opta pelo AES-256-GCM da ISO/TS 32003; lança uma exceção se o OpenSSL/libsodium do host não disponibilizar a cifra.
Ambos os parâmetros de senha são marcados com #[SensitiveParameter], então o PHP os oculta dos stack traces.
Bits de permissão (a entrada P, bits baixos 3–6 de uso comum):
| Bit | Valor | Operação |
|---|---|---|
| 3 | 4 | Imprimir o documento |
| 4 | 8 | Modificar o conteúdo do documento |
| 5 | 16 | Copiar / extrair texto e gráficos |
| 6 | 32 | Adicionar ou modificar anotações e preencher campos de formulário |
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->setTitle('Confidential Memo');
// Grant printing only (bit 3 = 4). MUST run before addPage().$doc->setEncryption( userPassword: 'open-me', ownerPassword: 'owner-secret', permissions: 4,);
$doc->addPage();$doc->setFont('helvetica', '', 12);$doc->cell(0, 10, 'Encrypted with AES-256; printing allowed only.', newLine: true);
$doc->save(__DIR__ . '/confidential.pdf');echo "Wrote confidential.pdf\n";Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”O exemplo completo abaixo espelha examples/22-protection.php e grava em NEXTPDF_COOKBOOK_OUTPUT para o harness.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$userPassword = 'demo';$ownerPassword = 'admin';
// Grant ONLY printing (bit 3 = 4); deny copy/modify/annotate.$permissions = 4;
$doc = Document::createStandalone();$doc->setTitle('Encrypted Document — Restricted Permissions');$doc->setAuthor('NextPDF Example');
// setEncryption() MUST be called before addPage().$doc->setEncryption( userPassword: $userPassword, ownerPassword: $ownerPassword, permissions: $permissions,);
$doc->addPage();$doc->setFont('helvetica', 'B', 20);$doc->cell(0, 14, 'Encrypted PDF Document', newLine: true);$doc->ln(8);
$doc->setFont('helvetica', '', 11);$doc->multiCell(0, 7, 'This document is protected with AES-256 encryption ' . '(standard security handler, revision 6). The user password is required ' . 'to open it; the owner password grants full access. The permission ' . 'bits below are honoured by conforming readers only.');$doc->ln(5);
$permissionTable = [ ['Bit 3 (4)', 'Printing', 'ALLOWED'], ['Bit 4 (8)', 'Content modification', 'DENIED'], ['Bit 5 (16)', 'Text copying / extraction', 'DENIED'], ['Bit 6 (32)', 'Annotations / form fields', 'DENIED'],];$doc->setFont('helvetica', 'B', 10);$doc->cell(30, 7, 'Flag');$doc->cell(60, 7, 'Operation');$doc->cell(0, 7, 'Status', newLine: true);foreach ($permissionTable as [$bit, $operation, $status]) { $doc->setFont('courier', '', 9); $doc->cell(30, 7, $bit); $doc->setFont('helvetica', '', 10); $doc->cell(60, 7, $operation); $doc->setFont('helvetica', 'B', 10); $doc->cell(0, 7, $status, newLine: true);}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$doc->save($out !== false ? $out : __DIR__ . '/encrypted.pdf');
echo "Wrote encrypted PDF (AES-256, printing only)\n";Saída esperada:
Wrote encrypted PDF (AES-256, printing only)Quando você abre o arquivo, o leitor solicita uma senha. A senha de usuário abre o arquivo com o conjunto restrito de permissões. A senha de proprietário abre o arquivo com acesso total.
Casos extremos & pegadinhas
Seção intitulada “Casos extremos & pegadinhas”- Ordem de chamada.
setEncryption()depois deaddPage()não criptografa retroativamente o conteúdo anterior. Configure a criptografia primeiro; o motor criptografa o corpo de cada objeto conforme ele é escrito. - Padrão da senha de proprietário. Uma senha de proprietário vazia faz o motor reutilizar a senha de usuário como senha de proprietário. Na prática, isso elimina qualquer papel privilegiado. Defina senhas distintas quando os dois papéis precisarem ser diferentes.
- A semântica das permissões é consultiva. Apenas leitores compatíveis respeitam os bits. Eles não são impostos criptograficamente: uma ferramenta não compatível, ou qualquer ferramenta usada com a senha de proprietário, pode realizar operações restritas. Trate as permissões como um sinal de política para softwares cooperativos, nunca como um controle de acesso capaz de resistir a uma parte determinada.
- Nenhuma garantia de integridade. A criptografia oferece confidencialidade, não integridade. Um atacante sem a senha não consegue ler o conteúdo, mas o próprio formato não detecta adulteração. A proteção de integridade requer um mecanismo separado, como uma assinatura digital ou o document MAC da ISO/TS 32004.
- Conflito com PDF/A. O PDF/A proíbe a chave de trailer
Encrypt. ChamarsetEncryption()em um documento PDF/A, em qualquer ordem, lança uma exceção de incompatibilidade. - Opt-in do AES-256-GCM.
useAesGcm()seleciona a criptografia em massa GCM da ISO/TS 32003 quando o OpenSSL ou o libsodium do host a fornece. Caso contrário, lançaInvalidConfigException. Ela é incompatível com o PDF/A pelo mesmo motivo. - A criptografia de chave pública ainda não está conectada.
setPublicKeyEncryption()congela a superfície da API, massave()lança uma exceção até que a conexão do writer seja entregue (um defeito conhecido). Não a use em produção no Core.
Desempenho
Seção intitulada “Desempenho”A derivação de chave executa o hash iterado do Algoritmo 2.B uma vez por documento. O AES-256-CBC por objeto é linear em relação ao tamanho do corpo do objeto. Para documentos típicos, o custo se mantém bem dentro do orçamento de 1500 ms / 64 MB. Documentos muito grandes incorrem em um custo de throughput de AES por objeto. O Galois/Counter Mode (GCM) com AES-NI é mais rápido em hosts compatíveis.
Notas de segurança
Seção intitulada “Notas de segurança”- Apenas confidencialidade. Reiterando a fronteira de confiança: a criptografia mantém o conteúdo inacessível para partes sem a senha. Ela não prova que o arquivo está inalterado, e os bits de permissão dependem da cooperação do leitor.
- A força da senha é sua responsabilidade. O handler é tão forte quanto as senhas. Depois que alguém obtém o arquivo, uma senha de usuário fraca pode ser quebrada por força bruta offline; o formato não consegue limitar a taxa de tentativas.
- A senha de proprietário é uma chave primária. Qualquer pessoa com a senha de proprietário contorna todas as restrições. Trate-a como uma credencial root; nunca a entregue junto com o documento nem a registre em log.
#[SensitiveParameter]é defesa em profundidade. Ele oculta as senhas dos stack traces do PHP, mas você ainda precisa mantê-las fora dos seus próprios logs, mensagens de exceção e relatórios de falha.
Residência de dados & mitigações de PII
Seção intitulada “Residência de dados & mitigações de PII”A biblioteca realiza a criptografia em processo. Ela não transmite o documento nem as senhas para lugar algum. O motor não grava nenhuma senha, chave ou byte do documento em disco, exceto a saída criptografada que você salva. Onde o arquivo de saída reside, e como as senhas são guardadas, são preocupações de implantação que cabem ao integrador. A biblioteca não oferece nenhuma garantia de residência. Se o documento em texto claro contiver dados pessoais, esses dados estarão protegidos apenas até o limite imposto pela senha mais fraca e pela ressalva acima sobre leitores cooperativos. A criptografia não substitui a minimização das informações de identificação pessoal (PII) que você coloca no documento.
Telemetria segura & limpeza de logs
Seção intitulada “Telemetria segura & limpeza de logs”A criptografia emite um EncryptionAppliedEvent que carrega apenas o nome do algoritmo (AES-256) e três booleanos que resumem se print/copy/modify são permitidos — nenhuma senha, chave, salt ou IV é colocada no evento em nenhum momento (src/Event/Security/EncryptionAppliedEvent.php). O caminho do OpenTelemetry roteia os atributos de span por um sanitizador de allowlist (src/Telemetry/AttributeSanitizer.php) que rejeita incondicionalmente senhas e caminhos de arquivo; apenas chaves da allowlist com valores escalares sobrevivem. Não adicione material de senha ou chave a spans, logs ou mensagens de exceção no seu próprio código de integração. Os marcadores #[SensitiveParameter] protegem os stack traces, mas não as strings que você mesmo monta.
Modelo de ameaças
Seção intitulada “Modelo de ameaças”Dentro do escopo: um adversário que obtém o arquivo criptografado, mas não as senhas. Ele não consegue ler o conteúdo, sujeito à força da senha, e o arquivo não vaza texto claro. Fora do escopo: um adversário que tenha a senha de usuário ou de proprietário; um leitor não compatível que ignore os bits de permissão; força bruta offline de uma senha fraca; detecção de adulteração (a criptografia fornece confidencialidade, não integridade); canais laterais na build do OpenSSL do host; e a custódia de chaves, que é inteiramente responsabilidade do integrador. Documentar essas ameaças não afirma a ausência de vulnerabilidades.
Comportamento no modo FIPS
Seção intitulada “Comportamento no modo FIPS”A build do OpenSSL do host fornece as primitivas criptográficas, então a postura FIPS é uma propriedade do host, não uma configuração da biblioteca. CryptoCapabilities::detectFipsMode() retorna um FipsModeDetection de três estados (src/Security/FipsModeDetection.php): FIPS_ACTIVE, FIPS_ABSENT, ou INDETERMINATE. A extensão openssl do PHP não expõe nenhum binding para o modelo de provider do OpenSSL 3, então a sondagem é feita em regime de melhor esforço. INDETERMINATE é tratado como “FIPS não comprovado” (fail-closed), distinguível em telemetria acionável pelo operador. O NextPDF não reivindica validação FIPS 140; executar em um OpenSSL validado por FIPS é responsabilidade do operador, e o resultado da detecção é consultivo.
Conformidade
Seção intitulada “Conformidade”| Declaração | Spec | Cláusula | reference_id |
|---|---|---|---|
O código V do dicionário de criptografia seleciona o algoritmo de criptografia. | ISO 32000-2 | §7.6 | |
O método de crypt filter AESV3 é nomeado pela entrada CFM. | ISO 32000-2 | §7.6 | |
A entrada P é uma quantidade de permissão de acesso de 32 bits sem sinal. | ISO 32000-2 | §7.6 | |
| O bit de permissão 3 controla a impressão. | ISO 32000-2 | §7.6 | |
| O bit de permissão 6 controla anotação / preenchimento de formulário. | ISO 32000-2 | §7.6 | |
| A criptografia protege o conteúdo contra acesso não autorizado (confidencialidade). | ISO 32000-2 | §7.6 | |
| A derivação de chave da revisão 6 usa hashing iterativo com salt (Algoritmo 2.B). | ISO 32000-2 | §7.6 | |
| O CBC é um modo de confidencialidade (não um modo de integridade). | NIST SP 800-38A | §6.2 | |
| Os vetores de inicialização do CBC devem ser imprevisíveis. | NIST SP 800-38A | Ap. C |
O NextPDF implementa as cláusulas citadas. Ele não afirma conformidade abrangente com a ISO 32000-2, validação FIPS 140, nem qualquer garantia legal ou contratual de confidencialidade. “Suporte ao handler de segurança padrão” não é uma certificação de segurança na sua implantação. Isso depende da custódia de senhas e da política do verificador, fora do escopo da biblioteca.