#http-ece #web-push


Encrypted Content-Encoding for HTTP Rust implementation

16 releases (stable)

2.2.0 Jan 31, 2022
2.1.0 Mar 30, 2021
1.3.0 Feb 3, 2021
1.2.1 Sep 10, 2020
0.1.3 Mar 12, 2019

#134 in Cryptography

Download history 731/week @ 2023-08-02 359/week @ 2023-08-09 615/week @ 2023-08-16 821/week @ 2023-08-23 758/week @ 2023-08-30 1097/week @ 2023-09-06 1343/week @ 2023-09-13 1595/week @ 2023-09-20 1039/week @ 2023-09-27 1541/week @ 2023-10-04 1201/week @ 2023-10-11 1023/week @ 2023-10-18 1038/week @ 2023-10-25 1289/week @ 2023-11-01 1305/week @ 2023-11-08 1500/week @ 2023-11-15

5,317 downloads per month
Used in 3 crates (2 directly)

MPL-2.0 license

1.5K SLoC

rust-ece   Build Status Latest Version

This crate has not been security reviewed yet, use at your own risk (tracking issue).

The ece crate is a Rust implementation of Message Encryption for Web Push (RFC8291) and the HTTP Encrypted Content-Encoding scheme (RFC8188) on which it is based.

It provides low-level cryptographic "plumbing" and is destined to be used by higher-level Web Push libraries, both on the server and the client side. It is a port of the ecec C library.

Full Documentation

Implemented schemes

This crate implements both the published Web Push Encryption scheme, and a legacy scheme from earlier drafts that is still widely used in the wild:

It does not support, and we have no plans to ever support, the obsolete aesgcm128 scheme from earlier drafts.


To receive messages via WebPush, the receiver must generate an EC keypair and a symmetric authentication secret, then distribute the public key and authentication secret to the sender:

let (keypair, auth_secret) = ece::generate_keypair_and_auth_secret()?;
let pubkey = keypair.pub_as_raw();
// Base64-encode the `pubkey` and `auth_secret` bytes and distribute them to the sender.

The sender can encrypt a Web Push message to the receiver's public key:

let ciphertext = ece::encrypt(&pubkey, &auth_secret, b"payload")?;

And the receiver can decrypt it using their private key:

let plaintext = ece::decrypt(&keypair, &auth_secret, &ciphertext)?;

That's pretty much all there is to it! It's up to the higher-level library to manage distributing the encrypted payload, typically by arranging for it to be included in a HTTP response with Content-Encoding: aes128gcm header.

Legacy aesgcm encryption

The legacy aesgcm scheme is more complicated, because it communicates some encryption parameters in HTTP header fields rather than as part of the encrypted payload. When used for encryption, the sender must deal with Encryption and Crypto-Key headers in addition to the ciphertext:

let encrypted_block = ece::legacy::encrypt_aesgcm(pubkey, auth_secret, b"payload")?;
for (header, &value) in encrypted_block.headers().iter() {
  // Set header to corresponding value
// Send encrypted_block.body() as the body

When receiving an aesgcm message, the receiver needs to parse encryption parameters from the Encryption and Crypto-Key fields:

// Parse `rs`, `salt` and `dh` from the `Encryption` and `Crypto-Key` headers.
// You'll need to consult the spec for how to do this; we might add some helpers one day.
let encrypted_block = ece::AesGcmEncryptedBlock::new(dh, rs, salt, ciphertext);
let plaintext = ece::legacy::decrypt_aesgcm(keypair, auth_secret, encrypted_block)?;

Unimplemented Features

  • We do not implement streaming encryption or decryption, although the ECE scheme is designed to permit it.
  • We only support encrypting or decrypting across multiple records for aes128gcm; messages using the legacy aesgcm scheme must fit in a single record.
  • We do not support customizing the record size parameter during encryption, but do check it during decryption.
    • The default record size is 4096 bytes.
  • We do not support customizing the number of padding bytes added during encryption.
    • We currently select the padding length at random for each encryption, but this is an implementation detail and should not be relied on.

These restrictions might be lifted in future, if it turns out that we need them.

Cryptographic backends

This crate is designed to use pluggable backend implementations of low-level crypto primitives. different crypto backends. At the moment only openssl is supported.

Release process

We use cargo-release to manage releases. To cut a new release, make sure you have it installed and then:

  1. Start a new branch for the release:
    • git checkout -b release-vX.Y.Z
    • git push -u origin release-vX.Y.Z
  2. Run cargo release --dry-run -vv [major|minor|patch] and check that the things it's proposing to do seem sensible.
  3. Run cargo release [major|minor|patch] to prepare, commit, tag and publish the release.
  4. Make a PR from your release-vX.Y.Z branch to request it be merged to the main branch.


~37K SLoC