跳到內容
getnextpdf.com

Enterprise 版本

數位浮水印與隱寫式嵌入

NextPDF Enterprise 透過對字母對之間的間距做出微小、受控的調整,將一個隱藏、加密的酬載嵌入所產生的 PDF。你提供一個酬載——通常是一個逐收件者的識別碼——與一把密鑰;編碼器會將酬載寫成與文字自然字距難以察覺的偏移。一個相符的解碼器,在獲得相同金鑰時,能還原該酬載。本頁是行為層級的:它陳述編碼器寫入什麼、它所使用的密碼學,以及該技術的邊界。

預期用途是內部文件洩漏追蹤:當一份受控文件外洩時,還原出的標記可識別出該收件者的副本。它不是對抗式隱寫術,也不保證能挺過任意處理。

先決條件陳述於前置資料中,並於先決條件一節重述。

此能力隨附於 NextPDF Enterprisenextpdf/enterprise),並以一個 Enterprise 層級的授權封套啟用。缺少該授權的部署不會載入此能力的類別。此能力在文件產生期間完全於行程內執行;沒有任何文件內容離開主機。比較各版本並取得授權

以字距陣列繪製的 PDF 文字,在字符之間承載一個數值調整。編碼器將每個調整位置視為承載少量位元的載體:

  1. 它以一個認證式加密附帶關聯資料(AEAD)密碼加密酬載——預設為 AES-256-GCM,或 ChaCha20-Poly1305。AEAD 同時提供機密性與完整性,因此被竄改的載體會在解密時失敗,而非默默地產出錯誤的酬載。
  2. 它使用基於 HMAC 的金鑰衍生函式(HKDF)搭配 SHA-256,從你的密鑰與字型金鑰衍生出 32 位元組的加密金鑰。HKDF 會從輸入金鑰材料萃取出一把固定長度的偽隨機金鑰,再將其擴展到所需長度,依 RFC 5869 §2。
  3. 它為每次加密產生一個全新隨機的 12 位元組初始化向量(IV)。AES-GCM 要求對某把給定金鑰而言 IV 必須唯一,否則認證保證會喪失,依 NIST SP 800-38D §5.2.1。
  4. 它將加密後的位元組對應為一個位元序列,並將位元分散到可用的字母對位置上,每個位置編碼一或兩個位元。它對自然字距所加入的偏移,受一個可設定的 em 分數所限制——小到足以維持視覺上難以察覺。

解碼器逆轉此過程:它從一個內容串流讀取字距調整、減去自然字距、將偏移量化回位元、重新組裝加密的 blob,並以相同金鑰解密它。若金鑰錯誤或載體已被破壞,解密會回傳空值,而非錯誤的酬載。

容量隨文字長度而擴展:每個字母對位置承載一或兩個位元,因此一個酬載必須能塞進文字所提供的位置。當酬載超過容量時,編碼器會引發一個具型別的溢位錯誤。

一個 PDF/A 相容模式會將最大偏移減半,以維持在驗證器的寬度容差門檻之下,以容量換取更嚴格的一致性。

承載此設計的關鍵選擇,是將標記藏在字距中,而非可見的疊加層或詮釋資料欄位。詮釋資料標記極易剝除,而可見的戳記會改變頁面。字距偏移則藏身於收件者必須保留的文字之中,並維持難以察覺。認證式加密是第二根支柱:被竄改或不完整的載體會通不過認證,因此解碼器回傳空值,而非錯誤的收件者。金鑰以 HKDF 逐字型衍生,將標記綁定至文件情境,而非一把裸露的共享密鑰。誠實的強健性揭露由此直接推導而出:標記能挺過一般的再散布,但無法挺過蓄意的內容串流改寫,因此其陳述範圍是內部洩漏追蹤,而非對抗式隱寫術。

