As part of our efforts to reduce the friction to adopt secure authenticated secret-key encryption in the PHP community, our Chief Development Officer has been helping Taylor Hornby develop the next version of Defuse Security's PHP Encryption Library (henceforth, referred to as, "Defuse Crypto"). After a year of research and development, we're happy to announce that version 2 is now available for use in your PHP projects.
As always, if you're allowed to install PHP extensions, you should really consider using PECL libsodium instead of a PHP-land cryptography library. We've contributed documentation for libsodium in PHP.
If you cannot (for whatever reason) install PHP extensions, you're much better off using Defuse Crypto than attempting to write your own cryptography library.
Changes to Crypto
at a Glance
Version 1.2.1 | Version 2.0.0 | Comment | |
---|---|---|---|
Class Path | Global namespace | PSR-4 vendor namespace | Everything is in \Defuse\Crypto now. |
Encryption | AES-128-CBC | AES-256-CTR | 256-bit keys offer $2^{128}$ post-quantum security |
Message Integrity | HMAC-SHA256 | HMAC-SHA256 | No change was necessary. |
Key-Splitting | Unsalted HKDF-HMAC-SHA256 | Salted HKDF-HMAC-SHA256 | Each message gets its own HKDF salt in addition to its own CTR nonce. |
Version Tag? | No | Yes | The header is included in the MAC |
Output encoding | Raw binary | Hexadecimal | We found that defaulting to raw binary was confusing to many users. |
Encryption keys | Strings | Objects | See below for details. |
New Features in Version 2.0.0
File Encryption
The flagship feature of the next version of Defuse Crypto is the ability to encrypt large files (e.g. up to 2GB safely on a 32-bit OS) while using very low amounts of RAM.
File encryption is facilitated by a new class, \Defuse\Crypto\File
. This was originally designed by our CDO, and then improved greatly by Taylor Hornby and many open source contributors. We have benchmarked encrypting and decrypting a 173 MB file in under 4 seconds with less than 4 MB peak memory usage using \Defuse\Crypto\File
.
Changes and Improvements in Version 2.0.0
PSR-4 Namespace
Between the last release (1.2.1) and Version 2, we have refactored the library to use the namespace \Defuse\Crypto
in compliance with PSR-4.
Previously, you could just use Crypto::encrypt()
and Crypto::decrypt()
. Now you have to options: Add a use
statement or explicitly define the namespace.
Version Tagging
When you encrypt data in Defuse Crypto 2.0.0 and newer, a fixed header is prefixed to the encrypted payload. This header contains the version of the library used for encrypting the message. This allows the library maintainers to publish a newer version with stronger algorithms (should a weakness ever be found in AES-128-CBC, HMAC-SHA-256, etc.) without rendering previously encrypted messages inaccessible.
A version tag for consists of 4 bytes: D3 F5 XX XX
. the first two are a magic value specific to this library, the next two are the major and minor version. The header for version 2.0.x will be D3 F5 02 00
. Should we need to update anything, version 2.1.x will use D3 F5 02 01
and 3.0.x will use D3 F5 03 00
. Patch releases (the third number) will not change the header.
If you have previously encrypted data with an earlier version of this library, you can use \Defuse\Crypto\Crypto::legacyDecrypt($your_ciphertext, $your_key)
to decrypt it. We recommend you decrypt all your old data and re-encrypt it with the new tagged format, as legacyDecrypt()
will not exist in Version 3.
This feature affects both the existing \Defuse\Crypto\Crypto
class and the new \Defuse\Crypto\File
class.
Version 2 uses AES in Counter Mode, Exclusively
Where the original version of Defuse Crypto used AES-128 in CBC (Cipher Block Chaining) mode, version 2 will use CTR (Counter) mode instead. There are many reasons contributing to this change:
- CTR made far more sense for the design of the
File
API - Simplicity -- why use two different modes when only one is needed?
- Public recommendations from cryptographers favor CTR over CBC
- One more reason (see below)
HKDF Now Uses Random Salts (Stored with Ciphertext)
In addition to a random nonce for AES-128-CTR encryption, the library also generates and stores a random 256-bit salt for the HKDF key splitting feature.
The reason for this change is subtle: If you encrypt $2^{64}$ messages with a random nonce, you have a 50% chance of generating at least one duplicate nonce. In probability, this is referred to as the Birthday paradox. A duplicate nonce can degrade the security of your cryptosystem if you use the same key. Since version 1 of the library already introduced HKDF key splitting (to separate encryption keys from authentication keys), we added a 256-bit salt (which is also authenticated by the HMAC) for the HKDF calculation.
- The situation before: Constant encryption and authentication keys, random IV. Collision after $2^{64}$ messages.
- The situation now: Deterministic encryption and authentication keys from a random salt, random IV. Collision after $2^{192}$ messages.
For the record 50% collision probability at $2^{64}$ is a rare event and $2^{192}$ is pretty close to "never going to happen before the heat death of the universe".
This change affects both the existing \Defuse\Crypto\Crypto
class and the new \Defuse\Crypto\File
class.
Keys Are Objects, Not Strings
Before version 2, users needed to generate a random key and then figure out how to persist it. This also had the drawback that some users were tempted to use a human-readable password instead of a proper encryption key.
Version 2 uses Key objects instead of strings:
<?php
use \Defuse\Crypto\Key;
$key = Key::createNewRandomKey();
$storeMe = $key->saveToAsciiSafeString();
To restore a key:
<?php
use \Defuse\Crypto\Key;
$key = Key::loadFromAsciiSafeString($storedString);
If you want to use a password, you can use KeyProtectedByPassword
instead. This generates a random key then encrypts the key with your password.
<?php
use \Defuse\Crypto\KeyProtectedByPassword;
$key = KeyProtectedByPassword::createRandomPasswordProtectedKey($password);
$saveMe = $protected_key->saveToAsciiSafeString();
And then:
<?php
use \Defuse\Crypto\KeyProtectedByPassword;
$protected_key = KeyProtectedByPassword::loadFromAsciiSafeString($storedString);
$user_key = $protected_key->unlock($password);
// Now you can use $user_key directly without having to persist the password in memory
Hexadecimal Encoding by Default
Due to feedback from the developer community (whom were largely frustrated with MySQL altering their raw binary data and rendering it unusable), \Defuse\Crypto\Crypto::encrypt()
has been changed to hex-encode output by default. If you want raw binary, simply pass true
as the third argument; this usage is consistent with the hash()
and hash_hmac()
functions.
This change affects both Crypto::encrypt()
and Crypto::decrypt()
. Crypto::legacyDecrypt()
expects raw binary strings.
How to Get Started with Defuse Crypto 2.0.0
If you're using Composer, you can simply run this command.
composer require defuse/php-encryption:^2.0
For non-Composer-users: download the PHP Archive and GPG signature bundled with the release, verify the signature, and just require
it.
The library's official documentation includes a tutorial for getting started.