Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.24% |
20 / 21 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
| AesCipher | |
95.24% |
20 / 21 |
|
66.67% |
2 / 3 |
8 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
| encrypt | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
2.01 | |||
| decrypt | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Phpdftk\Crypt; |
| 6 | |
| 7 | /** |
| 8 | * AES-CBC cipher for PDF encryption (ISO 32000-2 ยง7.6.3). |
| 9 | * |
| 10 | * Supports 128-bit and 256-bit keys. The 16-byte IV is prepended to |
| 11 | * the ciphertext on encrypt and stripped on decrypt, per the PDF spec |
| 12 | * requirement that each encrypted string/stream carries its own IV. |
| 13 | */ |
| 14 | final class AesCipher implements CryptInterface |
| 15 | { |
| 16 | public function __construct(private int $keyBits = 128) |
| 17 | { |
| 18 | if ($keyBits !== 128 && $keyBits !== 256) { |
| 19 | throw new \InvalidArgumentException("keyBits must be 128 or 256, got $keyBits"); |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | public function encrypt(string $data, string $key): string |
| 24 | { |
| 25 | $iv = random_bytes(16); |
| 26 | $cipherMethod = "AES-{$this->keyBits}-CBC"; |
| 27 | // Pad or truncate key to required length |
| 28 | $keyBytes = $this->keyBits / 8; |
| 29 | $key = str_pad(substr($key, 0, $keyBytes), $keyBytes, "\x00"); |
| 30 | |
| 31 | $encrypted = openssl_encrypt($data, $cipherMethod, $key, OPENSSL_RAW_DATA, $iv); |
| 32 | if ($encrypted === false) { |
| 33 | throw new \RuntimeException('AES encryption failed: ' . openssl_error_string()); |
| 34 | } |
| 35 | return $iv . $encrypted; |
| 36 | } |
| 37 | |
| 38 | public function decrypt(string $data, string $key): string |
| 39 | { |
| 40 | if (strlen($data) < 16) { |
| 41 | throw new \RuntimeException('AES decrypt: data too short (missing IV)'); |
| 42 | } |
| 43 | $iv = substr($data, 0, 16); |
| 44 | $ciphertext = substr($data, 16); |
| 45 | $cipherMethod = "AES-{$this->keyBits}-CBC"; |
| 46 | $keyBytes = $this->keyBits / 8; |
| 47 | $key = str_pad(substr($key, 0, $keyBytes), $keyBytes, "\x00"); |
| 48 | |
| 49 | $decrypted = openssl_decrypt($ciphertext, $cipherMethod, $key, OPENSSL_RAW_DATA, $iv); |
| 50 | if ($decrypted === false) { |
| 51 | throw new \RuntimeException('AES decryption failed: ' . openssl_error_string()); |
| 52 | } |
| 53 | return $decrypted; |
| 54 | } |
| 55 | } |