Oihana PHP

EncryptionFormat uses ConstantsTrait

On-disk file format constants for {@see \oihana\files\openssl\OpenSSLFileEncryption}.

V3 is the chunked, streaming format produced by encrypt(). V2 (a single GCM blob) and V1 (CBC, no MAC, no magic) are read-only legacy formats.

Layout of a V3 file:

┌─────────┬─────────┬─────────┬──────────┬──────────┬──────────────┐
│ MAGIC   │ VERSION │ KDF     │ BLOCKEXP │ SALT     │ NONCE PREFIX │
│ 4 bytes │ 1 byte  │ 1 byte  │ 1 byte   │ 16 bytes │ 8 bytes      │
└─────────┴─────────┴─────────┴──────────┴──────────┴──────────────┘
followed by one or more frames:
┌──────────────────────────┬──────────┐
│ encrypted block          │ GCM TAG  │   × N
│ ≤ 2^BLOCKEXP bytes       │ 16 bytes │
└──────────────────────────┴──────────┘

The nonce of block i is NONCE PREFIX ‖ pack('N', i) — 8 + 4 = 12 bytes, the NIST-recommended GCM IV length. The counter never repeats under one key, so no two blocks of one file share a nonce.

Each block is authenticated with associated data binding the whole header, the block index and a final-block flag:

AAD(i) = header ‖ pack('N', i) ‖ chr( isFinal ? 1 : 0 )

Without that binding every frame would authenticate on its own, and truncating a file, reordering two frames or splicing two files together would all go undetected. The final flag is decided at read time by the cursor position against the file size, so a frame written as non-final but read as the last one fails its tag — which is exactly what truncation looks like.

Layout of a V2 file (read-only):

┌─────────┬─────────┬─────────┬──────────┬──────────┬──────────────────────┐
│ MAGIC   │ VERSION │ KDF     │ SALT     │ IV       │ ciphertext + TAG     │
│ 4 bytes │ 1 byte  │ 1 byte  │ 16 bytes │ 12 bytes │ variable + 16 bytes  │
└─────────┴─────────┴─────────┴──────────┴──────────┴──────────────────────┘

The KDF byte names how the encryption key was derived from the passphrase, so a reader never has to guess, and a file stays readable after this library's default changes.

What it does not do is make that 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 rather than a requirement — so a file written where it is loaded cannot be read where it is not. Measured on 2026-10-24, that read reported "incorrect passphrase or corrupted data" on an intact archive with the right passphrase; it now names the missing extension instead (kdfIsAvailable()). EncryptionFormat::KDF_PBKDF2_SHA256 is native to PHP and reads anywhere.

Tags
author

Marc Alcaraz (ekameleon)

since
1.0.0

Table of Contents

Constants

BLOCK_COUNTER_LENGTH  : int = 4
Length of the big-endian block counter appended to the nonce prefix, in bytes.
BLOCK_SIZE_EXPONENT_LENGTH  : int = 1
Length of the block-size-exponent byte of a V3 header (right after the KDF byte).
DEFAULT_BLOCK_SIZE_EXPONENT  : int = 23
Default block-size exponent written by `encrypt()`: 2^23 = 8 MiB.
DEFAULT_CIPHER  : string = 'aes-256-gcm'
Default AEAD cipher used when writing V2 files.
GCM_IV_LENGTH  : int = 12
AES-GCM IV length in bytes (12 = 96 bits, NIST SP 800-38D recommendation).
GCM_TAG_LENGTH  : int = 16
AES-GCM authentication tag length in bytes (16 = 128 bits, maximum security).
HEADER_LENGTH  : int = 6
Total length of the V2 format prefix (MAGIC + VERSION + KDF) in bytes.
KDF_ARGON2ID  : int = 1
KDF identifier: Argon2id (memory-hard, GPU-resistant).
KDF_LENGTH  : int = 1
Length of the KDF-identifier byte (right after the version).
KDF_PBKDF2_SHA256  : int = 2
KDF identifier: PBKDF2-SHA256.
KEY_LENGTH  : int = 32
Derived symmetric key length in bytes (32 = 256 bits, fits AES-256).
LEGACY_CIPHER  : string = 'aes-256-cbc'
Default cipher used when reading legacy V1 files (no magic header).
LEGACY_MEMORY_FACTOR  : int = 4
Peak memory, as a multiple of the file size, of the in-memory V1/V2 read path.
MAGIC  : string = 'OPHE'
4-byte ASCII magic identifying an Oihana PHP Encryption file.
MAGIC_LENGTH  : int = 4
Length of {@see EncryptionFormat::MAGIC} in bytes.
MAX_BLOCK_SIZE_EXPONENT  : int = 30
Largest block-size exponent accepted, on write and on read: 2^30 = 1 GiB.
MIN_BLOCK_SIZE_EXPONENT  : int = 16
Smallest block-size exponent accepted, on write and on read: 2^16 = 64 KiB.
NONCE_PREFIX_LENGTH  : int = 8
Length of the per-file random nonce prefix of a V3 header, in bytes.
PBKDF2_ITERATIONS  : int = 600000
PBKDF2 iteration count.
SALT_LENGTH  : int = 16
KDF salt length in bytes (16 = 128 bits, NIST recommendation).
V3_HEADER_LENGTH  : int = 31
Total length of a V3 header in bytes: MAGIC (4) + VERSION (1) + KDF (1) + BLOCKEXP (1) + SALT (16) + NONCE PREFIX (8).
VERSION_LENGTH  : int = 1
Length of the version byte.
VERSION_V1  : int = 1
Legacy on-disk format version: raw IV + AES-CBC ciphertext, no magic, no MAC.
VERSION_V2  : int = 2
Legacy on-disk format version: a single AES-256-GCM blob with one tag.
VERSION_V3  : int = 3
Current on-disk format version, written right after the magic.

