openssl
Table of Contents
Namespaces
Classes
- OpenSSLFileEncryption
- File-level symmetric encryption powered by OpenSSL.
Functions
- assertBlockSizeExponent() : int
- Asserts that a V3 block-size exponent is one this library will act on, and returns it.
- availableMemoryBytes() : int|null
- Returns the number of bytes this process may reasonably allocate, or `null` when it cannot be told.
- deriveKey() : string
- Derives a 32-byte symmetric encryption key from a passphrase using a slow KDF.
- bestAvailableKdf() : int
- Returns the strongest KDF algorithm available in the current PHP environment.
- encryptionBlockAad() : string
- Builds the associated data that binds one V3 block to its file, its position and the end of the stream.
- encryptionBlockNonce() : string
- Builds the GCM nonce of one V3 block: the file's random prefix followed by the block index.
- isAeadCipher() : bool
- Determines whether an OpenSSL cipher name designates an AEAD mode.
- kdfIsAvailable() : bool
- Tells whether the KDF a file names can actually be run here.
- parseMemoryLimit() : int|null
- Parses a `memory_limit`-style size into bytes, or `null` when the value names no usable ceiling.
Functions
assertBlockSizeExponent()
Asserts that a V3 block-size exponent is one this library will act on, and returns it.
assertBlockSizeExponent(int $exponent) : int
The exponent is a single byte of the V3 header, so a file can declare any value from 0 to 255 — and the reader meets it before a single byte has been authenticated. Both ends of the range are hostile:
- Too small. An exponent of 0 asks for 1-byte blocks, which turns a 100 MB file
into a hundred million frames, each with its own 16-byte tag and its own
openssl_decrypt()call. The file would be 1.6 GB of tags and the read would run for hours. EncryptionFormat::MIN_BLOCK_SIZE_EXPONENT (64 KiB) is the floor. - Too large. An exponent of 255 asks the reader to allocate 2^255 bytes for the
first block. EncryptionFormat::MAX_BLOCK_SIZE_EXPONENT (1 GiB) is the
ceiling, and
1 << 255is undefined ground this never reaches.
Called on both sides: on write so the library never produces a file it would refuse to read, and on read so a crafted header is refused before it costs anything.
Parameters
- $exponent : int
-
The block-size exponent, as read from a header or chosen for one.
Tags
Return values
int —The same exponent, so it can be validated in place: 1 << assertBlockSizeExponent( $e ).
availableMemoryBytes()
Returns the number of bytes this process may reasonably allocate, or `null` when it cannot be told.
availableMemoryBytes([string|null $memoryLimit = null ][, string $meminfoPath = '/proc/meminfo' ]) : int|null
Why this exists. The legacy V1/V2 read path loads the whole file into memory
and peaks at about EncryptionFormat::LEGACY_MEMORY_FACTOR
times its size. Past the budget, PHP either throws a fatal "Allowed memory size
exhausted" or — when memory_limit is -1 — the kernel OOM killer takes the
process down with no exception and no stack trace, so nothing can be reported.
Measured on 2026-10-24 in a 3.8 GB VM: a 1 GB file logged
Out of memory: Killed process (php) in dmesg and nothing anywhere else.
Knowing the budget is what lets OpenSSLFileEncryption::decrypt() refuse
with a message instead of disappearing.
Two sources, in order:
memory_limit— the limit PHP enforces itself, read through parseMemoryLimit. Any positive value wins: it is the ceiling that actually applies, however much RAM the host has./proc/meminfo'sMemAvailable— consulted only whenmemory_limitis-1(unlimited), where the real ceiling is the machine's free memory. Linux only; elsewhere the function returnsnull.
null means unknown, not unlimited: callers should let the operation through
rather than refuse on a number they do not have.
Parameters
- $memoryLimit : string|null = null
-
A
memory_limit-style value to read instead of the ini setting ('512M','-1','1G'…). Testing seam;nullreadsini_get('memory_limit'). - $meminfoPath : string = '/proc/meminfo'
-
Path to read
MemAvailablefrom. Testing seam.
Tags
Return values
int|null —The budget in bytes, or null when no source gives a figure.
deriveKey()
Derives a 32-byte symmetric encryption key from a passphrase using a slow KDF.
deriveKey(string $passphrase, string $salt, int $algorithm) : string
Why a KDF ? A passphrase like 'chocolat' has roughly 8 bytes of entropy.
Using it directly as an AES-256 key (zero-padded to 32 bytes) makes brute-force
trivial. A KDF deliberately slows the key derivation down (hundreds of
thousands of iterations or memory-hard computation) so that an attacker who
tries 1 million passphrases must do 1 million × N work — increasing brute-force
cost by orders of magnitude.
Supported algorithms (passed via $algorithm):
- EncryptionFormat::KDF_ARGON2ID — memory-hard, GPU-resistant.
Requires the
sodiumextension. Recommended when available. - EncryptionFormat::KDF_PBKDF2_SHA256 — universal fallback, 600 000 iterations of SHA-256 (OWASP 2023+).
Use bestAvailableKdf() to pick the strongest algorithm available in the current PHP environment.
The chosen algorithm is explicit so that the encrypted file can carry it and any reader (even on a different PHP build) can reproduce the derivation.
Security boundary: this function protects the cost of brute-forcing the passphrase. It does not protect against:
- a leaked salt + ciphertext + passphrase (anyone with all three can decrypt) ;
- a passphrase observable by an attacker (e.g. typed in front of a camera) ;
- side-channel attacks on the host (memory dump while passphrase is in RAM).
Parameters
- $passphrase : string
-
The user-provided passphrase. Must be non-empty.
- $salt : string
-
A per-file random salt of EncryptionFormat::SALT_LENGTH bytes.
- $algorithm : int
-
One of EncryptionFormat::KDF_ARGON2ID or EncryptionFormat::KDF_PBKDF2_SHA256.
Tags
Return values
string —A 32-byte raw binary key.
bestAvailableKdf()
Returns the strongest KDF algorithm available in the current PHP environment.
bestAvailableKdf() : int
- If
ext-sodiumis loaded → EncryptionFormat::KDF_ARGON2ID. - Otherwise → EncryptionFormat::KDF_PBKDF2_SHA256.
Use this when writing new encrypted files; for reading, the algorithm is dictated by the file header.
Tags
Return values
int —One of EncryptionFormat::KDF_ARGON2ID or EncryptionFormat::KDF_PBKDF2_SHA256.
encryptionBlockAad()
Builds the associated data that binds one V3 block to its file, its position and the end of the stream.
encryptionBlockAad(string $header, int $index, bool $isFinal) : string
AAD(i) = header (31 bytes) ‖ pack('N', i) (4 bytes) ‖ chr( isFinal ? 1 : 0 ) (1 byte)
This function is the integrity of the format. A GCM tag proves that a block was sealed with the key — nothing more. Seal each block with no associated data and every frame authenticates on its own, which leaves four rewrites of a file undetectable, all of them trivial to perform with no key at all:
| Attack | What stops it |
|---|---|
| Cut the file short | the final flag: the reader computes final from the cursor, so the new last frame was sealed as non-final and fails |
| Swap two frames | the block index |
| Drop a frame from the middle | the block index of every frame after it |
| Splice two files from the same passphrase | the header, whose salt and nonce prefix are per-file |
The flag is one byte rather than a bit of the index because it has to be decided on the read side, from the cursor position against the file size, before the block is opened. Writer and reader must agree on it, and that agreement is what a truncation breaks.
Parameters
- $header : string
-
The file's V3 header, exactly EncryptionFormat::V3_HEADER_LENGTH bytes.
- $index : int
-
The zero-based block index.
- $isFinal : bool
-
Whether this block is the last of the file.
Tags
Return values
string —The associated data to pass to openssl_encrypt() / openssl_decrypt().
encryptionBlockNonce()
Builds the GCM nonce of one V3 block: the file's random prefix followed by the block index.
encryptionBlockNonce(string $noncePrefix, int $index) : string
nonce(i) = NONCE PREFIX (8 bytes) ‖ pack('N', i) (4 bytes) = 12 bytes
Why a counter and not 12 random bytes per block. A nonce must never repeat under one key, and with AES-GCM a repeat is not a degradation but a collapse: two blocks sharing a nonce leak their XOR and, worse, hand an attacker the material to forge tags for any message under that key. Random 96-bit nonces collide by birthday after about 2^32 blocks; a counter cannot collide at all within a file, and the per-file random prefix keeps two files apart.
Which is why the index is bounded here. EncryptionFormat::BLOCK_COUNTER_LENGTH
is 4 bytes, and pack('N', 2**32) wraps silently back to zero — the one arithmetic
accident in this format that would reuse a nonce. At the default block size that
ceiling sits at 32 EiB, so the guard costs nothing and removes the accident.
Parameters
- $noncePrefix : string
-
The file's random prefix, exactly EncryptionFormat::NONCE_PREFIX_LENGTH bytes.
- $index : int
-
The zero-based block index.
Tags
Return values
string —The EncryptionFormat::GCM_IV_LENGTH-byte nonce of that block.
isAeadCipher()
Determines whether an OpenSSL cipher name designates an AEAD mode.
isAeadCipher(string $cipher) : bool
AEAD (Authenticated Encryption with Associated Data) ciphers compute an
authentication tag in addition to the ciphertext. PHP's openssl_encrypt()
/ openssl_decrypt() API requires the &$tag parameter for these ciphers;
without it, integrity is not protected.
Currently recognised modes:
- GCM (Galois/Counter Mode) — preferred for general use.
- CCM (Counter with CBC-MAC).
- OCB (Offset Codebook) — modern, less ubiquitous.
The check is purely textual on the cipher name (case-insensitive).
Parameters
- $cipher : string
-
Cipher name as returned by
openssl_get_cipher_methods(), e.g.'aes-256-gcm','aes-128-ccm','aes-256-cbc'.
Tags
Return values
bool —True if the cipher operates in an AEAD mode.
kdfIsAvailable()
Tells whether the KDF a file names can actually be run here.
kdfIsAvailable(int $algorithm[, bool|null $sodiumLoaded = null ]) : bool
Every encrypted file stores the identifier of the KDF its key was derived with, so
a reader never has to guess. What that byte cannot do is make the KDF available:
EncryptionFormat::KDF_ARGON2ID needs ext-sodium at decryption time exactly
as much as at encryption time, and ext-sodium is a suggestion of this package, not
a requirement.
Which produces the one case worth a function of its own: a file written on a host that has sodium, read on a host that does not. Measured on 2026-10-24, that read failed with "Decryption failed due to incorrect passphrase or corrupted data" — on an intact archive, with the right passphrase, because the real cause sat in a wrapped exception nobody prints. For someone restoring a backup, that message sends them looking for a lost passphrase or a corrupt file instead of a missing extension.
EncryptionFormat::KDF_PBKDF2_SHA256 is native to PHP, so it is available everywhere.
An unknown identifier answers true, and that is deliberate: it names no
implementation that could be missing. A byte that is neither of the two known
values is a corrupt or tampered header, or one from a newer release — all of which
deriveKey() must reject, and under the generic message, since a forged byte
must not get an answer of its own.
Parameters
- $algorithm : int
- $sodiumLoaded : bool|null = null
-
Whether
ext-sodiumis loaded. Testing seam;nullasksfunction_exists().
Tags
Return values
boolparseMemoryLimit()
Parses a `memory_limit`-style size into bytes, or `null` when the value names no usable ceiling.
parseMemoryLimit(string $value) : int|null
Accepts a plain byte count, optionally suffixed with K, M or G
(case-insensitive), with optional surrounding whitespace — the shapes PHP itself
reads as a size. null comes back for:
-1, the value that means unlimited: there is no ceiling to report;0or a negative count, which name no usable budget;- an empty string, or anything that is not a number followed by at most one of those three suffixes.
That last case includes '128MB', and deliberately. PHP reads a quantity's
multiplier from what follows the digits, accepts only a single k, m or g
there, and warns then ignores anything else — so memory_limit = 128MB is
128 bytes to PHP, not 128 mebibytes. Returning the number this function would
like it to mean would overstate the budget by a factor of a million; returning
null says unknown, which is the one answer that cannot mislead a caller.
Parameters
- $value : string
-
The raw setting value, e.g.
'512M','1G','134217728','-1'.
Tags
Return values
int|null —The size in bytes, or null when the value names no usable ceiling.