meezaan/aescryptor

A library to encrypt / decrypt strings using the AES algorithm

Maintainers

Package info

1x.ax/meezaan/library/aescryptor

Homepage

pkg:composer/meezaan/aescryptor

Transparency log

Statistics

Installs: 1 286

Dependents: 0

Suggesters: 0

2.0 2026-07-28 11:22 UTC

This package is not auto-updated.

Last update: 2026-07-28 11:22:55 UTC


README

A small PHP library for encrypting and decrypting strings with AES‑256, with first‑class support for key rotation.

  • Aes — the low‑level primitive. Encrypts with AES‑256‑GCM (authenticated), and still decrypts data written by v1 (AES‑256‑CBC).
  • Keyring (new in v2) — a set of keys with one active key for new encryption and any number of retired keys kept for decryption, so you can rotate keys without downtime and without re‑encrypting everything up front.

v2 note: new data is written with authenticated AES‑256‑GCM, so decryption fails loudly (an exception) on the wrong key or tampered data rather than returning garbage. v1 CBC data is read transparently so you can migrate it.

This library requires the PHP OpenSSL extension.

Requirements

  • PHP 8.2+
  • ext-openssl, ext-ctype

Installation

composer require meezaan/aescryptor

Quick start (single key)

The library can generate a secure key for you. Store the key somewhere safe; the initialisation vector (IV) is generated per encryption and bundled with the ciphertext, so you don't have to store it separately.

<?php
use Meezaan\Aescryptor\Aes;
use Meezaan\Aescryptor\Generate;

$key = Generate::key();          // 256-bit key, hex encoded. Store this securely.

$aes = new Aes($key);
$encrypted = $aes->encrypt('A secret');
$decrypted = $aes->decrypt($encrypted);   // 'A secret'

Key rotation with Keyring

Rotating encryption keys periodically is a common security requirement. The challenge with AES‑256‑CBC is that decrypting with the wrong key does not raise an error — it silently returns garbage. Keyring solves this by tagging every ciphertext with a short fingerprint of the key that produced it, so decryption always selects the exact key that was used.

A keyring has:

  • one primary key — used for all new encryption, and
  • zero or more retired keys — kept only so older data can still be decrypted.
<?php
use Meezaan\Aescryptor\Keyring;
use Meezaan\Aescryptor\Generate;

// Today: a single active key.
$ring = new Keyring($primaryKey);

$encrypted = $ring->encrypt('A secret');   // tagged with the primary's fingerprint
$decrypted = $ring->decrypt($encrypted);   // 'A secret'

Rotating a key

When you introduce a new key, make it the primary and keep the previous key as a retired key. Existing data still decrypts (via the retired key); new data is encrypted under the new key.

// After rotation: $newKey is primary, $oldKey is retired (decrypt-only).
$ring = new Keyring($newKey, [$oldKey]);

$ring->decrypt($sealedUnderOldKey);   // still works — chooses $oldKey by its tag
$ring->encrypt('A new secret');       // encrypted under $newKey

You can carry as many retired keys as you like — a keyring is just a set, so the order does not matter and each key is identified by its own fingerprint.

Completing a rotation

To fully retire an old key, re‑encrypt any data still sealed under it, then drop it from the keyring. isOnPrimary() tells you whether a value is already on the current key, so you can migrate lazily (on write) and/or with a background sweep:

foreach ($rows as $row) {
    if (!$ring->isOnPrimary($row->value)) {
        $row->value = $ring->encrypt($ring->decrypt($row->value));
        // persist $row
    }
}
// Once nothing is sealed under the old key, remove it from the keyring:
// $ring = new Keyring($newKey);

Keyring::fingerprint($key) and Keyring::tagFingerprint($ciphertext) let you record and query which key each value uses, which is handy for proving a key is fully retired.

Legacy (untagged) values

Values produced by Aes directly (or by this library before v2) have no tag. Keyring::decrypt() treats an untagged value as encrypted with the primary key. This is only correct while that key is still the primary, so if you are adopting Keyring on existing data, run a one‑time pass to re‑encrypt (tag) all existing values before rotating that key out of the primary slot.

Missing keys

If a value is tagged with a key that is not in the keyring (for example, a key that was retired and removed too early), decrypt() throws Meezaan\Aescryptor\UnknownKeyException, whose ->fingerprint names the missing key. Keep a key in the ring until you have confirmed nothing depends on it.

Ciphertext format

New values (GCM):

  • Untagged (from Aes): <base64 ciphertext>:::<base64 iv>:::<base64 tag>
  • Tagged (from Keyring): k<fingerprint>.<base64 ciphertext>:::<base64 iv>:::<base64 tag>

Legacy v1 values (CBC) have two parts — <base64 ciphertext>:::<base64 iv> — and are still read by decrypt(). The number of ::: parts distinguishes GCM (3) from CBC (2).

The fingerprint is the first 8 hex characters of sha256(lowercase-hex-key). The . delimiter never occurs in the base64 body, so tagged and untagged values are never ambiguous.

Security notes

  • New data uses AES‑256‑GCM (authenticated encryption). A fresh random 96‑bit nonce is generated per encryption and stored with the ciphertext; the 128‑bit authentication tag is verified on decryption, so a wrong key or any tampering raises DecryptionException instead of returning garbage.
  • GCM's security depends on never reusing a nonce with the same key. This library generates a fresh random nonce for every encrypt() call — do not encrypt an impractically large number of messages under a single key.
  • Legacy v1 data is AES‑256‑CBC (unauthenticated) and read for migration only. Re‑encrypt it to move it onto GCM.
  • Store keys in a secrets manager, not in source. Rotate them on a schedule and keep a retired key only until its data has been re‑encrypted.

Interoperability

Results are not interoperable with the Linux openssl CLI. See https://stackoverflow.com/questions/71198954/openssl-aes-256-cbc-encryption-from-command-prompt-and-decryption-in-php-and-vi.

Testing

composer install
vendor/bin/phpunit tests/Unit

Credits

License

MIT