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(2mo ago)00MITPHPPHP ^8.1CI passing

Since Mar 26Pushed 2mo 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 3w 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

35

—

LowBetter than 77% of packages

Maintenance83

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity46

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

85d 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.2k532.1M2.5k](/packages/aws-aws-sdk-php)[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B3.8k](/packages/guzzlehttp-psr7)[symfony/psr-http-message-bridge

PSR HTTP message bridge

1.3k312.3M929](/packages/symfony-psr-http-message-bridge)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35729.6k2](/packages/telnyx-telnyx-php)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28146.3k](/packages/phpro-http-tools)[mezzio/mezzio-authentication-oauth2

OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications.

28545.4k3](/packages/mezzio-mezzio-authentication-oauth2)

PHPackages © 2026

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