Constants

BLOCK_COUNTER_LENGTH

Length of the big-endian block counter appended to the nonce prefix, in bytes.

public int BLOCK_COUNTER_LENGTH = 4

4 bytes cap a file at 2^32 blocks — 32 EiB at the default block size.

BLOCK_SIZE_EXPONENT_LENGTH

Length of the block-size-exponent byte of a V3 header (right after the KDF byte).

public int BLOCK_SIZE_EXPONENT_LENGTH = 1

DEFAULT_BLOCK_SIZE_EXPONENT

Default block-size exponent written by `encrypt()`: 2^23 = 8 MiB.

public int DEFAULT_BLOCK_SIZE_EXPONENT = 23

Large enough that the per-block 16-byte tag costs 0.0002% of the file, small enough that the peak memory of a block plus its ciphertext stays in the tens of mebibytes on the smallest VM.

DEFAULT_CIPHER

Default AEAD cipher used when writing V2 files.

public string DEFAULT_CIPHER = 'aes-256-gcm'

AES-256-GCM is the modern standard: confidentiality + integrity in a single primitive.

GCM_IV_LENGTH

AES-GCM IV length in bytes (12 = 96 bits, NIST SP 800-38D recommendation).

public int GCM_IV_LENGTH = 12

GCM_TAG_LENGTH

AES-GCM authentication tag length in bytes (16 = 128 bits, maximum security).

public int GCM_TAG_LENGTH = 16

HEADER_LENGTH

Total length of the V2 format prefix (MAGIC + VERSION + KDF) in bytes.

public int HEADER_LENGTH = 6

KDF_ARGON2ID

KDF identifier: Argon2id (memory-hard, GPU-resistant).

public int KDF_ARGON2ID = 1

Requires ext-sodium at both encrypt and decrypt time.

KDF_LENGTH

Length of the KDF-identifier byte (right after the version).

public int KDF_LENGTH = 1

KDF_PBKDF2_SHA256

KDF identifier: PBKDF2-SHA256.

public int KDF_PBKDF2_SHA256 = 2

Always available (native PHP).

KEY_LENGTH

Derived symmetric key length in bytes (32 = 256 bits, fits AES-256).

public int KEY_LENGTH = 32

LEGACY_CIPHER

Default cipher used when reading legacy V1 files (no magic header).

public string LEGACY_CIPHER = 'aes-256-cbc'

Matches the historical OpenSSLFileEncryption constructor default.

LEGACY_MEMORY_FACTOR

Peak memory, as a multiple of the file size, of the in-memory V1/V2 read path.

public int LEGACY_MEMORY_FACTOR = 4

Measured on 2026-10-24: a 300 MB V2 file peaked at 1.18 GB. file_get_contents() holds the ciphertext, substr() copies it, openssl_decrypt() allocates the plaintext, and the write holds it again.

MAGIC

4-byte ASCII magic identifying an Oihana PHP Encryption file.

public string MAGIC = 'OPHE'

MAGIC_LENGTH

Length of {@see EncryptionFormat::MAGIC} in bytes.

public int MAGIC_LENGTH = 4

MAX_BLOCK_SIZE_EXPONENT

Largest block-size exponent accepted, on write and on read: 2^30 = 1 GiB.

public int MAX_BLOCK_SIZE_EXPONENT = 30

Bounding the exponent at read time is what stops a hostile file from asking for a 2^255-byte allocation before a single byte has been authenticated.

MIN_BLOCK_SIZE_EXPONENT

Smallest block-size exponent accepted, on write and on read: 2^16 = 64 KiB.

public int MIN_BLOCK_SIZE_EXPONENT = 16

A hostile file could otherwise declare a 1-byte block size and turn a modest payload into millions of tag verifications.

PBKDF2_ITERATIONS

PBKDF2 iteration count.

public int PBKDF2_ITERATIONS = 600000

600 000 iterations of SHA-256 ≈ 250 ms on modern hardware. Matches OWASP 2023+ recommendation.

SALT_LENGTH

KDF salt length in bytes (16 = 128 bits, NIST recommendation).

public int SALT_LENGTH = 16

V3_HEADER_LENGTH

Total length of a V3 header in bytes: MAGIC (4) + VERSION (1) + KDF (1) + BLOCKEXP (1) + SALT (16) + NONCE PREFIX (8).

public int V3_HEADER_LENGTH = 31

VERSION_LENGTH

Length of the version byte.

public int VERSION_LENGTH = 1

VERSION_V1

Legacy on-disk format version: raw IV + AES-CBC ciphertext, no magic, no MAC.

public int VERSION_V1 = 1

V1 files carry no header at all, so this value is never written to disk — it is what the format detection reports for a file that does not begin with EncryptionFormat::MAGIC.

VERSION_V2

Legacy on-disk format version: a single AES-256-GCM blob with one tag.

public int VERSION_V2 = 2

Read-only. PHP exposes no incremental AEAD API, so a single-tag blob cannot be verified without holding it whole in memory — which is why V3 exists.

VERSION_V3

Current on-disk format version, written right after the magic.

public int VERSION_V3 = 3

V3 is chunked: the plaintext is cut into blocks of EncryptionFormat::DEFAULT_BLOCK_SIZE_EXPONENT and each block is sealed on its own, so both encrypt() and decrypt() run at constant memory whatever the file size.

On this page

Search results