# PRD — encryptionatrestphp

Evidence date: 2026-10-15.

Source: `git@bitbucket.org:overdrive/encryptionatrestphp.git`, `main` at `9b3df5c` (2025-08-05).

There is no prior PRD in the source repository or in Silicon Ark. Requirements below are recovered from `README.md`, `PROJECT_SUMMARY.md`, `SENSITIVE_DATA_GUIDE.md`, `src/EncryptionAtRest.php`, examples, tests, and `index.php`. Provenance is labelled.

## Product purpose

**Documented.** Provide a simple, secure, reusable PHP library for encrypting and decrypting data at rest using AES-256-GCM authenticated encryption, suitable for integration into any PHP framework or plain PHP application.

**Implemented.** `EncryptionAtRest` offers encrypt/decrypt, key generation, key export/import, and static support checks using OpenSSL only.

**Unknown / Requires Confirmation:** whether this library is an official internal standard for Overdrive PHP apps, and which products must adopt it.

## Users and roles

| Actor | Evidence | Status |
|---|---|---|
| PHP application developer | README install/API; examples; composer scripts | **Documented** primary user |
| Integration consumer (Laravel / Symfony / other) | README “Framework Integration”; `examples/framework_integration.php` | **Documented** patterns; **not** shipped as framework packages |
| Local demo operator | `index.php` HTML form | **Implemented** demo; not a product role |
| End user of a consuming app | Indirect — ciphertext stored by the app | **Inferred**; no end-user UI in this repo |
| Application role / RBAC | None in library | **Not implemented** — callers supply keys and decide who may encrypt/decrypt |

## Systems the product talks to

None as a network client. The library only calls PHP OpenSSL and `random_bytes`.

Consuming applications may store ciphertext in databases, files, or env-backed key stores; those integrations are **documented examples**, not runtime dependencies of this repository.

## Major workflows

### WF-1 — Construct encryptor with auto-generated key

**Documented** in README Quick Start. **Implemented:** `__construct(null)` calls `generateKey()` → `random_bytes(32)`.

### WF-2 — Construct encryptor with supplied 32-byte key

**Documented.** **Implemented:** `__construct($key)` validates length exactly 32 bytes via `validateKey`, else `InvalidArgumentException`.

### WF-3 — Encrypt plaintext

**Trigger:** caller invokes `encrypt($data)`.

**Inputs:** non-empty string (PHP `empty()` check — see BR / edge cases).

**Behaviour (Implemented):**

1. Generate 12-byte IV with `random_bytes`.
2. `openssl_encrypt` with `aes-256-gcm`, `OPENSSL_RAW_DATA`, empty AAD, 16-byte tag.
3. Concatenate `IV || tag || ciphertext`.
4. Return `base64_encode` of the combined bytes.

**Outputs:** base64 string.

**Errors:** `InvalidArgumentException` if data empty; `Exception` if OpenSSL fails.

### WF-4 — Decrypt ciphertext

**Trigger:** caller invokes `decrypt($encryptedData)`.

**Behaviour (Implemented):**

1. Reject empty input.
2. Strict `base64_decode(..., true)`; fail if invalid.
3. Require length ≥ 28 (12 IV + 16 tag).
4. Split IV, tag, ciphertext; `openssl_decrypt` with same cipher and key.
5. Return plaintext string, or throw `Exception` on failure.

### WF-5 — Export / import key

**Documented.** **Implemented:** `exportKey()` → base64 of raw key; `importKey($base64Key)` decodes, validates 32 bytes, replaces instance key.

### WF-6 — Capability check

**Documented.** **Implemented:** `isSupported()` requires `openssl` extension and `aes-256-gcm` in `openssl_get_cipher_methods()`; `getSupportedCiphers()` returns all methods.

### WF-7 — Demo form round-trip

**Implemented** in `index.php`: POST `text` → new `EncryptionAtRest()` → encrypt → decrypt → echo match. New key every request.

### WF-8 — Run examples / tests

**Documented:** `php examples/...`, `php tests/test_encryption.php`, Composer scripts `test` and `example`.

## Functional requirements

### FR-001 — AES-256-GCM authenticated encryption

**Requirement:** The system encrypts data with AES-256-GCM so ciphertext includes authentication (GCM tag).

**Evidence:** `CIPHER = 'aes-256-gcm'`, tag length 16, IV length 12.  
**Implementation:** Implemented. **Confidence:** High.

### FR-002 — Base64 transport encoding

**Requirement:** Encrypted output is base64 for storage/transmission.  
**Evidence:** `encrypt` return; README.  
**Implementation:** Implemented. **Confidence:** High.

### FR-003 — Auto key generation

**Requirement:** If no key is passed, generate a cryptographically secure 32-byte key.  
**Evidence:** constructor + `generateKey`.  
**Implementation:** Implemented. **Confidence:** High.

### FR-004 — Fixed key length

