OpenSSLFileEncryption
File-level symmetric encryption powered by OpenSSL.
Security properties of this class
What it guarantees for files written by encrypt() (format V2):
- Confidentiality — AES-256-GCM. Without the passphrase, the plaintext is computationally infeasible to recover.
- Integrity / authenticity — every V2 file carries a 16-byte GCM
authentication tag. Any tampering with the ciphertext makes
decrypt()throwRuntimeExceptioninstead of returning corrupted plaintext. - Brute-force resistance on the passphrase — the encryption key is
derived via Argon2id (if
ext-sodiumis loaded) or PBKDF2-SHA256 with 600 000 iterations. A per-file random salt prevents rainbow tables. - Per-file uniqueness — a fresh random salt (16 B) + IV (12 B) is
generated for each call to
encrypt(). Encrypting the same plaintext twice with the same passphrase produces two unrelated ciphertexts.
What it does NOT guarantee:
- Forward secrecy: there is no per-session key. Anyone with the passphrase can decrypt every past file encrypted with that passphrase.
- Key revocation: changing the passphrase does not invalidate old files. You must re-encrypt them with the new passphrase.
- Protection from a compromised endpoint: if an attacker can read PHP
memory while the passphrase is in use, they can recover it. The
__destruct()cleanup is best-effort. - Protection from the user choosing a weak passphrase: KDF only slows brute-force; it does not prevent it. Use long, random passphrases for sensitive data.
Backward compatibility (legacy format)
Files written by oihana/php-files ≤ 1.0 use the legacy V1 format:
a raw IV followed by AES-CBC ciphertext, with the passphrase used directly
(zero-padded) as the key, no MAC. Those files have no integrity protection,
but they remain readable: decrypt() auto-detects the absence of the
V2 magic header and falls back to the legacy code path.
encrypt() always produces V2. To migrate a legacy file, simply call
decrypt() (reads V1) then encrypt() (writes V2).
Tags
Table of Contents
Properties
- $ivLength : int
- The IV length of the legacy cipher (used for V1 backward-compat).
- $cipher : string
- The cipher method used to decrypt legacy V1 files.
- $maxInputBytes : int|null
- Optional cap on the size of the input files for {@see encrypt()} / {@see decrypt()}.
- $passphrase : string
- The passphrase used for encryption and decryption.
Methods
- __construct() : mixed
- Constructor.
- __destruct() : mixed
- Destructor.
- decrypt() : string
- Decrypts a previously encrypted file.
- encrypt() : string
- Encrypts a file in V2 format (AES-256-GCM + KDF + magic header).
- hasEncryptedFileSize() : bool
- Checks if a file is large enough to *possibly* be a legacy V1 encrypted file.
- isEncryptedFile() : bool
- Heuristically checks whether a file appears to be encrypted by this class.
- assertWithinMaxInputBytes() : void
- Rejects an input file whose size exceeds the configured `$maxInputBytes` cap.
- decryptLegacy() : string
- Decrypts a legacy V1 payload (no magic, no MAC, raw passphrase as key).
- decryptV2() : string
- Decrypts a V2 payload: reads the header, derives the key, validates the tag.
- isV2Payload() : bool
- Inspects the first bytes of an encrypted payload to determine whether it is a V2 file (carries magic + version + KDF).
Properties
$ivLength
The IV length of the legacy cipher (used for V1 backward-compat).
public
int
$ivLength
Hooks
public
int
get
$cipher
The cipher method used to decrypt legacy V1 files.
private
string
$cipher
$maxInputBytes
Optional cap on the size of the input files for {@see encrypt()} / {@see decrypt()}.
private
int|null
$maxInputBytes
null means no limit.
$passphrase
The passphrase used for encryption and decryption.
private
string
$passphrase
Methods
__construct()
Constructor.
public
__construct(string $passphrase[, string $cipher = EncryptionFormat::LEGACY_CIPHER ][, int|null $maxInputBytes = null ]) : mixed
Parameters
- $passphrase : string
-
Secret used to derive the encryption key. Must be non-empty.
- $cipher : string = EncryptionFormat::LEGACY_CIPHER
-
Cipher used to decrypt legacy V1 files. New V2 files always use EncryptionFormat::DEFAULT_CIPHER. Default
aes-256-cbcmatches the historical behaviour of this class. - $maxInputBytes : int|null = null
-
Optional cap on the size of the files read by encrypt() and decrypt(). When set, a file whose size exceeds this value is rejected before being read into memory (RuntimeException). Default
null(no limit — historical behaviour). Useful as a defensive guard against OOM when processing untrusted inputs.
Tags
__destruct()
Destructor.
public
__destruct() : mixed
Best-effort wiping of the passphrase from memory. Uses sodium_memzero()
when available (true in-place wipe), falling back to a string overwrite
(which only releases the current reference — older copies created by
PHP's copy-on-write may persist in RAM).
For strongly-secured deployments, prefer wiping the passphrase at the
application level (e.g. immediately after encrypt() / decrypt())
rather than relying on this destructor.
decrypt()
Decrypts a previously encrypted file.
public
decrypt(string $inputFile[, string|null $outputFile = null ]) : string
Auto-detects the format by inspecting the first EncryptionFormat::HEADER_LENGTH bytes:
- If the file begins with
'OPHE\x02'followed by a known KDF byte, it is treated as V2: the salt, IV, ciphertext and authentication tag are extracted; the tag is verified byopenssl_decrypt()before the plaintext is returned. - Otherwise the file is treated as legacy V1: the first
ivLengthbytes are the IV, the rest is the AES-CBC ciphertext, and the passphrase is used directly as the key.
Either path produces the same exception type on failure:
RuntimeException with the same generic message (no oracle on
"wrong passphrase" vs "tampered ciphertext").
Parameters
- $inputFile : string
-
Path to the encrypted file.
- $outputFile : string|null = null
-
Optional output path. If null, strips
.encfrom$inputFile.
Tags
Return values
string —Path to the decrypted output file.
encrypt()
Encrypts a file in V2 format (AES-256-GCM + KDF + magic header).
public
encrypt(string $inputFile[, string|null $outputFile = null ]) : string
Steps:
- Generate a 16-byte salt and a 12-byte IV with
random_bytes(). - Derive a 32-byte key from the passphrase + salt via the best
available KDF (Argon2id if
ext-sodiumis loaded, PBKDF2 otherwise). - Encrypt with AES-256-GCM, obtaining a 16-byte authentication tag.
- Write
MAGIC | VERSION | KDF | salt | IV | ciphertext | tagto the output.
Parameters
- $inputFile : string
-
Path to the plaintext input file.
- $outputFile : string|null = null
-
Optional output path. If null, appends
.encto$inputFile.
Tags
Return values
string —Path to the encrypted output file.
hasEncryptedFileSize()
Checks if a file is large enough to *possibly* be a legacy V1 encrypted file.
public
hasEncryptedFileSize(string $filePath) : bool
Verifies that the file has at least enough bytes to contain a legacy IV. This is a cheap size-only check; it does NOT validate the V2 magic or confirm anything about the content. Use isEncryptedFile() for a heuristic content check.
Parameters
- $filePath : string
-
Path to the file to check.
Return values
bool —True if the file exists and is at least ivLength bytes long.
isEncryptedFile()
Heuristically checks whether a file appears to be encrypted by this class.
public
isEncryptedFile(string $filePath) : bool
For files written by encrypt() (V2), simply checks for the magic header.
For legacy V1 files (no magic), falls back to the historical heuristic:
- file at least
ivLengthbytes long; - IV not entirely zero;
- IV not dominated by printable characters (which would indicate plaintext).
This method is best-effort. Some plaintext binary files (e.g. arbitrary compressed data) may pass the legacy heuristic. Always treat its result as a hint, never as a guarantee that decryption will succeed.
Parameters
- $filePath : string
-
Path to the file to check.
Return values
bool —True if the file likely contains encrypted content.
assertWithinMaxInputBytes()
Rejects an input file whose size exceeds the configured `$maxInputBytes` cap.
private
assertWithinMaxInputBytes(string $inputFile) : void
No-op when $maxInputBytes is null (default).
Parameters
- $inputFile : string
Tags
decryptLegacy()
Decrypts a legacy V1 payload (no magic, no MAC, raw passphrase as key).
private
decryptLegacy(string $data) : string
Kept for backward-compatibility only. Files in this format have no tampering detection; the integrity of the plaintext depends on the integrity of the storage medium.
Parameters
- $data : string
Return values
stringdecryptV2()
Decrypts a V2 payload: reads the header, derives the key, validates the tag.
private
decryptV2(string $data) : string
Parameters
- $data : string
Return values
stringisV2Payload()
Inspects the first bytes of an encrypted payload to determine whether it is a V2 file (carries magic + version + KDF).
private
isV2Payload(string $data) : bool
Parameters
- $data : string