PHPackages                             vamischenko/decorators - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. vamischenko/decorators

ActiveLibrary[HTTP &amp; Networking](/categories/http)

vamischenko/decorators
======================

PSR-7 stream decorators for WhatsApp media encryption and decryption

1.2.0(4mo ago)00MITPHPPHP ^8.1CI passing

Since Mar 26Pushed 4mo agoCompare

[ Source](https://github.com/vamischenko/decorators)[ Packagist](https://packagist.org/packages/vamischenko/decorators)[ Docs](https://github.com/vamischenko/decorators)[ RSS](/packages/vamischenko-decorators/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (5)Dependencies (6)Versions (6)Used By (0)

vamischenko/decorators
======================

[](#vamischenkodecorators)

PSR-7 stream decorators for encrypting and decrypting WhatsApp media files using the WhatsApp AES-256-CBC algorithm.

Requirements
------------

[](#requirements)

- PHP 8.1+
- ext-openssl
- ext-hash
- guzzlehttp/psr7 ^2.0

Installation
------------

[](#installation)

```
composer require vamischenko/decorators
```

Architecture
------------

[](#architecture)

The encryption pipeline is built from composable PSR-7 stream decorators, inspired by [jeskew/php-encrypted-streams](https://github.com/jeskew/php-encrypted-streams) (Apache 2.0). Since that package requires `guzzlehttp/psr7 ~1.0` and is no longer maintained, its core classes were adapted and included directly.

```
source
  → AesEncryptingStream   (AES-256-CBC, cipherKey, iv)
  → AppendStream([iv, ciphertext])   ← iv prepended so HMAC covers it
  → HashingStream                    (HMAC-SHA256, macKey)

```

Encryption is **true streaming** — no full-file buffering. Data is processed block by block as the stream is read.

Algorithm
---------

[](#algorithm)

1. Expand the 32-byte `mediaKey` to 112 bytes using HKDF with SHA-256 (RFC 5869)
2. Split into `iv` (16 bytes), `cipherKey` (32), `macKey` (32), `refKey` (32)
3. Encrypt with AES-256-CBC + PKCS7 padding
4. Compute HMAC-SHA256 over `iv + ciphertext`, truncate to 10 bytes
5. Output: `[ciphertext][mac]`

Media-type-specific HKDF info strings:

TypeInfo stringIMAGE`WhatsApp Image Keys`VIDEO`WhatsApp Video Keys`AUDIO`WhatsApp Audio Keys`DOCUMENT`WhatsApp Document Keys`Usage
-----

[](#usage)

### Encryption

[](#encryption)

```
use GuzzleHttp\Psr7\Utils;
use Vamischenko\Decorators\KeyExpander;
use Vamischenko\Decorators\MediaKey;
use Vamischenko\Decorators\MediaType;
use Vamischenko\Decorators\Sidecar\SidecarContext;
use Vamischenko\Decorators\Stream\EncryptingStream;

$mediaKey = MediaKey::generate(); // or MediaKey::fromBinary($existingKey)
$expanded = (new KeyExpander())->expand($mediaKey, MediaType::IMAGE);

$source  = Utils::streamFor(fopen('photo.jpg', 'rb'));
$sidecar = new SidecarContext($expanded->macKey); // optional, for VIDEO/AUDIO
$stream  = new EncryptingStream($source, $expanded, $sidecar);

// Read the encrypted stream — data is processed incrementally, not buffered
file_put_contents('photo.jpg.enc', (string) $stream);

// For streamable media, retrieve the sidecar after reading the full stream
$sidecarBytes = $sidecar->getSidecar();
```

### Decryption

[](#decryption)

```
use GuzzleHttp\Psr7\Utils;
use Vamischenko\Decorators\KeyExpander;
use Vamischenko\Decorators\MediaKey;
use Vamischenko\Decorators\MediaType;
use Vamischenko\Decorators\Stream\DecryptingStream;

$mediaKey = MediaKey::fromBinary($keyBytes);
$expanded = (new KeyExpander())->expand($mediaKey, MediaType::IMAGE);

$source = Utils::streamFor(fopen('photo.jpg.enc', 'rb'));
$stream = new DecryptingStream($source, $expanded);

// MAC is verified before any bytes are returned
$plaintext = (string) $stream;
```

### Exceptions

[](#exceptions)

- `InvalidMediaKeyException` — thrown when the key is not exactly 32 bytes
- `MacVerificationException` — thrown when the HMAC does not match (corrupt or tampered data)

Security
--------

[](#security)

- MAC verification uses **constant-time** `hash_equals()` to prevent timing attacks
- Integrity is verified **before** any plaintext is returned
- Key derivation follows RFC 5869 (HKDF) with WhatsApp-specific info strings

Memory behaviour
----------------

[](#memory-behaviour)

Stream typeEncryptionDecryptionSeekable (file)Incremental — O(block) memoryIncremental — O(block) memory (MAC read via seek, then streaming `AesDecryptingStream`)Non-seekable (HTTP)Incremental — O(block) memory**Full ciphertext buffered** (MAC is at the tail), then plaintext delivered incrementally — no second copyFor memory-constrained environments with non-seekable sources, wrap the response body in a temp-file stream before passing it to `DecryptingStream`.

Sidecar
-------

[](#sidecar)

The sidecar enables random-offset decryption for VIDEO and AUDIO streams, allowing players to seek without downloading the full file.

It is generated during encryption with **no additional reads** from the source stream. Each 10-byte entry is the HMAC-SHA256 of the `[n*64K, (n+1)*64K+16]` slice of the logical combined buffer `iv + ciphertext + mac`, truncated to 10 bytes.

Running tests
-------------

[](#running-tests)

```
composer install
vendor/bin/phpunit
```

###  Health Score

33

—

LowBetter than 72% of packages

Maintenance76

Regular maintenance activity

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

5

Last Release

130d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/533cf837b244f9c58175ec3cc68fb62c5c08302e73b8cced88e8c268d0503aca?d=identicon)[vamischenko](/maintainers/vamischenko)

---

Tags

psr-7encryptionmediaStreamswhatsapphmacaes-256-cbc

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/vamischenko-decorators/health.svg)

```
[![Health](https://phpackages.com/badges/vamischenko-decorators/health.svg)](https://phpackages.com/packages/vamischenko-decorators)
```

###  Alternatives

[aws/aws-sdk-php

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.2k555.0M2.8k](/packages/aws-aws-sdk-php)[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B4.4k](/packages/guzzlehttp-psr7)[algolia/algoliasearch-client-php

API powering the features of Algolia.

69735.8M177](/packages/algolia-algoliasearch-client-php)[gotenberg/gotenberg-php

A PHP client for interacting with Gotenberg, a developer-friendly API for converting numerous document formats into PDF files, and more!

3906.6M32](/packages/gotenberg-gotenberg-php)[oat-sa/tao-core

TAO core extension

65147.9k149](/packages/oat-sa-tao-core)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M662](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
