rfc:php71-crypto

PHP RFC: Simple Cryptography Library

Introduction

Cryptography is hard to get right, even for experts. Building atop the victories against insecure design that the password hashing API have brought us, I would seek to provide a simple, secure-by-default cryptography interface that puts as little burden on the user (PHP developers) as possible, that works with multiple cryptography backends.

Proposal

My proposal is to create a series of new classes (preferably in its own namespace, e.g. \Php\Crypto or simply \Cryptography):

  • Asymmetric\Crypto
  • Asymmetric\EncryptionSecretKey
  • Asymmetric\EncryptionPublicKey
  • Asymmetric\SignatureSecretKey
  • Asymmetric\SignaturePublicKey
  • Symmetric\AuthenticationKey
  • Symmetric\Crypto
  • Symmetric\EncryptionKey
  • Key (base type that all *Key classes inherit from)
  • KeyFactory (abstract class, static methods)
  • KeyPair

Users would primarily be interested in the Crypto classes, and the methods of the KeyFactory class.

For example:

  $keypair = \Php\Crypto\KeyFactory::generateEncryptionKeyPair('openssl'); 
  var_dump($keypair); // An instance of \Php\Crypto\KeyPair
  $secret = $keypair->getSecretKey(); // \Php\Crypto\Asymmetric\EncryptionSecretKey
  $public = $keypair->getPublicKey(); // \Php\Crypto\Asymmetric\EncryptionPublicKey
  
  $fips = new \Php\Crypto\Asymmetric\Crypto([
      'driver' => 'openssl',
      'cipher' => 'aes-256',
      'hash' => 'sha384'
  ]);
  $ciphertext = $fips->seal(
      'This is a text message',
      $public
  );
  $plaintext = $fips->unseal(
      $ciphertext,
      $secret
  );
  var_dump($plaintext === 'This is a text message'); // bool(true)

The Asymmetric\Crypto interface would look like this:

* ''string'' ''encrypt( string, EncryptionSecretKey, EncryptionPublicKey )''
* ''string'' ''decrypt( string, EncryptionSecretKey, EncryptionPublicKey )''
* ''string'' ''seal( string, EncryptionPublicKey )''
* ''string'' ''unseal( string, EncryptionSecretKey )''
* ''string'' ''sign( string, SignatureSecretKey )''
* ''bool' ''verify( string, SignaturePublicKey, string )''

The Symmetric\Crypto interface would look like this:

  • string auth( string, AuthenticationKey )
  • bool verify( string, AuthenticationKey )
  • string aeadEncrypt( string, EncryptionKey, string )
  • string aeadDecrypt( string, EncryptionKey, string )
  • string encrypt( string, EncryptionKey )
  • string decrypt( string, EncryptionKey )

All decryption (including unseal()) operations will throw a typed exception (e.g. \Php\Crypto\CryptoException) if the MAC doesn't validate.

Drivers, Configurations, and Defaults

This API will act similar to PDO in the sense that it can, behind the scenes, support multiple drivers. As of the day we ship PHP 7.1.0, we MUST support at least two:

  • libsodium
  • openssl

Which driver and which primitives can be supplied at the time a Crypto object is created, but the valid choices will be limited.

  • Libsodium (not configurable)
    • Cipher: Xsalsa20
    • Hash/HMAC: HMAC-SHA-512/256
    • Public keys: X25519
    • Signatures: Ed25519
    • HKDF: BLAKE2b
    • Password-Based Key Derivation: Argon2i
  • OpenSSL (configurable):
    • Cipher: AES-128, AES-192, AES-256
    • Hash/HMAC: SHA256, SHA384, SHA512, SHA3-356, SHA3-384, SHA3-512
    • Public keys: ECDH over NIST P-256
    • Signatures: ECDSA over NIST P-256
    • HKDF: (See hash function above)
    • Password-Based Key Derivation: PBKDF2-(hash function above) with 86,000 rounds

If both drivers are installed, both Crypto classes will default to libsodium (reason: secure defaults) unless otherwise specified.

If you only specify a driver, OpenSSL will default to: AES-256 and SHA-384. Only CTR mode is supported regardless of cipher, except for aeadEncrypt() and aeadDecrypt(), which should only allow GCM mode.

Ciphertext Message Format

The first four bytes of any message are a header that indicates the version of the library and various other information.

  • First byte: Major version of this interface (e.g. 01)
  • Second byte: Minor version of this interface (e.g. 05)
  • Third byte: Driver ID
  • Fourth byte: A checksum (Byte0 ^ Byte1 ^ Byte2) to detect corruption

Driver-specific metadata can follow this four-byte header, but it is not required.

Proposed PHP Version(s)

This should be considered for inclusion in PHP 7.1

RFC Impact

To Existing Extensions

Unaffected PHP Functionality

Future Scope

Proposed Voting Choices

This is a new feature; would a 50%+1 majority be acceptable?

Patches and Tests

A prototype is available here, which fleshed out a lot of the ideas: https://github.com/paragonie/pco_prototype

Implementation

References

Rejected Features

rfc/php71-crypto.txt · Last modified: 2017/09/22 13:28 (external edit)