設計背景:編校不是一塊黑色矩形

  1. 安裝 NextPDF Core 與 Enterprise 套件,並持有一份有效的 Enterprise 授權。
  2. 以一個會公開字距對度量的字型產生文件;編碼器會從字型度量讀取自然字距。
  3. 從你的密碼管理器(而非從原始碼)提供密鑰。解碼需要相同的金鑰。
  4. 依你的容量與一致性需求,決定位元深度(每個位置一或兩個位元)以及是否需要 PDF/A 相容。

編碼設定在建構時不可變且已驗證:

  • 位元深度 — 每個字母對位置一或兩個位元。較高的深度提供更多容量,但偏移較大。
  • 最大調整比率 — 偏移上限,以 em 的分數表示,在一個受限範圍內。較大的值提供更多餘裕,但有可見的風險。
  • 密碼 — AES-256-GCM(預設)或 ChaCha20-Poly1305。兩者皆為 AEAD。
  • PDF/A 相容 — 啟用時,將有效的最大偏移減半。

編碼與解碼請使用相同的設定;不符會導致無法還原任何酬載。

  1. 從你的密碼管理器讀取密鑰。
  2. 建立編碼設定(位元深度、偏移比率、密碼、PDF/A 旗標)。
  3. 為你即將呈現的文字計算字距調整,傳入酬載、文字、字型金鑰、字型度量、密鑰,以及設定。
  4. 在你寫入文字 run 時套用回傳的調整,讓標記在產生期間被嵌入。
  5. 若要追蹤一份外洩的副本,以相同的字型金鑰、字型度量、密鑰與設定,對可疑文件的內容串流執行解碼器,並讀取還原出的酬載。
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. 將一個已知酬載編碼進一個已知文字 run,再以相同金鑰與設定將它解碼回來;確認還原出的酬載相符。
  2. 以一個故意錯誤的金鑰解碼,並確認結果為 null,而非一個錯誤的酬載——這就是 AEAD 完整性保證在發揮作用。
  3. 檢視呈現出的頁面,並確認在所設定的偏移比率下,間距變化在視覺上並不明顯。
  4. 當需要 PDF/A 相容時,對照你的 PDF/A 設定檔驗證輸出,並確認沒有觸發寬度容差。
  • 認證式加密。 酬載以 AES-256-GCM 或 ChaCha20-Poly1305 加密。一個被竄改或截斷的載體會在解密時通不過認證;它不會產出一個錯誤的酬載。
  • 逐次加密的 IV。 每次加密都會產生一個全新隨機的 12 位元組 IV,滿足 AES-GCM 的唯一性要求,依 NIST SP 800-38D §5.2.1。
  • 衍生金鑰。 加密金鑰以 HKDF-SHA-256 從你的密鑰與字型金鑰衍生而來(RFC 5869 §2)。請將密鑰保存在你的密碼管理器中;請像對待任何簽署密碼一樣對待它。
  • 標記是文件內容。 嵌入的位元組是頁面內容的一部分,而非日誌內容。請勿將酬載或密鑰寫入日誌。

本頁涉及密碼學嵌入。每個規範性來源皆為改寫;不重現任何規範性文字。### 強健性揭露

標記承載於字距調整中。它可能被列印與重新掃描、被 PDF 轉換工具、被重新線性化,或被任何將字距正規化的內容串流改寫所破壞。此技術最適合用於以所產生形式散布之文件的內部洩漏追蹤。它不是對抗式隱寫術,也無法挺過任意的下游處理。當威脅模型包含蓄意剝除時,請勿將其作為唯一的控制措施而仰賴它。

  • 酬載過大。 當酬載超過文字容量時,編碼器會引發一個具型別的溢位錯誤。請縮短酬載或加長載體文字。
  • 載體文字太少。 短於兩個字元的文字不提供任何載體位置,並會引發錯誤。
  • 解碼時金鑰錯誤。 解碼回傳 null。請將 null 視為「無標記可還原」,而非部分結果。
  • 設定不符。 編碼與解碼必須使用相同的位元深度、偏移比率、密碼與 PDF/A 旗標;不符會導致無法還原任何酬載。

本頁僅記載外部可觀察的行為與受支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表格、runbook 檔名,以及工單前綴皆不在範圍內。