Aller au contenu
getnextpdf.com

Enterprise édition

Filigrane numérique et intégration stéganographique

NextPDF Enterprise intègre une charge utile chiffrée et cachée dans un PDF généré en apportant de petits ajustements contrôlés à l’espacement entre les paires de lettres. Tu fournis une charge utile — typiquement un identifiant par destinataire — et une clé secrète ; l’encodeur écrit la charge utile sous forme de déviations imperceptibles par rapport au crénage naturel du texte. Un décodeur correspondant, à qui l’on donne la même clé, récupère la charge utile. Cette page est au niveau du comportement : elle énonce ce que l’encodeur écrit, la cryptographie qu’il utilise et la frontière de la technique.

L’usage prévu est le traçage interne des fuites de documents : lorsqu’un document contrôlé fuit, le marqueur récupéré identifie la copie du destinataire. Ce n’est pas de la stéganographie adversariale et ce n’est pas une garantie de survie à un traitement arbitraire.

Les prérequis sont énoncés dans le front matter et répétés sous Prérequis.

Cette capacité est livrée dans NextPDF Enterprise (nextpdf/enterprise) et s’active avec une enveloppe de licence de niveau Enterprise. Un déploiement sans ce droit ne charge pas les classes de la capacité. La capacité s’exécute entièrement en mémoire (in-process) pendant la génération du document ; aucun contenu de document ne quitte l’hôte. Comparer les éditions et obtenir une licence.

Le texte PDF dessiné avec un tableau de crénage porte un ajustement numérique entre les glyphes. L’encodeur traite chaque position d’ajustement comme un porteur de quelques bits :

  1. Il chiffre la charge utile avec un chiffrement authentifié avec données associées (AEAD) — AES-256-GCM par défaut, ou ChaCha20-Poly1305. L’AEAD fournit à la fois la confidentialité et l’intégrité, de sorte qu’un porteur altéré échoue au déchiffrement plutôt que de produire silencieusement une charge utile erronée.
  2. Il dérive la clé de chiffrement de 32 octets à partir de ta clé secrète et de la clé de police à l’aide de la fonction de dérivation de clé basée sur HMAC (HKDF) avec SHA-256. HKDF extrait une clé pseudo-aléatoire de longueur fixe du matériel de clé d’entrée, puis l’étend à la longueur requise, conformément à RFC 5869 §2.
  3. Il génère un vecteur d’initialisation (IV) aléatoire frais de 12 octets par chiffrement. AES-GCM exige que l’IV soit unique pour une clé donnée, sinon l’assurance d’authentification est perdue, conformément à NIST SP 800-38D §5.2.1.
  4. Il mappe les octets chiffrés vers une séquence de bits et répartit les bits sur les positions de paires de lettres disponibles, encodant un ou deux bits par position. La déviation qu’il ajoute au crénage naturel est bornée par une fraction configurable du cadratin (em) — suffisamment petite pour rester visuellement imperceptible.

Le décodeur inverse le processus : il lit les ajustements de crénage d’un flux de contenu, soustrait le crénage naturel, quantifie les déviations en bits, réassemble le blob chiffré et le déchiffre avec la même clé. Si la clé est erronée ou le porteur a été détruit, le déchiffrement ne renvoie rien plutôt qu’une charge utile erronée.

La capacité évolue avec la longueur du texte : chaque position de paire de lettres porte un ou deux bits, donc une charge utile doit tenir dans les positions que le texte fournit. L’encodeur lève une erreur de débordement typée lorsque la charge utile dépasse la capacité.

Un mode de compatibilité PDF/A divise par deux la déviation maximale pour rester sous le seuil de tolérance de largeur d’un validateur, échangeant de la capacité contre une conformité plus stricte.