**Requirement:** Supplied keys must be exactly 32 bytes.  
**Evidence:** `KEY_LENGTH`, `validateKey`.  
**Implementation:** Implemented. **Confidence:** High.

### FR-005 — Key export and import (base64)

**Requirement:** Callers can export and later import keys as base64.  
**Evidence:** `exportKey` / `importKey`; README; tests.  
**Implementation:** Implemented. **Confidence:** High.

### FR-006 — Reject empty plaintext and empty ciphertext inputs

**Requirement:** Empty encrypt/decrypt inputs raise `InvalidArgumentException`.  
**Evidence:** `empty($data)` / `empty($encryptedData)` checks; tests.  
**Implementation:** Implemented. **Note:** PHP `empty("0")` is true — string `"0"` is rejected as empty. **Confidence:** High for empty string; edge case for `"0"` is **Inferred** behaviour.

### FR-007 — Reject invalid or truncated ciphertext

**Requirement:** Invalid base64, too-short payloads, and failed OpenSSL decrypt raise exceptions.  
**Evidence:** `decrypt` validation; test suite.  
**Implementation:** Implemented. **Confidence:** High.

### FR-008 — Framework-agnostic single-file / Composer install

**Requirement:** Consumers can require the class file or use Composer classmap autoload.  
**Evidence:** README install; `composer.json` autoload.  
**Implementation:** Implemented. **Confidence:** High.

### FR-009 — Documented framework and DB field patterns

**Requirement:** Documentation shows Laravel, Symfony, and repository encrypt-before-save patterns.  
**Evidence:** README; examples; guides.  
**Implementation:** Documented examples only (not framework packages). **Confidence:** High.

### FR-010 — Sensitive-data usage guidance

**Requirement:** Document which field types may be encrypted and which should stay searchable.  
**Evidence:** `SENSITIVE_DATA_GUIDE.md`.  
**Implementation:** Documented guidance. Compliance claims (GDPR/HIPAA/PCI) are **Documented assertions**, not audited certifications — **Requires Confirmation** if treated as formal compliance evidence.

### FR-011 — Test coverage for core behaviours

**Requirement:** A test script verifies support, round-trip, keys, errors, large data, Unicode, JSON, binary.  
**Evidence:** `tests/test_encryption.php`; `PROJECT_SUMMARY.md` claims all pass.  
**Implementation:** Implemented as a custom runner (not PHPUnit). This onboarding did not re-execute the suite. Pass claim is **Documented** in PROJECT_SUMMARY; runtime pass on this host is **Unknown**.

### FR-012 — No third-party PHP packages required

**Requirement:** Library uses only built-in OpenSSL.  
**Evidence:** README “No Dependencies”; `composer.json` require only `php` and `ext-openssl`.  
**Implementation:** Implemented. **Confidence:** High.

## Non-goals (from evidence)

- Searching or sorting on ciphertext without additional app design (guide says keep searchable fields plaintext) — **Documented**.
- Built-in key rotation — **Documented** as a best practice for operators, **not implemented** in the class.
- Network services, authn/authz, multi-tenant key isolation — **not present**.

## Constraints

| Constraint | Provenance |
|---|---|
| PHP ≥ 7.4 | Documented + composer |
| OpenSSL with AES-256-GCM | Documented + Implemented |
| Class is global `EncryptionAtRest` (no namespace) | Implemented |
| MIT license declared | Documented in README / composer; no `LICENSE` file — **Requires Confirmation** |
| Packagist / GitHub homepage placeholders | Documented inconsistency with Bitbucket origin |

## Discrepancies

1. **Scan host vs git:** `sonar-project.properties` and `sonarqube_scan.bat` listed at discovery; absent from `9b3df5c`.
2. **Composer support URLs** point at `github.com/yourusername/...` while origin is Bitbucket `overdrive/encryptionatrestphp`.
3. **`examples/framework_integration.php` `UserRepository::getUser`:** returns decrypt of hard-coded placeholder strings (`encrypted_email_data_here`, etc.), not the ciphertext produced by `saveUser`. Integrity check after `getUser(1)` will fail if that path is used as-is. `saveUser` alone encrypts correctly. Provenance: **Implemented** demo bug / incomplete simulation.
4. **`index.php`:** new key per request — cannot persist decrypt across page loads. Fine for demo; unsuitable as a persistent demo vault without change.
5. **README performance** says “processes data in chunks”; `encrypt`/`decrypt` operate on the full string in one OpenSSL call — **Documented claim ≠ Implemented** chunking.

## Open questions for human confirmation

1. Is Packagist publishing planned under `encryption-at-rest/php`?
2. Should compliance wording in `SENSITIVE_DATA_GUIDE.md` be softened to “supports controls often used toward …” rather than “Compliant with …”?
3. Should `getUser` in the framework example be fixed to decrypt the in-memory encrypted map?
4. Is a `LICENSE` file required for legal completeness?
5. Should Sonar/CI artefacts be added to the source repo?
