generated from jric11/baseProject
Initial commit
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DecryptTest.php
|
||||
*
|
||||
* @since 2026-04-30
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use Com\Tecnick\Pdf\Encrypt\Decrypt;
|
||||
use Com\Tecnick\Pdf\Encrypt\Encrypt;
|
||||
|
||||
/**
|
||||
* Decrypt test
|
||||
*
|
||||
* Coverage notes (unreachable / untestable defensive guards):
|
||||
* - Decrypt::tryDecryptRecipient() `$tmpIn === false || $tmpOut === false`:
|
||||
* tempnam() failure requires a filesystem-level fault; cannot be reliably
|
||||
* induced in unit tests.
|
||||
* - Decrypt::tryDecryptRecipient() `file_put_contents === false`:
|
||||
* same as above.
|
||||
* - AESnopad::decrypt() `$dec === false`:
|
||||
* openssl_decrypt() cannot return false for well-formed AES-CBC ciphertext
|
||||
* with a valid key; this guard protects against hypothetical extension failures.
|
||||
* - Decrypt::decryptAes() `$dec === false` return '':
|
||||
* same reasoning; openssl_decrypt is called with correct key/iv/cipher.
|
||||
*
|
||||
* @since 2026-04-30
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class DecryptTest extends TestUtil
|
||||
{
|
||||
/** Build a Decrypt object from an Encrypt instance's encryption data. */
|
||||
private function decryptFromEncrypt(Encrypt $enc): Decrypt
|
||||
{
|
||||
return new Decrypt($enc->getEncryptionData());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Mode 2 (AES-128) — standard password authentication
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAuthenticateUserMode2(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 2, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('userpass'));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
}
|
||||
|
||||
public function testAuthenticateOwnerMode2(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 2, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('ownerpass'));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
}
|
||||
|
||||
public function testAuthenticateWrongPasswordMode2(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 2, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertFalse($dec->authenticate('wrongpassword'));
|
||||
// Key must remain empty after failed authentication.
|
||||
$this->assertSame('', $dec->getDocumentKey());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Mode 3 (AES-256 R5) — standard password authentication
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAuthenticateUserMode3(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('userpass'));
|
||||
$this->assertEquals(32, \strlen($dec->getDocumentKey()));
|
||||
}
|
||||
|
||||
public function testAuthenticateOwnerMode3(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('ownerpass'));
|
||||
$this->assertEquals(32, \strlen($dec->getDocumentKey()));
|
||||
}
|
||||
|
||||
public function testAuthenticateWrongPasswordMode3(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertFalse($dec->authenticate('wrong'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Mode 4 (AES-256 R6 / PDF 2.0) — standard password authentication
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAuthenticateUserMode4(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 4, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('userpass'));
|
||||
$this->assertEquals(32, \strlen($dec->getDocumentKey()));
|
||||
}
|
||||
|
||||
public function testAuthenticateOwnerMode4(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 4, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('ownerpass'));
|
||||
$this->assertEquals(32, \strlen($dec->getDocumentKey()));
|
||||
}
|
||||
|
||||
public function testAuthenticateWrongPasswordMode4(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 4, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertFalse($dec->authenticate('wrong'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Modes 0 and 1 (RC4 — deprecated but must still authenticate correctly)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAuthenticateUserMode0(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated/i', function (): void {
|
||||
$enc = new Encrypt(true, \md5('file'), 0, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('userpass'));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
});
|
||||
}
|
||||
|
||||
public function testAuthenticateOwnerMode0(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated/i', function (): void {
|
||||
$enc = new Encrypt(true, \md5('file'), 0, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('ownerpass'));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
});
|
||||
}
|
||||
|
||||
public function testAuthenticateUserMode1(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated/i', function (): void {
|
||||
$enc = new Encrypt(true, \md5('file'), 1, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('userpass'));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
});
|
||||
}
|
||||
|
||||
public function testAuthenticateOwnerMode1(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated/i', function (): void {
|
||||
$enc = new Encrypt(true, \md5('file'), 1, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('ownerpass'));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// decryptString round-trips
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* RC4 modes are symmetric: encrypt(encrypt(data, key)) = data.
|
||||
* The plaintext is recovered exactly (no padding).
|
||||
*/
|
||||
public function testDecryptStringRoundtripMode0(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated/i', function (): void {
|
||||
$enc = new Encrypt(true, \md5('file'), 0, ['print'], 'alpha', 'beta');
|
||||
$plaintext = 'hello world';
|
||||
$ciphertext = $enc->encryptString($plaintext, 1);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame($plaintext, $dec->decryptString($ciphertext, 1));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AES-128: IV-prefixed stream; PKCS#7 padding is stripped so the exact
|
||||
* plaintext is recovered.
|
||||
*/
|
||||
public function testDecryptStringRoundtripMode2(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 2, ['print'], 'alpha', 'beta');
|
||||
$plaintext = 'hello world';
|
||||
$ciphertext = $enc->encryptString($plaintext, 1);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame($plaintext, $dec->decryptString($ciphertext, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES-256 R5: full document key used; exact plaintext recovered.
|
||||
*/
|
||||
public function testDecryptStringRoundtripMode3(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'alpha', 'beta');
|
||||
$plaintext = 'hello world';
|
||||
$ciphertext = $enc->encryptString($plaintext, 1);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame($plaintext, $dec->decryptString($ciphertext, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES-256 R6: same as R5 but with hash2B key derivation.
|
||||
*/
|
||||
public function testDecryptStringRoundtripMode4(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 4, ['print'], 'alpha', 'beta');
|
||||
$plaintext = 'hello world';
|
||||
$ciphertext = $enc->encryptString($plaintext, 1);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame($plaintext, $dec->decryptString($ciphertext, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Block-aligned plaintext (exactly 16 bytes) must round-trip exactly: the
|
||||
* PKCS#7 scheme appends a full extra padding block on encryption that must
|
||||
* be removed on decryption.
|
||||
*/
|
||||
public function testDecryptStringRoundtripBlockAligned(): void
|
||||
{
|
||||
foreach ([2, 3, 4] as $mode) {
|
||||
$enc = new Encrypt(true, \md5('file'), $mode, ['print'], 'alpha', 'beta');
|
||||
$plaintext = \str_repeat('A', 16);
|
||||
$ciphertext = $enc->encryptString($plaintext, 7);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame($plaintext, $dec->decryptString($ciphertext, 7), "mode {$mode}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty plaintext must round-trip to an empty string for all AES modes.
|
||||
*/
|
||||
public function testDecryptStringRoundtripEmpty(): void
|
||||
{
|
||||
foreach ([2, 3, 4] as $mode) {
|
||||
$enc = new Encrypt(true, \md5('file'), $mode, ['print'], 'alpha', 'beta');
|
||||
$ciphertext = $enc->encryptString('', 3);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame('', $dec->decryptString($ciphertext, 3), "mode {$mode}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Without authentication the key is empty; decryptString returns data that
|
||||
* differs from the original plaintext (garbage decrypt, not the correct value).
|
||||
*/
|
||||
public function testDecryptStringWithoutAuthProducesGarbage(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'userpass', 'ownerpass');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
// Key is cleared in constructor; without authenticate(), key is empty.
|
||||
$ciphertext = $enc->encryptString('hello world', 1);
|
||||
$result = $dec->decryptString($ciphertext, 1);
|
||||
// Without the correct key the output must differ from the plaintext.
|
||||
$this->assertStringNotContainsString('hello world', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* decryptString with too-short AES data (≤ 16 bytes) returns empty string.
|
||||
*/
|
||||
public function testDecryptStringAesTooShortData(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'alpha', 'beta');
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('alpha'));
|
||||
$this->assertSame('', $dec->decryptString(\str_repeat('x', 16), 0));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getDocumentKey after failed/successful authentication
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testGetDocumentKeyAfterFailedAuth(): void
|
||||
{
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], 'userpass', 'ownerpass');
|
||||
// Store the real key, then construct Decrypt with an overwritten empty key
|
||||
$data = $enc->getEncryptionData();
|
||||
$data['key'] = '';
|
||||
$dec = new Decrypt($data);
|
||||
$this->assertFalse($dec->authenticate('wrong'));
|
||||
$this->assertSame('', $dec->getDocumentKey());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public-key mode authentication
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAuthenticatePublicKeyMode3(): void
|
||||
{
|
||||
$certPath = __DIR__ . '/data/cert.pem';
|
||||
$pubkeys = [['c' => $certPath, 'p' => ['print']]];
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], '', '', $pubkeys);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('', $certPath));
|
||||
$this->assertEquals(32, \strlen($dec->getDocumentKey()));
|
||||
}
|
||||
|
||||
public function testAuthenticatePublicKeyMode1(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
// Mode 1 pubkey silently promotes mode 0 → 1 (covered elsewhere).
|
||||
$certPath = __DIR__ . '/data/cert.pem';
|
||||
$pubkeys = [['c' => $certPath, 'p' => ['print']]];
|
||||
$enc = new Encrypt(true, \md5('file'), 1, ['print'], '', '', $pubkeys);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertTrue($dec->authenticate('', $certPath));
|
||||
$this->assertNotEmpty($dec->getDocumentKey());
|
||||
});
|
||||
}
|
||||
|
||||
public function testAuthenticatePublicKeyEmptyPathReturnsFalse(): void
|
||||
{
|
||||
$certPath = __DIR__ . '/data/cert.pem';
|
||||
$pubkeys = [['c' => $certPath, 'p' => ['print']]];
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], '', '', $pubkeys);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
$this->assertFalse($dec->authenticate('', ''));
|
||||
}
|
||||
|
||||
public function testAuthenticatePublicKeyWrongKeyReturnsFalse(): void
|
||||
{
|
||||
$certPath = __DIR__ . '/data/cert.pem';
|
||||
$pubkeys = [['c' => $certPath, 'p' => ['print']]];
|
||||
$enc = new Encrypt(true, \md5('file'), 3, ['print'], '', '', $pubkeys);
|
||||
$dec = $this->decryptFromEncrypt($enc);
|
||||
// Use the test PHP file as a "wrong" key — openssl_pkcs7_decrypt will fail.
|
||||
$this->assertFalse($dec->authenticate('', __FILE__));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cover the `hex2bin() === false` branch in findDecryptedRecipientSeed().
|
||||
*
|
||||
* When a Recipients entry contains non-hexadecimal characters, hex2bin()
|
||||
* returns false and the entry is skipped via `continue`. With no valid
|
||||
* entries the method returns null and authenticate() returns false.
|
||||
*/
|
||||
public function testAuthenticatePublicKeyInvalidHexRecipientReturnsFalse(): void
|
||||
{
|
||||
$certPath = __DIR__ . '/data/cert.pem';
|
||||
// Manually build an encryptdata array in pubkey mode whose Recipients
|
||||
// list contains only a string that is not valid hex (non-hex characters
|
||||
// cause hex2bin() to return false).
|
||||
$data = [
|
||||
'V' => 6,
|
||||
'Length' => 256,
|
||||
'O' => \str_repeat('x', 32),
|
||||
'U' => \str_repeat('x', 48),
|
||||
'P' => 0,
|
||||
'fileid' => \md5('test'),
|
||||
'mode' => 3,
|
||||
'pubkey' => true,
|
||||
'Recipients' => ['ZZZZINVALID!!'], // hex2bin returns false for non-hex chars
|
||||
];
|
||||
$dec = new \Com\Tecnick\Pdf\Encrypt\Decrypt($data);
|
||||
$this->assertFalse($dec->authenticate('', $certPath));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// AESnopad::decrypt() direct tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testAesnopadDecryptRoundtrip32Bytes(): void
|
||||
{
|
||||
$aesnopad = new \Com\Tecnick\Pdf\Encrypt\Type\AESnopad();
|
||||
$key = \str_repeat('k', 32);
|
||||
$plaintext = \str_repeat('p', 32); // exact 32-byte payload (e.g. file key)
|
||||
$ciphertext = $aesnopad->encrypt($plaintext, $key);
|
||||
$decrypted = $aesnopad->decrypt($ciphertext, $key);
|
||||
$this->assertSame($plaintext, $decrypted);
|
||||
}
|
||||
|
||||
public function testAesnopadDecryptRoundtripAes128(): void
|
||||
{
|
||||
$aesnopad = new \Com\Tecnick\Pdf\Encrypt\Type\AESnopad();
|
||||
$key = \str_repeat('k', 16);
|
||||
$plaintext = \str_repeat('p', 16);
|
||||
$ivect = \Com\Tecnick\Pdf\Encrypt\Type\AESnopad::IVECT;
|
||||
$ciphertext = $aesnopad->encrypt($plaintext, $key, $ivect, 'aes-128-cbc');
|
||||
$decrypted = $aesnopad->decrypt($ciphertext, $key, $ivect, 'aes-128-cbc');
|
||||
$this->assertSame($plaintext, $decrypted);
|
||||
}
|
||||
|
||||
public function testAesnopadDecryptInvalidCipherThrows(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$aesnopad = new \Com\Tecnick\Pdf\Encrypt\Type\AESnopad();
|
||||
$aesnopad->decrypt('data', 'key', \Com\Tecnick\Pdf\Encrypt\Type\AESnopad::IVECT, 'des-cbc');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* EncryptTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* Encrypt Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class EncryptTest extends TestUtil
|
||||
{
|
||||
// Coverage note: src/Compute.php hash2B() line ~295
|
||||
// `throw new EncException('AES-128-CBC encryption failed in hash2B')` is a defensive guard;
|
||||
// openssl_encrypt() never returns false for valid block-aligned AES-128-CBC inputs with
|
||||
// a correct 16-byte key and IV — this branch is unreachable under normal PHP/OpenSSL conditions.
|
||||
//
|
||||
// Coverage note: src/Compute.php getEncryptedRecipientBytes()
|
||||
// The `tempnam() === false` / `file_put_contents() === false` guards (and the
|
||||
// accompanying unlink() cleanup on those error paths) require a filesystem failure
|
||||
// that cannot be reliably induced in unit tests. The happy-path try/finally cleanup
|
||||
// and encryptRecipientEnvelope() are exercised by the public-key tests.
|
||||
//
|
||||
// Coverage note: src/Encrypt.php convertStringToHexString() line ~246
|
||||
// `return ''` after `preg_split('//', ...)` guards against the impossible case where
|
||||
// preg_split returns false; the regex '//\'' is always valid and never returns false.
|
||||
|
||||
public function testEncryptException(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'));
|
||||
$encrypt->encrypt('WRONG');
|
||||
});
|
||||
}
|
||||
|
||||
public function testEncryptModeException(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 5);
|
||||
}
|
||||
|
||||
public function testEncryptThree(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$result = $encrypt->encrypt(3, 'alpha');
|
||||
$this->assertEquals(32, \strlen($result));
|
||||
}
|
||||
|
||||
public function testEncryptWithAesEncoderName(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$result = $encrypt->encrypt('AES', 'alpha', '0123456789abcdef0123456789abcdef');
|
||||
$this->assertGreaterThan(16, \strlen($result));
|
||||
}
|
||||
|
||||
public function testEncryptPubThree(): void
|
||||
{
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta', $pubkeys);
|
||||
$result = $encrypt->encrypt(3, 'alpha');
|
||||
$this->assertEquals(32, \strlen($result));
|
||||
}
|
||||
|
||||
public function testEncryptPubNoP(): void
|
||||
{
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta', $pubkeys);
|
||||
$result = $encrypt->encrypt(3, 'alpha');
|
||||
$this->assertEquals(32, \strlen($result));
|
||||
}
|
||||
|
||||
public function testEncryptPubException(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta', [[
|
||||
'c' => __FILE__,
|
||||
'p' => ['print'],
|
||||
]]);
|
||||
}
|
||||
|
||||
public function testEncryptPubUnreadableCertificateException(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
|
||||
\set_error_handler(static fn(): bool => true);
|
||||
try {
|
||||
new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta', [[
|
||||
'c' => __DIR__ . '/data/does-not-exist.pem',
|
||||
'p' => ['print'],
|
||||
]]);
|
||||
} finally {
|
||||
\restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
public function testEncryptRc4ThroughOpenSslWhenAvailable(): void
|
||||
{
|
||||
if (!\in_array('RC4', \openssl_get_cipher_methods(), true)) {
|
||||
$this->markTestSkipped('OpenSSL RC4 cipher is not available on this runtime.');
|
||||
}
|
||||
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$result = $encrypt->encrypt('RC4', 'alpha', '0123456789abcdef');
|
||||
$this->assertSame(5, \strlen($result));
|
||||
}
|
||||
|
||||
public function testEncryptModZeroPub(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
0,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
$pubkeys,
|
||||
);
|
||||
$result = $encrypt->encrypt(1, 'alpha');
|
||||
// Check for "error:0308010C:digital envelope routines::unsupported" when using OpenSSL 3.
|
||||
// \var_dump(\openssl_error_string());
|
||||
$this->assertEquals(5, \strlen($result));
|
||||
});
|
||||
}
|
||||
|
||||
/** Issue 6: RC4 mode 0 must emit a deprecation notice. */
|
||||
public function testRc4DeprecationModeZero(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated.*cryptographically broken/i', function (): void {
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 0, ['print'], 'alpha', 'beta');
|
||||
$result = $encrypt->encrypt(0, 'alpha');
|
||||
$this->assertGreaterThan(0, \strlen($result));
|
||||
});
|
||||
}
|
||||
|
||||
/** Issue 6: RC4 mode 1 must emit a deprecation notice. */
|
||||
public function testRc4DeprecationModeOne(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/RC4 encryption.*deprecated.*cryptographically broken/i', function (): void {
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 1, ['print'], 'alpha', 'beta');
|
||||
$result = $encrypt->encrypt(1, 'alpha');
|
||||
$this->assertGreaterThan(0, \strlen($result));
|
||||
});
|
||||
}
|
||||
|
||||
/** Issue 5: mode 0 + pubkeys must emit the upgrade deprecation notice. */
|
||||
public function testPubKeyModeZeroDeprecation(): void
|
||||
{
|
||||
$this->bcAssertUserDeprecationMessageMatches('/Public-key encryption requires at least RC4-128/i', function (): void {
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
0,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
$pubkeys,
|
||||
);
|
||||
// After promotion to mode 1, the resulting encryption data must reflect mode 1
|
||||
$data = $encrypt->getEncryptionData();
|
||||
$this->assertEquals(1, $data['mode']);
|
||||
$this->assertEquals(2, $data['V']);
|
||||
});
|
||||
}
|
||||
|
||||
/** Issue 2: AES-256 perms bytes 12-15 must be random (not 'nick'). */
|
||||
public function testPermsRandomBytes(): void
|
||||
{
|
||||
$encrypt1 = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$encrypt2 = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$data1 = $encrypt1->getEncryptionData();
|
||||
$data2 = $encrypt2->getEncryptionData();
|
||||
// The 16-byte AES-encrypted perms block (AESnopad strips the PKCS7 padding block)
|
||||
$this->assertEquals(16, \strlen($data1['perms']));
|
||||
$this->assertEquals(16, \strlen($data2['perms']));
|
||||
// Two independently generated perms values should almost certainly differ (random bytes 12-15)
|
||||
// Note: 1 in 2^32 chance of collision is acceptable to document rather than retry.
|
||||
$this->assertNotEquals($data1['perms'], $data2['perms'], 'perms bytes should be random each time');
|
||||
}
|
||||
|
||||
/** Issue 3: AES-256 with EncryptMetadata=false must store the flag. */
|
||||
public function testEncryptMetadataFalse(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
3,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
null,
|
||||
false, // encryptMetadata = false
|
||||
);
|
||||
$data = $encrypt->getEncryptionData();
|
||||
$this->assertFalse($data['EncryptMetadata']);
|
||||
}
|
||||
|
||||
/** Issue 4: AES-256 R6 (mode 4) encrypt round-trip. */
|
||||
public function testEncryptFour(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 4, ['print'], 'alpha', 'beta');
|
||||
$result = $encrypt->encrypt(4, 'alpha');
|
||||
$this->assertEquals(32, \strlen($result));
|
||||
}
|
||||
|
||||
/** Issue 4: AES-256 R6 (mode 4) encryptdata must have V=6 and mode=4. */
|
||||
public function testEncryptFourSettings(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 4, ['print'], 'alpha', 'beta');
|
||||
$data = $encrypt->getEncryptionData();
|
||||
$this->assertEquals(4, $data['mode']);
|
||||
$this->assertEquals(6, $data['V']);
|
||||
$this->assertEquals(256, $data['Length']);
|
||||
$this->assertEquals('AESV3', $data['CF']['CFM']);
|
||||
$this->assertEquals(48, \strlen($data['U']));
|
||||
$this->assertEquals(48, \strlen($data['O']));
|
||||
$this->assertEquals(32, \strlen($data['UE']));
|
||||
$this->assertEquals(32, \strlen($data['OE']));
|
||||
$this->assertEquals(16, \strlen($data['perms']));
|
||||
}
|
||||
|
||||
/** Issue 4: AES-256 R6 (mode 4) public-key encryption. */
|
||||
public function testEncryptPubFour(): void
|
||||
{
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 4, ['print'], 'alpha', 'beta', $pubkeys);
|
||||
$result = $encrypt->encrypt(4, 'alpha');
|
||||
$this->assertEquals(32, \strlen($result));
|
||||
}
|
||||
|
||||
public function testGetEncryptionData(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$permissions = ['print'];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 0, $permissions, 'alpha', 'beta');
|
||||
$result = $encrypt->getEncryptionData();
|
||||
$this->assertEquals(2_147_422_008, $result['protection']);
|
||||
$this->assertEquals(1, $result['V']);
|
||||
$this->assertEquals(40, $result['Length']);
|
||||
$this->assertEquals('V2', $result['CF']['CFM']);
|
||||
});
|
||||
}
|
||||
|
||||
public function testGetObjectKey(): void
|
||||
{
|
||||
$permissions = ['print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'];
|
||||
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 2, $permissions, 'alpha', 'beta');
|
||||
$result = $encrypt->getObjectKey(123);
|
||||
$this->assertEquals('93879594941619c98047c404192b977d', \bin2hex($result));
|
||||
}
|
||||
|
||||
public function testGetUserPermissionCode(): void
|
||||
{
|
||||
$permissions = [
|
||||
'owner',
|
||||
'print',
|
||||
'modify',
|
||||
'copy',
|
||||
'annot-forms',
|
||||
'fill-forms',
|
||||
'extract',
|
||||
'assemble',
|
||||
'print-high',
|
||||
];
|
||||
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
$result = $encrypt->getUserPermissionCode($permissions, 0);
|
||||
$this->assertEquals(2_147_421_954, $result);
|
||||
}
|
||||
|
||||
public function testGetUserPermissionCodeIgnoreInvalidPermission(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
$result = $encrypt->getUserPermissionCode(['invalid-permission'], 0);
|
||||
$this->assertEquals(2_147_422_012, $result);
|
||||
}
|
||||
|
||||
public function testConvertHexStringToString(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
|
||||
$result = $encrypt->convertHexStringToString('');
|
||||
$this->assertEquals('', $result);
|
||||
|
||||
$result = $encrypt->convertHexStringToString('68656c6c6f20776f726c64');
|
||||
$this->assertEquals('hello world', $result);
|
||||
|
||||
$result = $encrypt->convertHexStringToString('68656c6c6f20776f726c642');
|
||||
$this->assertEquals('hello world ', $result);
|
||||
}
|
||||
|
||||
public function testConvertStringToHexString(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
|
||||
$result = $encrypt->convertStringToHexString('');
|
||||
$this->assertEquals('', $result);
|
||||
|
||||
$result = $encrypt->convertStringToHexString('hello world');
|
||||
$this->assertEquals('68656c6c6f20776f726c64', $result);
|
||||
}
|
||||
|
||||
public function testEncodeNameObject(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
|
||||
$result = $encrypt->encodeNameObject('');
|
||||
$this->assertEquals('', $result);
|
||||
|
||||
$result = $encrypt->encodeNameObject('059akzAKZ#_=-');
|
||||
$this->assertEquals('059akzAKZ#_=-', $result);
|
||||
|
||||
$result = $encrypt->encodeNameObject('059[]{}+~*akzAKZ#_=-');
|
||||
$this->assertEquals('059#5B#5D#7B#7D#2B#7E#2AakzAKZ#_=-', $result);
|
||||
}
|
||||
|
||||
public function testEscapeString(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
|
||||
$result = $encrypt->escapeString('');
|
||||
$this->assertEquals('', $result);
|
||||
|
||||
$result = $encrypt->escapeString('hello world');
|
||||
$this->assertEquals('hello world', $result);
|
||||
|
||||
$result = $encrypt->escapeString('(hello world) slash \\' . \chr(13));
|
||||
$this->assertEquals('\\(hello world\\) slash \\\\\r', $result);
|
||||
}
|
||||
|
||||
public function testEncryptStringDisabled(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
|
||||
$result = $encrypt->encryptString('');
|
||||
$this->assertEquals('', $result);
|
||||
|
||||
$result = $encrypt->encryptString('hello world');
|
||||
$this->assertEquals('hello world', $result);
|
||||
|
||||
$result = $encrypt->encryptString('(hello world) slash \\' . \chr(13) . \chr(250));
|
||||
$this->assertEquals('(hello world) slash \\' . \chr(13) . \chr(250), $result);
|
||||
}
|
||||
|
||||
public function testEncryptStringEnabled(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$permissions = [
|
||||
'print',
|
||||
'modify',
|
||||
'copy',
|
||||
'annot-forms',
|
||||
'fill-forms',
|
||||
'extract',
|
||||
'assemble',
|
||||
'print-high',
|
||||
];
|
||||
|
||||
$enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 0, $permissions, 'alpha');
|
||||
$result = $enc->encryptString('(hello world) slash \\' . \chr(13));
|
||||
$this->assertEquals('728cc693be1e4c1fb6b7e7b2a34644ad', \md5($result));
|
||||
|
||||
$enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 1, $permissions, 'alpha', 'beta');
|
||||
$result = $enc->encryptString('(hello world) slash \\' . \chr(13));
|
||||
$this->assertEquals('258ad774ddeec21b3b439a720df18e0d', \md5($result));
|
||||
});
|
||||
}
|
||||
|
||||
public function testEscapeDataStringDisabled(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
|
||||
|
||||
$result = $encrypt->escapeDataString('');
|
||||
$this->assertEquals('()', $result);
|
||||
|
||||
$result = $encrypt->escapeDataString('hello world');
|
||||
$this->assertEquals('(hello world)', $result);
|
||||
|
||||
$result = $encrypt->escapeDataString('(hello world) slash \\' . \chr(13));
|
||||
$this->assertEquals('(\\(hello world\\) slash \\\\\r)', $result);
|
||||
}
|
||||
|
||||
public function testEscapeDataStringEnabled(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$permissions = [
|
||||
'print',
|
||||
'modify',
|
||||
'copy',
|
||||
'annot-forms',
|
||||
'fill-forms',
|
||||
'extract',
|
||||
'assemble',
|
||||
'print-high',
|
||||
];
|
||||
|
||||
$enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 0, $permissions, 'alpha');
|
||||
$result = $enc->escapeDataString('(hello world) slash \\' . \chr(13));
|
||||
$this->assertEquals('24f60765c1c07a44fc3c9b44d2f55dbc', \md5($result));
|
||||
|
||||
$enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 1, $permissions, 'alpha', 'beta');
|
||||
$result = $enc->escapeDataString('(hello world) slash \\' . \chr(13));
|
||||
$this->assertEquals('ebc28272f4aff661fa0b7764d791fb79', \md5($result));
|
||||
});
|
||||
}
|
||||
|
||||
public function testGetFormattedDate(): void
|
||||
{
|
||||
$permissions = ['print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'];
|
||||
|
||||
$enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(false);
|
||||
$result = $enc->getFormattedDate();
|
||||
$this->assertEquals('(D:', \substr($result, 0, 3));
|
||||
$this->assertEquals("+00'00')", \substr($result, -8));
|
||||
|
||||
$this->bcRunIgnoringUserDeprecations(function () use ($permissions): void {
|
||||
$enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 0, $permissions, 'alpha');
|
||||
$result = $enc->getFormattedDate();
|
||||
$this->assertNotEmpty($result);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OutputTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* Output Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class OutputTest extends TestUtil
|
||||
{
|
||||
/** @param array<string,mixed> $data */
|
||||
protected function setRawEncryptData(OutputTestDouble $output, array $data): void
|
||||
{
|
||||
$property = new \ReflectionProperty(\Com\Tecnick\Pdf\Encrypt\Output::class, 'encryptdata');
|
||||
$property->setValue($output, $data);
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
protected function getRawEncryptData(OutputTestDouble $output): array
|
||||
{
|
||||
$property = new \ReflectionProperty(\Com\Tecnick\Pdf\Encrypt\Output::class, 'encryptdata');
|
||||
/** @var array<string,mixed> */
|
||||
return $property->getValue($output);
|
||||
}
|
||||
|
||||
protected function getOutputTestDouble(): OutputTestDouble
|
||||
{
|
||||
return new OutputTestDouble();
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjZero(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 0, ['print'], 'alpha', 'beta');
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$expected =
|
||||
'3132332030206f626a0a3c3c0a2f46696c746572202f5374616e646172640a2f5620310a2f4c656e6774682034300a2'
|
||||
. 'f5220320a2f4f20280542fa0e15496869a825cd08c633ac10675c5c02167661241f5369895d768278b1290a2f552028550539dc185'
|
||||
. 'e79d4c676f803babbdc50acf8a4427d2de5303d59e7c315b30eba290a2f5020323134373432323030380a2f456e63727970744d657'
|
||||
. '4616461746120747275650a3e3e0a656e646f626a0a';
|
||||
$this->assertEquals($expected, \bin2hex($result));
|
||||
});
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjOne(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 1, ['print'], 'alpha', 'beta');
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 150);
|
||||
});
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjTwo(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 2, ['print'], 'alpha', 'beta');
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 200);
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjThree(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 300);
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjThreePub(): void
|
||||
{
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta', $pubkeys);
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 200);
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjOnePub(): void
|
||||
{
|
||||
$this->bcRunIgnoringUserDeprecations(function (): void {
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
1,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
$pubkeys,
|
||||
);
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 100);
|
||||
});
|
||||
}
|
||||
|
||||
public function testGetPdfEncryptionObjFour(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 4, ['print'], 'alpha', 'beta');
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 300);
|
||||
$this->assertStringContainsString('/V 6', $result);
|
||||
$this->assertStringContainsString('/R 6', $result);
|
||||
$this->assertStringContainsString('/Length 256', $result);
|
||||
}
|
||||
|
||||
/** Issue 1: EFF entry must appear for V >= 4 when embedded file encryption is enabled. */
|
||||
public function testGetPdfEncryptionObjEff(): void
|
||||
{
|
||||
// V >= 4 (mode 2 = AES-128, V=4) with embedded file encryption enabled (default)
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
2,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
null,
|
||||
true, // encryptMetadata
|
||||
true, // encryptEmbeddedFiles
|
||||
);
|
||||
$pon = 0;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertStringContainsString('/EFF /StdCF', $result);
|
||||
}
|
||||
|
||||
/** Issue 1: No EFF entry when embedded file encryption is disabled. */
|
||||
public function testGetPdfEncryptionObjNoEff(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
2,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
null,
|
||||
true, // encryptMetadata
|
||||
false, // encryptEmbeddedFiles = false
|
||||
);
|
||||
$pon = 0;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertStringNotContainsString('/EFF', $result);
|
||||
}
|
||||
|
||||
/** Issue 3: EncryptMetadata=false must appear in standard-mode output. */
|
||||
public function testGetPdfEncryptionObjEncryptMetadataFalse(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(
|
||||
true,
|
||||
\md5('file_id'),
|
||||
3,
|
||||
['print'],
|
||||
'alpha',
|
||||
'beta',
|
||||
null,
|
||||
false, // encryptMetadata = false
|
||||
);
|
||||
$pon = 0;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertStringContainsString('/EncryptMetadata false', $result);
|
||||
}
|
||||
|
||||
/** Issue 3: EncryptMetadata=true (default) must appear as true in output. */
|
||||
public function testGetPdfEncryptionObjEncryptMetadataTrue(): void
|
||||
{
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 3, ['print'], 'alpha', 'beta');
|
||||
$pon = 0;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertStringContainsString('/EncryptMetadata true', $result);
|
||||
}
|
||||
|
||||
/** Issue 4: mode 4 pubkey output must contain Recipients. */
|
||||
public function testGetPdfEncryptionObjFourPub(): void
|
||||
{
|
||||
$pubkeys = [[
|
||||
'c' => __DIR__ . '/data/cert.pem',
|
||||
'p' => ['print'],
|
||||
]];
|
||||
$encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(true, \md5('file_id'), 4, ['print'], 'alpha', 'beta', $pubkeys);
|
||||
$pon = 122;
|
||||
$result = $encrypt->getPdfEncryptionObj($pon);
|
||||
$this->assertTrue(\strlen($result) > 200);
|
||||
$this->assertStringContainsString('/V 6', $result);
|
||||
}
|
||||
|
||||
public function testSetMissingValuesCopiesEncryptMetadataFalseToCf(): void
|
||||
{
|
||||
$output = $this->getOutputTestDouble();
|
||||
$data = $this->getRawEncryptData($output);
|
||||
if (!isset($data['CF']) || !\is_array($data['CF'])) {
|
||||
$this->fail('Missing CF array in encryptdata');
|
||||
}
|
||||
|
||||
/** @var array<string,mixed> $cfData */
|
||||
$cfData = $data['CF'];
|
||||
$data['EncryptMetadata'] = false;
|
||||
$cfData['EncryptMetadata'] = true;
|
||||
$data['CF'] = $cfData;
|
||||
$this->setRawEncryptData($output, $data);
|
||||
|
||||
$output->callSetMissingValues();
|
||||
|
||||
$result = $this->getRawEncryptData($output);
|
||||
if (!isset($result['CF']) || !\is_array($result['CF'])) {
|
||||
$this->fail('Missing CF array in encryptdata');
|
||||
}
|
||||
|
||||
/** @var array<string,mixed> $cfData */
|
||||
$cfData = $result['CF'];
|
||||
if (!\array_key_exists('EncryptMetadata', $cfData) || !\is_bool($cfData['EncryptMetadata'])) {
|
||||
$this->fail('Missing boolean EncryptMetadata in CF array');
|
||||
}
|
||||
|
||||
$this->assertFalse($cfData['EncryptMetadata']);
|
||||
}
|
||||
|
||||
public function testSetMissingValuesCopiesEncryptMetadataTrueToCf(): void
|
||||
{
|
||||
$output = $this->getOutputTestDouble();
|
||||
$data = $this->getRawEncryptData($output);
|
||||
if (!isset($data['CF']) || !\is_array($data['CF'])) {
|
||||
$this->fail('Missing CF array in encryptdata');
|
||||
}
|
||||
|
||||
/** @var array<string,mixed> $cfData */
|
||||
$cfData = $data['CF'];
|
||||
$data['EncryptMetadata'] = true;
|
||||
$cfData['EncryptMetadata'] = false;
|
||||
$data['CF'] = $cfData;
|
||||
$this->setRawEncryptData($output, $data);
|
||||
|
||||
$output->callSetMissingValues();
|
||||
|
||||
$result = $this->getRawEncryptData($output);
|
||||
if (!isset($result['CF']) || !\is_array($result['CF'])) {
|
||||
$this->fail('Missing CF array in encryptdata');
|
||||
}
|
||||
|
||||
/** @var array<string,mixed> $cfData */
|
||||
$cfData = $result['CF'];
|
||||
if (!\array_key_exists('EncryptMetadata', $cfData) || !\is_bool($cfData['EncryptMetadata'])) {
|
||||
$this->fail('Missing boolean EncryptMetadata in CF array');
|
||||
}
|
||||
|
||||
$this->assertTrue($cfData['EncryptMetadata']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OutputTestDouble.php
|
||||
*
|
||||
* @since 2026-04-19
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
class OutputTestDouble extends \Com\Tecnick\Pdf\Encrypt\Output
|
||||
{
|
||||
public function callSetMissingValues(): void
|
||||
{
|
||||
$this->setMissingValues();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TestUtil.php
|
||||
*
|
||||
* @since 2020-12-19
|
||||
* @category Library
|
||||
* @package file
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test Util
|
||||
*
|
||||
* @since 2020-12-19
|
||||
* @category Library
|
||||
* @package file
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*/
|
||||
class TestUtil extends TestCase
|
||||
{
|
||||
/**
|
||||
* @param class-string<\Throwable> $exception
|
||||
*/
|
||||
public function bcExpectException(string $exception): void
|
||||
{
|
||||
parent::expectException($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback and assert that it triggers a matching user deprecation.
|
||||
*
|
||||
* @param callable():void $callback
|
||||
*/
|
||||
public function bcAssertUserDeprecationMessageMatches(string $pattern, callable $callback): void
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
\set_error_handler(static function (int $errno, string $errstr) use (&$messages): bool {
|
||||
if ($errno !== E_USER_DEPRECATED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$messages[] = $errstr;
|
||||
return true;
|
||||
});
|
||||
|
||||
try {
|
||||
$callback();
|
||||
} finally {
|
||||
\restore_error_handler();
|
||||
}
|
||||
|
||||
$this->assertNotEmpty($messages, 'Expected a user deprecation but none was triggered.');
|
||||
|
||||
foreach ($messages as $message) {
|
||||
if (\preg_match($pattern, $message) === 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->fail(
|
||||
'User deprecation message did not match pattern ' . $pattern . '. Got: ' . \implode(' | ', $messages),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback while swallowing user deprecations.
|
||||
*
|
||||
* @template T
|
||||
* @param callable():T $callback
|
||||
* @return T
|
||||
*/
|
||||
public function bcRunIgnoringUserDeprecations(callable $callback): mixed
|
||||
{
|
||||
\set_error_handler(static fn(int $errno): bool => $errno === E_USER_DEPRECATED);
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
\restore_error_handler();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AESTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* AES encryption Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class AESTest extends TestUtil
|
||||
{
|
||||
protected function getTestObject(): \Com\Tecnick\Pdf\Encrypt\Type\AES
|
||||
{
|
||||
return new \Com\Tecnick\Pdf\Encrypt\Type\AES();
|
||||
}
|
||||
|
||||
public function testEncrypt128(): void
|
||||
{
|
||||
$aes = $this->getTestObject();
|
||||
$data = 'alpha';
|
||||
$key = '0123456789abcdef'; // 16 bytes = 128 bit KEY
|
||||
|
||||
$enc_a = $aes->encrypt($data, $key);
|
||||
$enc_b = $aes->encrypt($data, $key, 'aes-128-cbc');
|
||||
$this->assertEquals(\strlen($enc_a), \strlen($enc_b));
|
||||
|
||||
$aesSixteen = new \Com\Tecnick\Pdf\Encrypt\Type\AESSixteen();
|
||||
$enc_c = $aesSixteen->encrypt($data, $key);
|
||||
$this->assertEquals(\strlen($enc_a), \strlen($enc_c));
|
||||
}
|
||||
|
||||
public function testEncrypt256(): void
|
||||
{
|
||||
$aes = $this->getTestObject();
|
||||
$data = 'alpha';
|
||||
$key = '0123456789abcdef0123456789abcdef'; // 32 bytes = 256 bit KEY
|
||||
|
||||
$enc_a = $aes->encrypt($data, $key, '');
|
||||
$enc_b = $aes->encrypt($data, $key, 'aes-256-cbc');
|
||||
$this->assertEquals(\strlen($enc_a), \strlen($enc_b));
|
||||
|
||||
$aesThirtytwo = new \Com\Tecnick\Pdf\Encrypt\Type\AESThirtytwo();
|
||||
$enc_c = $aesThirtytwo->encrypt($data, $key);
|
||||
$this->assertEquals(\strlen($enc_a), \strlen($enc_c));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES::encrypt() output = 16-byte IV + PKCS#7-padded ciphertext.
|
||||
* padded_len = ceil((n + 1)/16) * 16, so aligned input gets one full block.
|
||||
* Total = padded_len + 16.
|
||||
*
|
||||
* With the old truncation bug, pad() always produced 16 bytes, so every
|
||||
* plaintext — no matter how long — produced a 32-byte output. These tests
|
||||
* verify that the output size grows correctly with the plaintext length.
|
||||
*/
|
||||
public function testEncrypt128LongData(): void
|
||||
{
|
||||
$aes = $this->getTestObject();
|
||||
$key = '0123456789abcdef'; // 16 bytes = 128 bit KEY
|
||||
|
||||
// 17 bytes → padded to 32 → 32 ciphertext + 16 IV = 48
|
||||
$enc17 = $aes->encrypt(\str_repeat('x', 17), $key, 'aes-128-cbc');
|
||||
$this->assertSame(48, \strlen($enc17));
|
||||
|
||||
// 32 bytes → padded to 48 (full PKCS#7 block) → 48 + 16 = 64
|
||||
$enc32 = $aes->encrypt(\str_repeat('x', 32), $key, 'aes-128-cbc');
|
||||
$this->assertSame(64, \strlen($enc32));
|
||||
|
||||
// 33 bytes → padded to 48 → 48 + 16 = 64
|
||||
$enc33 = $aes->encrypt(\str_repeat('x', 33), $key, 'aes-128-cbc');
|
||||
$this->assertSame(64, \strlen($enc33));
|
||||
|
||||
// Short input must produce shorter output than long input.
|
||||
$encShort = $aes->encrypt('alpha', $key, 'aes-128-cbc'); // 5 bytes → 32
|
||||
$this->assertGreaterThan(\strlen($encShort), \strlen($enc33));
|
||||
}
|
||||
|
||||
public function testEncrypt256LongData(): void
|
||||
{
|
||||
$aes = $this->getTestObject();
|
||||
$key = '0123456789abcdef0123456789abcdef'; // 32 bytes = 256 bit KEY
|
||||
|
||||
// 17 bytes → padded to 32 → 32 ciphertext + 16 IV = 48
|
||||
$enc17 = $aes->encrypt(\str_repeat('x', 17), $key, 'aes-256-cbc');
|
||||
$this->assertSame(48, \strlen($enc17));
|
||||
|
||||
// 32 bytes → padded to 48 (full PKCS#7 block) → 48 + 16 = 64
|
||||
$enc32 = $aes->encrypt(\str_repeat('x', 32), $key, 'aes-256-cbc');
|
||||
$this->assertSame(64, \strlen($enc32));
|
||||
|
||||
// 100 bytes → padded to 112 → 112 + 16 = 128
|
||||
$enc100 = $aes->encrypt(\str_repeat('x', 100), $key, 'aes-256-cbc');
|
||||
$this->assertSame(128, \strlen($enc100));
|
||||
|
||||
$aesThirtytwo = new \Com\Tecnick\Pdf\Encrypt\Type\AESThirtytwo();
|
||||
$enc100b = $aesThirtytwo->encrypt(\str_repeat('x', 100), $key);
|
||||
$this->assertSame(\strlen($enc100), \strlen($enc100b));
|
||||
}
|
||||
|
||||
public function testEncryptException(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$aes = $this->getTestObject();
|
||||
$aes->encrypt('alpha', '12345', 'ERROR');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AESnopadTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use Com\Tecnick\Pdf\Encrypt\Type\AESnopad;
|
||||
|
||||
/**
|
||||
* AESnopad encryption Test
|
||||
*
|
||||
* Verifies that pad() extends data to the next multiple of BLOCKSIZE without
|
||||
* truncating, so that stream data longer than one AES block is not silently
|
||||
* discarded before encryption.
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class AESnopadTest extends TestUtil
|
||||
{
|
||||
/**
|
||||
* 32-byte key used throughout (exact multiple of BLOCKSIZE, no padding applied).
|
||||
*/
|
||||
private const KEY256 = '0123456789abcdef0123456789abcdef';
|
||||
|
||||
protected function getTestObject(): AESnopad
|
||||
{
|
||||
return new AESnopad();
|
||||
}
|
||||
|
||||
/**
|
||||
* AESnopad::encrypt() returns padded_len bytes of ciphertext (no IV prefix).
|
||||
* padded_len = ceil(n / 16) * 16, but if n % 16 == 0 then padded_len = n.
|
||||
*
|
||||
* The formula below matches that behaviour for all cases tested.
|
||||
*/
|
||||
private function expectedCiphertextLen(int $plainLen): int
|
||||
{
|
||||
$rem = $plainLen % AESnopad::BLOCKSIZE;
|
||||
return $rem === 0 ? $plainLen : $plainLen + (AESnopad::BLOCKSIZE - $rem);
|
||||
}
|
||||
|
||||
// --- output-length tests (validate pad() indirectly) ---
|
||||
|
||||
public function testEncryptOutputLenShortData(): void
|
||||
{
|
||||
// 5 bytes → padded to 16
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 5), self::KEY256);
|
||||
$this->assertSame($this->expectedCiphertextLen(5), \strlen($enc));
|
||||
}
|
||||
|
||||
public function testEncryptOutputLenExactlyOneBlock(): void
|
||||
{
|
||||
// 16 bytes → already a multiple, no extra padding → 16
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 16), self::KEY256);
|
||||
$this->assertSame($this->expectedCiphertextLen(16), \strlen($enc));
|
||||
}
|
||||
|
||||
public function testEncryptOutputLenJustOverOneBlock(): void
|
||||
{
|
||||
// 17 bytes → padded to 32
|
||||
// This test would fail with the old code (which truncated data to 16 bytes,
|
||||
// producing only 16 bytes of ciphertext regardless of input length).
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 17), self::KEY256);
|
||||
$this->assertSame($this->expectedCiphertextLen(17), \strlen($enc));
|
||||
}
|
||||
|
||||
public function testEncryptOutputLenTwoBlocks(): void
|
||||
{
|
||||
// 32 bytes → padded to 32
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 32), self::KEY256);
|
||||
$this->assertSame($this->expectedCiphertextLen(32), \strlen($enc));
|
||||
}
|
||||
|
||||
public function testEncryptOutputLenJustOverTwoBlocks(): void
|
||||
{
|
||||
// 33 bytes → padded to 48
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 33), self::KEY256);
|
||||
$this->assertSame($this->expectedCiphertextLen(33), \strlen($enc));
|
||||
}
|
||||
|
||||
public function testEncryptOutputLenLargeData(): void
|
||||
{
|
||||
// 100 bytes → padded to 112
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 100), self::KEY256);
|
||||
$this->assertSame($this->expectedCiphertextLen(100), \strlen($enc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Longer input must produce longer ciphertext.
|
||||
* With the old bug, both short and long inputs produced 16-byte ciphertext.
|
||||
*/
|
||||
public function testCiphertextGrowsWithPlaintext(): void
|
||||
{
|
||||
$aesnopad = $this->getTestObject();
|
||||
$key = self::KEY256;
|
||||
|
||||
$short = $aesnopad->encrypt(\str_repeat('a', 5), $key);
|
||||
$long = $aesnopad->encrypt(\str_repeat('a', 100), $key);
|
||||
|
||||
$this->assertGreaterThan(\strlen($short), \strlen($long));
|
||||
}
|
||||
|
||||
// --- AES-128-CBC variant ---
|
||||
|
||||
public function testEncryptAes128OutputLen(): void
|
||||
{
|
||||
// 17 bytes with aes-128-cbc → padded to 32
|
||||
$enc = $this->getTestObject()->encrypt(\str_repeat('x', 17), self::KEY256, AESnopad::IVECT, 'aes-128-cbc');
|
||||
$this->assertSame($this->expectedCiphertextLen(17), \strlen($enc));
|
||||
}
|
||||
|
||||
// --- deterministic output with fixed IV ---
|
||||
|
||||
public function testEncryptDeterministicWithFixedIv(): void
|
||||
{
|
||||
$aesnopad = $this->getTestObject();
|
||||
$data = \str_repeat('x', 32);
|
||||
$key = self::KEY256;
|
||||
|
||||
$enc1 = $aesnopad->encrypt($data, $key, AESnopad::IVECT, 'aes-256-cbc');
|
||||
$enc2 = $aesnopad->encrypt($data, $key, AESnopad::IVECT, 'aes-256-cbc');
|
||||
$this->assertSame($enc1, $enc2);
|
||||
}
|
||||
|
||||
// --- exception paths ---
|
||||
|
||||
public function testCheckCipherInvalidName(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$this->getTestObject()->checkCipher('des-cbc');
|
||||
}
|
||||
|
||||
public function testEncryptInvalidCipher(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$this->getTestObject()->encrypt('data', self::KEY256, AESnopad::IVECT, 'des-cbc');
|
||||
}
|
||||
|
||||
public function testCheckCipherUnavailable(): void
|
||||
{
|
||||
$missingCipher = null;
|
||||
$available = \openssl_get_cipher_methods();
|
||||
foreach (AESnopad::VALID_CIPHERS as $cipher) {
|
||||
if (\in_array($cipher, $available, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$missingCipher = $cipher;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($missingCipher === null) {
|
||||
$this->markTestSkipped('All AESnopad valid ciphers are available on this runtime.');
|
||||
}
|
||||
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$this->getTestObject()->checkCipher($missingCipher);
|
||||
}
|
||||
|
||||
public function testDecryptInvalidCiphertextLength(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$this->getTestObject()->decrypt('short', self::KEY256, AESnopad::IVECT, 'aes-256-cbc');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* MDFiveSixteenTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* MD5-16 encryption Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class MDFiveSixteenTest extends TestUtil
|
||||
{
|
||||
protected function getTestObject(): \Com\Tecnick\Pdf\Encrypt\Type\MDFiveSixteen
|
||||
{
|
||||
return new \Com\Tecnick\Pdf\Encrypt\Type\MDFiveSixteen();
|
||||
}
|
||||
|
||||
public function testEncrypt(): void
|
||||
{
|
||||
$mdFiveSixteen = $this->getTestObject();
|
||||
$result = $mdFiveSixteen->encrypt('hello');
|
||||
$this->assertEquals('5d41402abc4b2a76b9719d911017c592', \bin2hex($result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RCFourTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* RC4 encryption Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class RCFourTest extends TestUtil
|
||||
{
|
||||
protected function getTestObject(): \Com\Tecnick\Pdf\Encrypt\Type\RCFour
|
||||
{
|
||||
return new \Com\Tecnick\Pdf\Encrypt\Type\RCFour();
|
||||
}
|
||||
|
||||
public function testEncrypt40(): void
|
||||
{
|
||||
$rcFour = $this->getTestObject();
|
||||
$data = 'alpha';
|
||||
$key = '12345'; // 5 bytes = 40 bit KEY
|
||||
|
||||
$enc_a = $rcFour->encrypt($data, $key, '');
|
||||
$enc_b = $rcFour->encrypt($data, $key, 'RC4-40');
|
||||
$this->assertEquals($enc_a, $enc_b);
|
||||
|
||||
$rcFourFive = new \Com\Tecnick\Pdf\Encrypt\Type\RCFourFive();
|
||||
$enc_c = $rcFourFive->encrypt($data, $key);
|
||||
$this->assertEquals($enc_a, $enc_c);
|
||||
}
|
||||
|
||||
public function testEncrypt128(): void
|
||||
{
|
||||
$rcFour = $this->getTestObject();
|
||||
$data = 'alpha';
|
||||
$key = '0123456789abcdef'; // 16 bytes = 128 bit KEY
|
||||
|
||||
$enc_a = $rcFour->encrypt($data, $key);
|
||||
$enc_b = $rcFour->encrypt($data, $key, 'RC4');
|
||||
$this->assertEquals($enc_a, $enc_b);
|
||||
|
||||
$rcFourSixteen = new \Com\Tecnick\Pdf\Encrypt\Type\RCFourSixteen();
|
||||
$enc_c = $rcFourSixteen->encrypt($data, $key);
|
||||
$this->assertEquals($enc_a, $enc_c);
|
||||
}
|
||||
|
||||
public function testEncryptException(): void
|
||||
{
|
||||
$this->bcExpectException(\Com\Tecnick\Pdf\Encrypt\Exception::class);
|
||||
$rcFour = $this->getTestObject();
|
||||
$rcFour->encrypt('alpha', '12345', 'ERROR');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SeedTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*
|
||||
* This file is part of tc-lib-pdf-encrypt software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* Seed Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfEncrypt
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-encrypt
|
||||
*/
|
||||
class SeedTest extends TestUtil
|
||||
{
|
||||
protected function getTestObject(): \Com\Tecnick\Pdf\Encrypt\Type\Seed
|
||||
{
|
||||
return new \Com\Tecnick\Pdf\Encrypt\Type\Seed();
|
||||
}
|
||||
|
||||
public function testEncrypt(): void
|
||||
{
|
||||
$seed = $this->getTestObject();
|
||||
$result = $seed->encrypt('hello', 'world');
|
||||
$this->assertNotEmpty($result);
|
||||
}
|
||||
|
||||
public function testEncryptRaw(): void
|
||||
{
|
||||
$seed = $this->getTestObject();
|
||||
$result = $seed->encrypt('hello', 'world', 'raw');
|
||||
$this->assertNotEmpty($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALtxWB+0PKdvXMih
|
||||
p8+xkg4BzMH8MDyNQNqv56mhrQ5wM1icQaoB1dXWUZj8NRPgvzkhw7xJ98P3Kka7
|
||||
mLA2qDNGh77H8aYc64pIII8Xd1iQMbHVrUqtsEec7ZCHL3U63rLeERCqu+eibQl4
|
||||
2w14bdflcMun0xHN/aJyA7sJWmInAgMBAAECgYB8dGNomYl0OpLGe98qHeK1/ifv
|
||||
3PxCGB+plAYjMT/wSDsvaICI2rMaSjTfeQXc7urIikymJg6mROQDFufoiEHgtliz
|
||||
mQeQIss6xPoBeObRBptjPBnmREnB3pP1nBngDmDQj/BwDsxbHXiZxRvQNvvKllyE
|
||||
vF0i9ZjgT+Bh1/pKoQJBAOiEFZTZxMF+Ri3VVMEhV8CkMSom4815OPg+5RhS/mwX
|
||||
X5bBG6AdLbOYgvq1iMGnjRZs9Vquk+uzmpMw+Uds8rkCQQDOX9m2t2O51/QyROIY
|
||||
obJGHp4fyAtGHH/qbyFg3L7c/LolqqDnq+26tP7+UdKXMZeGMs1FeL4crLcpJab8
|
||||
rQvfAkA7zJScVrGCrg4whXgpv4CJG/FFDQFze+TJ+6sB6X5joFNGO132fOqfEO9G
|
||||
uV91prjjdpxXeSSz7tonVuYVl5CJAkBW8xNrZEDkIBSGyXbpIddWq7e7dDErzP4n
|
||||
68KIaPkRanmTPRyl/04eB/wXcqnjgcVxiR3rUz/mHO7eqzp74vzJAkB+O8VRe350
|
||||
4I9Ut/gZmUW3IiSVT2FfZezk2XoB3X+zFKfhdhNt0i8EUwoTTI1mwy3nUyqMc/sb
|
||||
JhEpQgcZ31SP
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICsDCCAhmgAwIBAgIUAQb8DRdrYNBU56/iGjwLAKJ3dLQwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCVUsxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEjMCEGCSqGSIb3DQEJARYUdGVzdEBl
|
||||
eGFtcGxlLmludmFsaWQwHhcNMjIxMjIxMTExNjA5WhcNMzIxMjE4MTExNjA5WjBq
|
||||
MQswCQYDVQQGEwJVSzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMSMwIQYJKoZIhvcNAQkBFhR0ZXN0QGV4YW1w
|
||||
bGUuaW52YWxpZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAu3FYH7Q8p29c
|
||||
yKGnz7GSDgHMwfwwPI1A2q/nqaGtDnAzWJxBqgHV1dZRmPw1E+C/OSHDvEn3w/cq
|
||||
RruYsDaoM0aHvsfxphzrikggjxd3WJAxsdWtSq2wR5ztkIcvdTrest4REKq756Jt
|
||||
CXjbDXht1+Vwy6fTEc39onIDuwlaYicCAwEAAaNTMFEwHQYDVR0OBBYEFLULVT3y
|
||||
ounwqaWxwEaFOs7s0+DFMB8GA1UdIwQYMBaAFLULVT3younwqaWxwEaFOs7s0+DF
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAJ8Gh0uOLLZA3UsmL
|
||||
eUxOrS19QbfNuDzfayiOuLwiMCKCY29ch/HEaKPdvUuGAbFczhnryJLsb6TYBBeP
|
||||
a4zUcyhylafqZH450awKEOxd/5ns0QoWlFeBkgVSHK/j7BP8P9LXZkjM4nLJXoAl
|
||||
M/SOSeyHUEHfE97284X8VaNPCT4=
|
||||
-----END CERTIFICATE-----
|
||||
Reference in New Issue
Block a user