Le choix porteur est de cacher le marqueur dans le crénage plutôt que dans une surcouche visible ou un champ de métadonnées. Un marqueur de métadonnées est trivial à retirer, et une estampille visible altère la page. Les déviations de crénage, au contraire, voyagent à l’intérieur du texte que le destinataire doit conserver, et restent imperceptibles. Le chiffrement authentifié est le second pilier : un porteur altéré ou partiel échoue à l’authentification, de sorte que le décodeur ne renvoie rien plutôt qu’un mauvais destinataire. La clé est dérivée par police avec HKDF, liant le marqueur au contexte du document, et non à un simple secret partagé. La divulgation de robustesse honnête en découle directement : le marqueur survit à une redistribution ordinaire mais pas à une réécriture délibérée du flux de contenu, de sorte que la portée annoncée est le traçage interne des fuites, et non la stéganographie adversariale.

Contexte de conception : Le caviardage n’est pas un rectangle noir.

  1. Installe NextPDF Core et le paquet Enterprise, et détiens une licence Enterprise active.
  2. Génère le document avec une police qui expose des métriques de paires de crénage ; l’encodeur lit le crénage naturel dans les métriques de la police.
  3. Fournis la clé secrète depuis ton gestionnaire de secrets, et non depuis le code source. La même clé est requise pour décoder.
  4. Décide de la profondeur de bits (un ou deux bits par position) et de la nécessité ou non d’une compatibilité PDF/A, selon tes besoins de capacité et de conformité.

La configuration d’encodage est immuable et validée à la construction :

  • Profondeur de bits — un ou deux bits par position de paire de lettres. Une profondeur plus élevée donne plus de capacité mais des déviations plus grandes.
  • Ratio d’ajustement maximal — le plafond de déviation comme fraction du cadratin (em), dans une plage bornée. Des valeurs plus grandes donnent plus de marge mais risquent la visibilité.
  • Chiffrement — AES-256-GCM (par défaut) ou ChaCha20-Poly1305. Les deux sont AEAD.
  • Compatibilité PDF/A — lorsqu’activée, divise par deux la déviation maximale effective.

Utilise la même configuration pour l’encodage et le décodage ; une discordance ne produit aucune charge utile récupérée.

  1. Lis la clé secrète depuis ton gestionnaire de secrets.
  2. Construis la configuration d’encodage (profondeur de bits, ratio de déviation, chiffrement, indicateur PDF/A).
  3. Calcule les ajustements de crénage pour le texte que tu t’apprêtes à rendre, en passant la charge utile, le texte, la clé de police, les métriques de la police, la clé secrète et la configuration.
  4. Applique les ajustements renvoyés lorsque tu écris la séquence de texte, afin que le marqueur soit intégré pendant la génération.
  5. Pour tracer une copie ayant fuité, exécute le décodeur sur le flux de contenu du document suspect avec la même clé de police, les mêmes métriques de police, la même clé secrète et la même configuration, et lis la charge utile récupérée.
