Skip to content

Enkripsi Lanjutan

Pro — Commercial License Required
Internal enkripsi lanjutan memerlukan paket Pro.

Halaman ini mendokumentasikan implementasi enkripsi internal di TCPDF-Next Pro. Halaman ini membahas handler AES-256 AESV3, algoritma key derivation, normalisasi password, dan penanganan parameter sensitif. Jika Anda mencari penggunaan enkripsi dasar, lihat contoh Enkripsi AES-256.

AES-256 dengan Handler AESV3

TCPDF-Next Pro mengimplementasikan ISO 32000-2 (PDF 2.0) Standard Security Handler revisi 6, yang mewajibkan AES-256-CBC untuk semua enkripsi stream dan string. Handler diidentifikasi oleh /V 5 dan /R 6 di encryption dictionary.

php
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;

$encryptor = new Aes256Encryptor(
    ownerPassword: 'Str0ng!OwnerP@ss',
    userPassword:  'reader2026',
);

Mengapa Tanpa RC4 atau AES-128

TCPDF-Next Pro sengaja mengecualikan RC4 (40-bit dan 128-bit) dan AES-128:

AlgoritmaAlasan Pengecualian
RC4-40Rusak sejak 1995; bisa diserang dengan mudah
RC4-128Bias dalam keystream; dilarang oleh PDF 2.0
AES-128Digantikan oleh AES-256 di revisi 6; tidak forward-compatible

PDF 2.0 (ISO 32000-2:2020) mewajibkan AESV3 untuk dokumen baru. Mendukung algoritma yang lebih lemah akan mengkompromikan postur keamanan dan melanggar spesifikasi.

Algorithm 2.B: Key Derivation

File encryption key diturunkan dari password menggunakan Algorithm 2.B (ISO 32000-2, klausul 7.6.4.3.4). Ini adalah proses iteratif yang mengaitkan SHA-256, SHA-384, dan 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 key

Hashing iteratif ini membuat serangan brute-force mahal secara komputasional sambil tetap cukup cepat untuk penggunaan yang sah.

Komponen Key di Encryption Dictionary

EntryPanjangTujuan
/O48 byteValidasi owner password (hash + validation salt)
/U48 byteValidasi user password (hash + validation salt)
/OE32 byteFile encryption key yang dienkripsi owner
/UE32 byteFile encryption key yang dienkripsi user
/Perms16 byteFlag permission yang dienkripsi AES-256

Normalisasi Password SASLprep

Sebelum operasi kriptografis apa pun, password dinormalisasi menggunakan SASLprep (RFC 4013), yang merupakan profil dari stringprep (RFC 3454). Ini memastikan penanganan password yang konsisten terlepas dari bentuk normalisasi Unicode yang digunakan oleh sistem operasi atau input method.

php
use Yeeefang\TcpdfNext\Pro\Security\SaslPrep;

$normalized = SaslPrep::prepare('P\u{00E4}ssw\u{00F6}rd');
// Menormalisasi bentuk composed/decomposed, memetakan karakter tertentu,
// dan menolak codepoint yang dilarang.

Apa yang Dilakukan SASLprep

  1. Map -- Karakter yang umumnya di-map-ke-nothing (misalnya, soft hyphen U+00AD) dihapus.
  2. Normalize -- String dikonversi ke Unicode NFC (Canonical Decomposition diikuti Canonical Composition).
  3. Prohibit -- Karakter dari RFC 3454 Tabel C.1.2 sampai C.9 ditolak (karakter kontrol, private use, surrogate, non-character, dll.).
  4. Bidirectional check -- String dengan karakter kiri-ke-kanan dan kanan-ke-kiri divalidasi sesuai RFC 3454 klausul 6.

Ini berarti user yang mengetik U+00FC (LATIN SMALL LETTER U WITH DIAERESIS) dan user lain yang mengetik U+0075 U+0308 (LATIN SMALL LETTER U + COMBINING DIAERESIS) akan menghasilkan encryption key yang sama.

Encoding Permission

Permission disimpan di entry /Perms sebagai blok terenkripsi AES-256-ECB 16 byte. Layout plaintext-nya:

Byte 0-3:   Flag permission (little-endian int32)
Byte 4-7:   0xFFFFFFFF
Byte 8:     'T' jika EncryptMetadata true, 'F' jika tidak
Byte 9-11:  'adb'
Byte 12-15: Random padding

Flag permission mengikuti layout bit yang sama seperti yang didefinisikan di ISO 32000-2 Tabel 22.

Penanganan Stream Terenkripsi

Setiap content stream dan string di PDF dienkripsi secara individual:

  1. Initialization Vector (IV) 16 byte unik dihasilkan per stream/string menggunakan random_bytes(16).
  2. IV ditambahkan di awal ciphertext.
  3. Padding PKCS#7 diterapkan sebelum enkripsi.
  4. Filter /Crypt dengan /AESV3 diset pada semua parameter decode stream.
php
// Internal -- ditangani otomatis oleh 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 selalu mengenkripsi data stream. Flag /EncryptMetadata default ke true. Jika diset false, stream metadata XMP tetap tidak terenkripsi (berguna untuk search indexing), tapi semua stream lainnya tetap terenkripsi.

Penanganan Parameter Sensitif

Semua method yang menerima password dianotasi dengan atribut PHP 8.2 #[\SensitiveParameter]. Ini mencegah password muncul di stack trace, output debug, dan log error:

php
public function setOwnerPassword(
    #[\SensitiveParameter] string $password,
): self {
    $this->ownerPassword = SaslPrep::prepare($password);
    return $this;
}

Jika exception terjadi, stack trace akan menampilkan Object(SensitiveParameterValue) alih-alih string password yang sebenarnya.

Contoh Lengkap

php
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');

Didistribusikan di bawah lisensi LGPL-3.0-or-later.