Chiffrement avancé
Cette page documente l'implémentation interne du chiffrement dans TCPDF-Next Pro. Elle couvre le gestionnaire AES-256 AESV3, l'algorithme de dérivation de clés, la normalisation des mots de passe et la gestion sécurisée des paramètres. Si vous recherchez l'utilisation basique du chiffrement, consultez l'exemple de chiffrement AES-256.
AES-256 avec gestionnaire AESV3
TCPDF-Next Pro implémente le ISO 32000-2 (PDF 2.0) Standard Security Handler révision 6, qui impose AES-256-CBC pour tout chiffrement de flux et de chaîne. Le gestionnaire est identifié par /V 5 et /R 6 dans le dictionnaire de chiffrement.
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
$encryptor = new Aes256Encryptor(
ownerPassword: 'Str0ng!OwnerP@ss',
userPassword: 'reader2026',
);Pourquoi pas RC4 ou AES-128
TCPDF-Next Pro exclut délibérément RC4 (40-bit et 128-bit) et AES-128 :
| Algorithme | Raison de l'exclusion |
|---|---|
| RC4-40 | Cassé depuis 1995 ; attaqué trivialement |
| RC4-128 | Biais dans le flux de clés ; interdit par PDF 2.0 |
| AES-128 | Remplacé par AES-256 en révision 6 ; non compatible futur |
PDF 2.0 (ISO 32000-2:2020) exige AESV3 pour les nouveaux documents. Supporter des algorithmes plus faibles compromettrait la posture de sécurité et violerait la spécification.
Algorithme 2.B : Dérivation de clés
La clé de chiffrement de fichier est dérivée du mot de passe en utilisant l'Algorithme 2.B (ISO 32000-2, clause 7.6.4.3.4). C'est un processus itératif qui enchaîne SHA-256, SHA-384 et SHA-512 :
function computeHash(password, salt, userKey = ''):
K = SHA-256(password || salt || userKey)
round = 0
lastByte = 0
while round < 64 OR lastByte > round - 32:
K1 = (password || K || userKey) repeated 64 times
E = AES-128-CBC(key = K[0..15], iv = K[16..31], data = K1)
mod3 = (sum of all bytes in E) mod 3
if mod3 == 0: K = SHA-256(E)
elif mod3 == 1: K = SHA-384(E)
else: K = SHA-512(E)
lastByte = E[len(E) - 1]
round += 1
return K[0..31] // 32-byte file encryption keyCe hachage itératif rend les attaques par force brute coûteuses en calcul tout en restant suffisamment rapide pour une utilisation légitime.
Composants clés dans le dictionnaire de chiffrement
| Entrée | Longueur | Objectif |
|---|---|---|
/O | 48 bytes | Validation du mot de passe propriétaire (hash + sel de validation) |
/U | 48 bytes | Validation du mot de passe utilisateur (hash + sel de validation) |
/OE | 32 bytes | Clé de chiffrement de fichier chiffrée par le propriétaire |
/UE | 32 bytes | Clé de chiffrement de fichier chiffrée par l'utilisateur |
/Perms | 16 bytes | Indicateurs de permission chiffrés AES-256 |
Normalisation des mots de passe SASLprep
Avant toute opération cryptographique, les mots de passe sont normalisés en utilisant SASLprep (RFC 4013), qui est un profil de stringprep (RFC 3454). Cela garantit une gestion cohérente des mots de passe quelle que soit la forme de normalisation Unicode utilisée par le système d'exploitation ou la méthode d'entrée.
use Yeeefang\TcpdfNext\Pro\Security\SaslPrep;
$normalized = SaslPrep::prepare('P\u{00E4}ssw\u{00F6}rd');
// Normalise les formes composées/décomposées, mappe certains caractères,
// et rejette les points de code interdits.Ce que fait SASLprep
- Map -- Les caractères couramment mappés à rien (ex : trait d'union conditionnel
U+00AD) sont supprimés. - Normalize -- La chaîne est convertie en Unicode NFC (Décomposition Canonique suivie de Composition Canonique).
- Prohibit -- Les caractères des Tables C.1.2 à C.9 de RFC 3454 sont rejetés (caractères de contrôle, usage privé, substituts, non-caractères, etc.).
- Bidirectional check -- Les chaînes avec caractères gauche-droite et droite-gauche sont validées selon RFC 3454 clause 6.
Cela signifie qu'un utilisateur tapant U+00FC (LETTRE MINUSCULE LATINE U AVEC TRÉMA) et un autre tapant U+0075 U+0308 (LETTRE MINUSCULE LATINE U + TRÉMA COMBINANT) produiront la même clé de chiffrement.
Encodage des permissions
Les permissions sont stockées dans l'entrée /Perms sous forme de bloc chiffré AES-256-ECB de 16 bytes. La disposition en clair est :
Bytes 0-3: Indicateurs de permission (int32 little-endian)
Bytes 4-7: 0xFFFFFFFF
Byte 8: 'T' si EncryptMetadata est true, 'F' sinon
Bytes 9-11: 'adb'
Bytes 12-15: Remplissage aléatoireLes indicateurs de permission suivent la même disposition de bits que définie dans ISO 32000-2 Table 22.
Gestion des flux chiffrés
Chaque flux de contenu et chaîne dans le PDF est chiffré individuellement :
- Un unique Vecteur d'initialisation (IV) de 16 bytes est généré par flux/chaîne en utilisant
random_bytes(16). - L'IV est préfixé au texte chiffré.
- Le remplissage PKCS#7 est appliqué avant le chiffrement.
- Le filtre
/Cryptavec/AESV3est défini sur tous les paramètres de décodage de flux.
// Interne -- géré automatiquement par le writer
$iv = random_bytes(16);
$padded = pkcs7_pad($plaintext, blockSize: 16);
$encrypted = openssl_encrypt($padded, 'aes-256-cbc', $fileKey, OPENSSL_RAW_DATA, $iv);
$output = $iv . $encrypted;WARNING
TCPDF-Next Pro chiffre toujours les données de flux. L'indicateur /EncryptMetadata est par défaut true. S'il est défini sur false, le flux de métadonnées XMP reste non chiffré (utile pour l'indexation de recherche), mais tous les autres flux sont toujours chiffrés.
Gestion des paramètres sensibles
Toutes les méthodes qui acceptent des mots de passe sont annotées avec l'attribut #[\SensitiveParameter] de PHP 8.2. Cela empêche les mots de passe d'apparaître dans les traces de pile, la sortie de débogage et les journaux d'erreurs :
public function setOwnerPassword(
#[\SensitiveParameter] string $password,
): self {
$this->ownerPassword = SaslPrep::prepare($password);
return $this;
}Si une exception se produit, la trace de pile affichera Object(SensitiveParameterValue) au lieu de la chaîne de mot de passe réelle.
Exemple complet
use Yeeefang\TcpdfNext\Core\Document;
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
use Yeeefang\TcpdfNext\Pro\Security\Permissions;
$pdf = Document::create()
->setTitle('Confidential Report')
->addPage()
->setFont('Helvetica', size: 12)
->multiCell(0, 6, 'This document is protected with AES-256 encryption.');
$encryptor = new Aes256Encryptor(
ownerPassword: 'Str0ng!OwnerP@ss',
userPassword: 'reader2026',
permissions: new Permissions(
print: true,
printHighQuality: false,
copy: false,
modify: false,
annotate: true,
fillForms: true,
extractForAccess: true,
assemble: false,
),
);
$pdf->encrypt($encryptor)
->save(__DIR__ . '/encrypted.pdf');