examples/enterprise/steganography-capacity-guard.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
/**
* Reject a payload that cannot fit the carrier text before encoding.
*
* Each letter-pair position carries $bitDepth bits. Guarding capacity up
* front turns an unencodable payload into a clear caller-side error instead
* of relying on the encoder's overflow exception alone.
*
* @param non-empty-string $payload The bytes to embed (already minimal).
* @param positive-int $textLength The character count of the carrier text.
* @param int<1, 2> $bitDepth Bits encoded per letter-pair position.
*
* @throws \OverflowException When the payload cannot fit the available positions.
*/
function assertPayloadFits(string $payload, int $textLength, int $bitDepth): void
{
$positions = $textLength - 1;
$capacityBytes = \intdiv($positions * $bitDepth, 8);
if (\strlen($payload) > $capacityBytes) {
throw new \OverflowException(\sprintf(
'Payload of %d bytes exceeds carrier capacity of %d bytes.',
\strlen($payload),
$capacityBytes,
));
}
}
examples/enterprise/steganography-decode-guarded.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Enterprise\Security\Steganography\SteganographyDecoder;
use NextPDF\Enterprise\Security\Steganography\SteganographyConfig;
use NextPDF\Typography\FontMetrics;
use Psr\Log\LoggerInterface;
final readonly class LeakTracer
{
public function __construct(private LoggerInterface $logger) {}
/**
* Recover the embedded marker from a suspect document's content stream.
*
* Decoding returns null on a wrong key or a destroyed carrier rather than
* a wrong payload, so the caller treats null as "no marker recovered".
*
* @param string $contentStream The suspect content-stream bytes.
* @param non-empty-string $fontKey The font key used at generation.
* @param FontMetrics $metrics Font metrics with kerning pairs.
* @param string $secretKey The same secret key used to encode.
* @param SteganographyConfig $config The same configuration used to encode.
*
* @return string|null The recovered marker, or null when none is found.
*/
public function trace(
string $contentStream,
string $fontKey,
FontMetrics $metrics,
string $secretKey,
SteganographyConfig $config,
): ?string {
$marker = SteganographyDecoder::decodeFromContentStream(
$contentStream,
$fontKey,
$metrics,
$secretKey,
$config,
);
if ($marker === null) {
$this->logger->info('No steganographic marker recovered from content stream.');
}
return $marker;
}
}
  1. Encode une charge utile connue dans une séquence de texte connue, puis décode-la avec la même clé et la même configuration ; confirme que la charge utile récupérée correspond.
  2. Décode avec une clé délibérément erronée et confirme que le résultat est null, et non une charge utile erronée — c’est la garantie d’intégrité AEAD en action.
  3. Inspecte la page rendue et confirme que le changement d’espacement n’est pas visuellement apparent au ratio de déviation configuré.
  4. Lorsqu’une compatibilité PDF/A est requise, valide la sortie au regard de ton profil PDF/A et confirme que la tolérance de largeur n’est pas dépassée.
  • Chiffrement authentifié. La charge utile est chiffrée avec AES-256-GCM ou ChaCha20-Poly1305. Un porteur altéré ou tronqué échoue à l’authentification au déchiffrement ; il ne produit pas de charge utile erronée.
  • IV par chiffrement. Un IV aléatoire frais de 12 octets est généré pour chaque chiffrement, satisfaisant l’exigence d’unicité d’AES-GCM conformément à NIST SP 800-38D §5.2.1.
  • Clé dérivée. La clé de chiffrement est dérivée avec HKDF-SHA-256 à partir de ton secret et de la clé de police (RFC 5869 §2). Garde le secret dans ton gestionnaire de secrets ; traite-le comme n’importe quel secret de signature.
  • Le marqueur est du contenu de document. Les octets intégrés font partie du contenu de la page, et non du contenu de journal. N’écris pas la charge utile ni la clé secrète dans les journaux.

Cette page concerne l’intégration cryptographique. Chaque source normative est paraphrasée ; aucun texte normatif n’est reproduit. ### Divulgation de robustesse

Le marqueur est porté par les ajustements de crénage. Il peut être détruit par l’impression et la renumérisation, par les outils de conversion PDF, par une re-linéarisation, ou par toute réécriture de flux de contenu qui normalise le crénage. La technique est la mieux adaptée au traçage interne des fuites de documents distribués sous leur forme générée. Ce n’est pas de la stéganographie adversariale et elle ne survit pas à un traitement aval arbitraire. Ne t’y fie pas comme seul contrôle lorsque le modèle de menace inclut un retrait délibéré.

  • Charge utile trop grande. L’encodeur lève une erreur de débordement typée lorsque la charge utile dépasse la capacité du texte. Raccourcis la charge utile ou rallonge le texte porteur.
  • Trop peu de texte porteur. Un texte de moins de deux caractères n’offre aucune position de porteur et lève une erreur.
  • Clé erronée au décodage. Le décodage renvoie null. Traite null comme « aucun marqueur récupéré », et non comme un résultat partiel.
  • Discordance de configuration. L’encodage et le décodage doivent utiliser la même profondeur de bits, le même ratio de déviation, le même chiffrement et le même indicateur PDF/A ; une discordance ne produit aucune charge utile récupérée.

Cette page documente uniquement le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins de namespace internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de tickets sont hors de portée.