Added sodium-wrapper

This commit is contained in:
gtbu 2022-09-25 17:33:54 +02:00
parent d76ed1233d
commit 438ba114f5
5 changed files with 815 additions and 0 deletions

View file

@ -0,0 +1,26 @@
https://github.com/dwgebler/php-encryption
A cryptography API wrapping the Sodium library, providing a simple object interface for symmetrical and asymmetrical encryption, decryption, digital signing and message authentication.
MIT License
Copyright (c) 2022 David Gebler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,239 @@
# PHP Encryption
![Build Status!](https://app.travis-ci.com/dwgebler/php-encryption.svg?token=uj4HfXm5wqJXVuPAd984&branch=master)
A cryptography API wrapping the Sodium library, providing a simple object interface for symmetrical and asymmetrical encryption, decryption, digital signing and message authentication.
The `Encryption` class is able to generate secrets and keypairs, encrypt and decrypt data, sign and verify data, and generate and verify digital signatures.
Encrypted messages are returned base64 encoded, while keys and secrets are returned as hexadecimal strings.
The transformation of these to and from binary data makes use of the `sodium_*` timing-safe functions.
All underlying cryptography is performed using the [Sodium](https://www.php.net/manual/en/book.sodium.php) library.
This library requires PHP 7.2 or higher with `libsodium` installed (this is bundled with PHP 7.2 or above,
so you probably already have it).
## Installation
Install via Composer
```bash
composer require dwgebler/encryption
```
## Usage
For a quick start, see the included `demo.php` file.
Create an instance of the Encryption class.
```php
<?php
use Gebler\Encryption\Encryption;
$crypt = new Encryption();
```
### Symmetric Encryption
Use the function `encryptWithSecret(string $message, string $password, bool $hexEncoded = true)` to encrypt a message with a secret key.
This function expects the message or data to be encrypted as a string, and the secret key as a hexadecimal string.
If your secret is not a hexadecimal encoded, you can pass `false` as the third parameter to indicate that the secret is not encoded.
You can either generate a secret key with `generateSecret()` or use a pre-existing one.
Alternatively, you can pass in a reference to a null or empty string to generate a secret key.
```php
$mySecret = null;
$data = "Hello world! This is a secret message.";
$result = $crypt->encryptWithSecret($data, $mySecret);
// $mySecret has now been populated with a new secret key
// Alternatively, generate a new key.
$mySecret = $crypt->generateEncryptionSecret();
$result = $crypt->encryptWithSecret($data, $mySecret);
// Alternatively, create a key and encode it as hex.
// Keys should be 32 bytes long - shorter keys are forced to this length by a deterministic hash,
// but this is not recommended. Longer keys will throw an InvalidArgumentException.
$mySecret = bin2hex("my_super_secret_key");
// ...or use random_bytes() to generate a random key.
$mySecret = bin2hex(random_bytes(32));
$result = $crypt->encryptWithSecret($data, $mySecret);
// Or, pass in a raw binary key by setting the `hex` parameter to false.
$mySecret = random_bytes(32);
$result = $crypt->encryptWithSecret($data, $mySecret, false);
// $result is now base64 encoded, e.g.
echo $result;
// wgYwuB/by9bz+CvHj1EtylicXnRH6hl9hLALsUUPUHaZeO3sEj4hgi8+pKBZGZIG6ueRKw3xpvrG8dRWU9OCn3aMtlwLz8aapUX/oK3L
```
To decrypt your message, use the function `decryptWithSecret()`.
```php
$mySecret = "my_super_secret_key";
$message = "This is a test message.";
$encrypted = $crypt->encryptWithSecret($message, $mySecret, false);
echo $encrypted, PHP_EOL;
$decrypted = $crypt->decryptWithSecret($encrypted, $mySecret, false);
echo $decrypted, PHP_EOL;
```
### Asymmetric Encryption
To carry out authenticated asymmetric encryption (i.e. where the message is both encrypted and the sender of the message can be verified), you need to generate a public and private key pair for the sender.
You will also need the public key of the recipient.
```php
// Generate a new random keypair.
$keypair = $crypt->generateEncryptionKeypair();
// Or provide a password to generate a deterministic keypair.
$keypair = $crypt->generateEncryptionKeypair("my_super_secret_password");
// Or use a pre-existing keypair.
// Once you have a keypair, you can export the public key as a hexadecimal string,
// for storage or transmission.
$publicKey = $keypair['publicKey'];
// The keypair also includes the private key.
$privateKey = $keypair['privateKey'];
// The full keypair is also provided. This is a string containing both the private and public key.
$fullKeypair = $keypair['keypair'];
```
As an example, let's encrypt a message from Alice to Bob.
```php
$aliceKeypair = $crypt->generateEncryptionKeypair("alice_secret");
// In the real-world, Bob has provided Alice with his public key, but for demo purposes
// we'll generate a keypair for him too.
$bobKeypair = $crypt->generateEncryptionKeypair("bob_secret");
// Alice encrypts a message for Bob, using his public key and her private key.
$message = "Hello Bob! This is a secret message from Alice.";
$encrypted = $crypt->encryptWithKey($message, $bobKeypair['publicKey'], $aliceKeypair['privateKey']);
// Alice can now transmit $encrypted to Bob. It will look something like this:
// hMvdJf2L78ZWcF38WRXJ16q3xXnlsWWfOsbJISPVwJhBtdcWbZ8SquS3oyJD1k6H/lAs+VHXPpDNfYLWO3wMLl+FB8rYUyCe+IZzti3dFL0YljeJ3QreGlrv
echo $encrypted, PHP_EOL;
// Bob decrypts the message using his private key and the public key of Alice.
$decrypted = $crypt->decryptWithKey($encrypted, $bobKeypair['privateKey'], $aliceKeypair['publicKey']);
// Hello Bob! This is a secret message from Alice.
echo $decrypted, PHP_EOL;
```
You can also use this library to carry out anonymous asymmetric encryption, using only the public key of the
recipient. In this case, the sender's private key is not required and although only the recipient (the holder of the corresponding private key) can decode the message,
they cannot identify or authenticate the sender. This is similar to `openssl_public_encrypt `.
```php
$bobKeypair = $crypt->generateEncryptionKeypair("bob_secret");
// Alice encrypts a message for Bob, using his public key.
$message = "Hello Bob! This is a secret message from an unknown sender.";
$encrypted = $crypt->encryptWithKey($message, $bobKeypair['publicKey']);
// Alice can now transmit $encrypted to Bob.
echo $encrypted, PHP_EOL;
// Bob decrypts the message using his full keypair.
$decrypted = $crypt->decryptWithKey($encrypted, $bobKeypair['keypair']);
// Hello Bob! This is a secret message from an unknown sender.
echo $decrypted, PHP_EOL;
```
### Digital Signing
Asymmetric encryption is useful for securing messages, but it is also useful for authenticating the sender of a message.
Digital signatures are a way to authenticate the sender of a message, as well the message itself, ensuring it
has not been tampered with or altered during transmission.
```php
// Generate a new random keypair.
// Like generateEncryptionKeypair, you can also optionally provide a password to generate a deterministic keypair.
$aliceSigningKeypair = $crypt->generateSigningKeypair();
// Alice signs a message for Bob, using her private key.
$message = "This is a message signed by Alice.";
$signedMessage = $crypt->getSignedMessage($message, $aliceSigningKeypair['privateKey']);
// Alice can now transmit $signedMessage to Bob. It will look something like this:
// JaI6p6jb5qQ041DiK1Yqbk8u1r/wVAovzy57ELfwrWfhqLCUU9jTzBLH6K6v1VF/8vOxaOZe2r8ch/GUKmfgC1RoaXMgaXMgYSBtZXNzYWdlIHNpZ25lZCBieSBBbGljZS4=
// Note: The message itself is NOT encrypted and can be viewed by anyone, by decoding the base64-encoded signed message.
echo $signedMessage, PHP_EOL;
// Bob can now use Alice's public key to verify the signature and obtain the message part.
// If the message has been tampered with, the signature will be invalid and the message will be rejected.
$verifiedMessage = $crypt->verifySignedMessage($signedMessage, $aliceSigningKeypair['publicKey']);
// This is a message signed by Alice.
echo $verifiedMessage, PHP_EOL;
```
We can also generate a signature for a message without attaching it to the message itself.
```php
$aliceSigningKeypair = $crypt->generateSigningKeypair();
// Alice signs a message for Bob, using her private key.
$message = "This is a message signed by Alice.";
$signature = $crypt->getMessageSignature($message, $aliceSigningKeypair['privateKey']);
// Alice can now transmit the message and signature separately to Bob.
// Bob can now use Alice's public key to verify the signature.
// If the message has been tampered with, the signature will be invalid and the message will be rejected.
$messageAuthenticated = $crypt->verifyMessageSignature($message, $signature, $aliceSigningKeypair['publicKey']);
if ($messageAuthenticated === true) {
echo "The message has not been tampered with.", PHP_EOL;
}
```
### Message Authentication
Instead of asymmetric keys, we can also use a shared secret to generate a Message Authentication Code (MAC)
and use this to sign and authenticate messages.
```php
$message = "This is a message signed anonymously with a secret key.";
// We can generate a secure, random 32 byte key, which is returned as a hexadecimal string.
$secret = $crypt->generateSigningSecret();
// Or, as long as the key is 32 bytes (256 bits), you can use any other string.
$secret = hash("sha256", "my secret key");
// Like with the symmetric encryption functions, you can pass an optional third parameter
// to signWithSecret to specify that the secret key is NOT a hexadecimal string.
$secret = hash("sha256", "my secret key", true);
$signature = $crypt->signWithSecret($message, $secret, false);
// Or omit this parameter if the secret is a hexadecimal string.
$secret = $crypt->generateSigningSecret();
$signature = $crypt->signWithSecret($message, $secret);
// The message can now be either transmitted to someone else who also has the shared secret,
// or later verified on the same system, e.g. after being retrieved from a database.
$messageAuthenticated = $crypt->verifyWithSecret($signature, $message, $secret);
if ($messageAuthenticated === true) {
echo "The message has not been tampered with.", PHP_EOL;
}
// Similarly, pass false as the third parameter if the secret is NOT a hexadecimal string.
$secret = hash("sha256", "my secret key", true);
$signature = $crypt->signWithSecret($message, $secret, false);
$messageAuthenticated = $crypt->verifyWithSecret($signature, $message, $secret, false);
if ($messageAuthenticated === true) {
echo "The message has not been tampered with.", PHP_EOL;
}
```
### Licence
This software is released under the [MIT License](https://opensource.org/licenses/MIT).
### Bugs, questions, comments
Please raise a GitHub issue if you encounter any problems or have any questions.

View file

@ -0,0 +1,17 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="./vendor/autoload.php" colors="true" testdox="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" verbose="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<clover outputFile="build/logs/clover.xml"/>
</report>
</coverage>
<testsuites>
<testsuite name="All">
<directory>./tests</directory>
</testsuite>
</testsuites>
<logging/>
</phpunit>

View file

@ -0,0 +1,434 @@
<?php
namespace Gebler\Encryption;
use InvalidArgumentException;
use RuntimeException;
use SodiumException;
use Exception;
class Encryption
{
public function __construct()
{
if (!extension_loaded('sodium')) {
throw new RuntimeException('The sodium extension is not loaded.');
}
}
/**
* Generate a deterministic 32 byte hash from the given password and encode as hex.
* Use this to obtain a text representation of the password that can be easily stored or transmitted.
* @param string $password
* @return string
*/
public function hashPassword(string $password): string
{
try {
return sodium_bin2hex(sodium_crypto_generichash($password, "", 32));
} catch (SodiumException $e) {
throw new RuntimeException('Could not generate password hash.', 0, $e);
}
}
/**
* Symmetrical (shared secret) encryption of a message with a password.
* @param string $data
* @param string|null $password
* @param bool $keyIsHex
* @return string
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function encryptWithSecret(string $data, ?string &$password = "", bool $keyIsHex = true): string
{
try {
if ($data === '') {
throw new InvalidArgumentException('Data to encrypt cannot be empty.');
}
if ($password === null || $password === '') {
$password = sodium_bin2hex(sodium_crypto_secretbox_keygen());
$keyIsHex = true;
}
$key = $password;
if ($keyIsHex) {
$key = sodium_hex2bin($password);
}
if (strlen($key) < SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
$key = sodium_crypto_generichash($key, "", 32);
}
if (strlen($key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new InvalidArgumentException('Key must be 32 bytes long');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$encrypted = sodium_crypto_secretbox($data, $nonce, $key);
$encrypted = sodium_bin2base64($nonce . $encrypted, SODIUM_BASE64_VARIANT_ORIGINAL);
sodium_memzero($data);
sodium_memzero($key);
sodium_memzero($nonce);
return $encrypted;
} catch (SodiumException $e) {
throw new RuntimeException('Could not encrypt data', 0, $e);
} catch (InvalidArgumentException $e) {
throw $e;
} catch (Exception $e) {
throw new RuntimeException('Unable to generate random bytes', 0, $e);
}
}
/**
* Symmetrical (shared secret) decryption of a message with a password.
* @param string $data
* @param string $password
* @param bool $keyIsHex
* @return string
* @throws RuntimeException
* @throws InvalidArgumentException
*/
public function decryptWithSecret(string $data, string $password, bool $keyIsHex = true): string
{
try {
if ($keyIsHex) {
$password = sodium_hex2bin($password);
}
if (strlen($password) < SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
$password = sodium_crypto_generichash($password, "", 32);
}
if (strlen($password) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new InvalidArgumentException('Key must be 32 bytes long');
}
$decoded = sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL);
if (strlen($decoded) < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
throw new InvalidArgumentException('Encrypted data is too short');
}
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $password);
if ($plaintext === false) {
throw new InvalidArgumentException('Could not decrypt data; probably the wrong password');
}
sodium_memzero($ciphertext);
sodium_memzero($nonce);
sodium_memzero($password);
return $plaintext;
} catch (SodiumException $e) {
throw new RuntimeException('Could not decrypt data; probably password wrong format', 0, $e);
}
}
/**
* Symmetrical message authentication code (MAC) of a message with a password.
* Returns a hex string of the message signature.
*/
public function signWithSecret(string $data, ?string &$key = "", bool $keyIsHex = true): string
{
try {
if ($key === null || $key === "") {
$key = $this->generateSigningSecret();
}
$realKey = $key;
if ($keyIsHex) {
$realKey = sodium_hex2bin($key);
}
if (strlen($realKey) !== SODIUM_CRYPTO_AUTH_KEYBYTES) {
throw new InvalidArgumentException('The key must be ' . SODIUM_CRYPTO_AUTH_KEYBYTES . ' long.');
}
if (strlen($data) === 0) {
throw new InvalidArgumentException('The data must not be empty.');
}
$result = sodium_bin2hex(sodium_crypto_auth($data, $realKey));
sodium_memzero($data);
sodium_memzero($realKey);
return $result;
} catch (SodiumException $e) {
throw new RuntimeException('Could not sign data', 0, $e);
}
}
/**
* Verifies the MAC of a message with a password.
*/
public function verifyWithSecret(string $signature, string $data, string $key, bool $keyIsHex = true): bool
{
try {
$signature = sodium_hex2bin($signature);
if ($keyIsHex) {
$key = sodium_hex2bin($key);
}
if (strlen($key) !== SODIUM_CRYPTO_AUTH_KEYBYTES) {
throw new InvalidArgumentException('The key must be ' . SODIUM_CRYPTO_AUTH_KEYBYTES . ' long.');
}
$result = sodium_crypto_auth_verify($signature, $data, $key);
sodium_memzero($signature);
sodium_memzero($key);
return $result;
} catch (SodiumException $e) {
throw new RuntimeException('Could not verify data', 0, $e);
}
}
/**
* Generates a random key for use with symmetrical signing and return as a hex string.
* @return string
*/
public function generateSigningSecret(): string
{
try {
return sodium_bin2hex(sodium_crypto_auth_keygen());
} catch (SodiumException $e) {
throw new RuntimeException('Could not generate signing key', 0, $e);
}
}
/**
* Generate a random key for use with symmetrical encryption and return as a hex string.
*/
public function generateEncryptionSecret(): string
{
try {
return sodium_bin2hex(sodium_crypto_secretbox_keygen());
} catch (SodiumException $e) {
throw new RuntimeException('Could not generate encryption key', 0, $e);
}
}
/**
* Generate an X25519 key pair and return the public and private key as an array of hex strings.
* If a password is supplied, the generated key pair will be deterministic.
*/
public function generateEncryptionKeypair(?string $password = ''): array
{
try {
$keypair = empty($password) ?
sodium_crypto_box_keypair() :
sodium_crypto_box_seed_keypair(sodium_crypto_generichash($password, "", 32));
return [
'keypair' => sodium_bin2hex($keypair),
'publicKey' => sodium_bin2hex(sodium_crypto_box_publickey($keypair)),
'privateKey' => sodium_bin2hex(sodium_crypto_box_secretkey($keypair)),
];
} catch (SodiumException $e) {
throw new RuntimeException('Could not generate keypair.', 0, $e);
}
}
/**
* Generate an Ed25519 key pair and return the public and private key as an array of hex strings.
* If a password is supplied, the generated key pair will be deterministic.
*/
public function generateSigningKeypair(?string $password = ''): array
{
try {
$keypair = empty($password) ?
sodium_crypto_sign_keypair() :
sodium_crypto_sign_seed_keypair(sodium_crypto_generichash($password, "", 32));
return [
'keypair' => sodium_bin2hex($keypair),
'publicKey' => sodium_bin2hex(sodium_crypto_sign_publickey($keypair)),
'privateKey' => sodium_bin2hex(sodium_crypto_sign_secretkey($keypair)),
];
} catch (SodiumException $e) {
throw new RuntimeException('Could not generate keypair.', 0, $e);
}
}
/**
* Sign a message with an Ed25519 private key and return the signed message.
*/
public function getSignedMessage(string $message, string $privateKey): string
{
try {
$privateKey = sodium_hex2bin($privateKey);
if (strlen($privateKey) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
throw new InvalidArgumentException('The key must be ' . SODIUM_CRYPTO_SIGN_SECRETKEYBYTES . ' long.');
}
if (strlen($message) === 0) {
throw new InvalidArgumentException('The message must not be empty.');
}
return sodium_bin2base64(sodium_crypto_sign($message, $privateKey), SODIUM_BASE64_VARIANT_ORIGINAL);
} catch (SodiumException $e) {
throw new RuntimeException('Could not sign data', 0, $e);
}
}
/**
* Verify a signed message with an Ed25519 public key, ensuring it hasn't been tampered with and return the message.
*/
public function verifySignedMessage(string $signedMessage, string $publicKey): string
{
try {
$publicKey = sodium_hex2bin($publicKey);
$signedMessage = sodium_base642bin($signedMessage, SODIUM_BASE64_VARIANT_ORIGINAL);
if (strlen($publicKey) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES) {
throw new InvalidArgumentException('The key must be ' . SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES . ' long.');
}
if (strlen($signedMessage) === 0) {
throw new InvalidArgumentException('The message must not be empty.');
}
$result = sodium_crypto_sign_open($signedMessage, $publicKey);
if ($result === false) {
throw new RuntimeException('Could not verify message.');
}
return $result;
} catch (SodiumException $e) {
throw new RuntimeException('Could not verify data', 0, $e);
}
}
/**
* Sign a message with an ED25519 private key and return the signature.
*/
public function getMessageSignature(string $message, string $privateKey): string
{
try {
$privateKey = sodium_hex2bin($privateKey);
if (strlen($privateKey) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
throw new InvalidArgumentException('The key must be ' . SODIUM_CRYPTO_SIGN_SECRETKEYBYTES . ' long.');
}
if (strlen($message) === 0) {
throw new InvalidArgumentException('The message must not be empty.');
}
return sodium_bin2hex(sodium_crypto_sign_detached($message, $privateKey));
} catch (SodiumException $e) {
throw new RuntimeException('Could not sign data', 0, $e);
}
}
/**
* Verify a signed message with an ED25519 public key, ensuring it hasn't been tampered with and return true
* if the signature is valid.
*/
public function verifyMessageSignature(string $message, string $signature, string $publicKey): bool
{
try {
$publicKey = sodium_hex2bin($publicKey);
$signature = sodium_hex2bin($signature);
if (strlen($publicKey) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES) {
throw new InvalidArgumentException('The key must be ' . SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES . ' long.');
}
if (strlen($message) === 0) {
throw new InvalidArgumentException('The message must not be empty.');
}
if (strlen($signature) !== SODIUM_CRYPTO_SIGN_BYTES) {
throw new InvalidArgumentException('The signature must be ' . SODIUM_CRYPTO_SIGN_BYTES . ' long.');
}
return sodium_crypto_sign_verify_detached($signature, $message, $publicKey);
} catch (SodiumException $e) {
throw new RuntimeException('Could not verify data', 0, $e);
}
}
/**
* Encrypt a message with the recipient public key and optionally sign with the sender private key.
* @param string $data
* @param string $recipientPublicKey
* @param string|null $senderPrivateKey
* @return string
*/
public function encryptWithKey(string $data, string $recipientPublicKey, ?string $senderPrivateKey = null): string
{
try {
if ($senderPrivateKey !== null) {
return $this->encryptAuthenticated($data, $recipientPublicKey, $senderPrivateKey);
}
// Anonymous encryption
$recipientPublicKey = sodium_hex2bin($recipientPublicKey);
$encrypted = sodium_crypto_box_seal($data, $recipientPublicKey);
sodium_memzero($recipientPublicKey);
sodium_memzero($data);
return sodium_bin2base64($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL);
} catch (SodiumException $e) {
throw new RuntimeException('Could not encrypt data', 0, $e);
} catch (Exception $e) {
throw new RuntimeException('Unable to generate random bytes', 0, $e);
}
}
/**
* Decrypt a message with either the recipient keypair for anonymous decryption, or
* the recipient private key and sender public key for authenticated decryption.
*/
public function decryptWithKey(string $data, string $recipientKey, ?string $senderPublicKey = null): string
{
try {
if ($senderPublicKey !== null) {
return $this->decryptAuthenticated($data, $recipientKey, $senderPublicKey);
}
// Anonymous decryption
$recipientKeyPair = sodium_hex2bin($recipientKey);
$decoded = sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL);
$plaintext = sodium_crypto_box_seal_open($decoded, $recipientKeyPair);
sodium_memzero($recipientKeyPair);
sodium_memzero($decoded);
sodium_memzero($data);
return $plaintext;
} catch (SodiumException $e) {
throw new RuntimeException('Could not decrypt data', 0, $e);
}
}
/**
* @throws SodiumException
* @throws Exception
*/
private function encryptAuthenticated(string $data, string $recipientPublicKey, string $senderPrivateKey): string
{
$senderPrivateKey = sodium_hex2bin($senderPrivateKey);
$recipientPublicKey = sodium_hex2bin($recipientPublicKey);
$key = sodium_crypto_box_keypair_from_secretkey_and_publickey($senderPrivateKey, $recipientPublicKey);
$nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
$ciphertext = sodium_crypto_box($data, $nonce, $key);
sodium_memzero($data);
sodium_memzero($key);
return sodium_bin2base64($nonce . $ciphertext, SODIUM_BASE64_VARIANT_ORIGINAL);
}
/**
* @throws SodiumException
*/
private function decryptAuthenticated(string $data, string $recipientPrivateKey, string $senderPublicKey): string
{
$senderPublicKey = sodium_hex2bin($senderPublicKey);
$recipientPrivateKey = sodium_hex2bin($recipientPrivateKey);
$key = sodium_crypto_box_keypair_from_secretkey_and_publickey($recipientPrivateKey, $senderPublicKey);
$data = sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL);
$nonce = substr($data, 0, SODIUM_CRYPTO_BOX_NONCEBYTES);
$ciphertext = substr($data, SODIUM_CRYPTO_BOX_NONCEBYTES);
$plaintext = sodium_crypto_box_open($ciphertext, $nonce, $key);
sodium_memzero($data);
sodium_memzero($key);
if ($plaintext === false) {
throw new RuntimeException('Decryption failed.');
}
return $plaintext;
}
}

View file

@ -0,0 +1,99 @@
<?php
use Gebler\Encryption\Encryption;
require_once('vendor/autoload.php');
$crypt = new Encryption();
/**
* Example 1: Symmetric (anonymous) message authentication.
*/
$message = "This is a message signed anonymously with a secret key.";
$secret = $crypt->generateSigningSecret();
$signature = $crypt->signWithSecret($message, $secret);
$messageAuthenticated = $crypt->verifyWithSecret($signature, $message, $secret);
if ($messageAuthenticated === true) {
echo "The message has not been tampered with.", PHP_EOL;
}
echo PHP_EOL,"-------------------------------------------------------",PHP_EOL;
/**
* Example 2: Asymmetric (identified) message authentication.
*/
$aliceSigningKeypair = $crypt->generateSigningKeypair();
$message = "This is a message signed by Alice.";
$signedMessage = $crypt->getSignedMessage($message, $aliceSigningKeypair['privateKey']);
echo $signedMessage, PHP_EOL;
$verifiedMessage = $crypt->verifySignedMessage($signedMessage, $aliceSigningKeypair['publicKey']);
echo $verifiedMessage, PHP_EOL;
echo PHP_EOL,"-------------------------------------------------------",PHP_EOL;
/**
* Example 3: Asymmetric (identified) message signature (detached).
*/
$aliceSigningKeypair = $crypt->generateSigningKeypair();
$message = "This is a message signed by Alice.";
$signature = $crypt->getMessageSignature($message, $aliceSigningKeypair['privateKey']);
$messageAuthenticated = $crypt->verifyMessageSignature($message, $signature, $aliceSigningKeypair['publicKey']);
if ($messageAuthenticated === true) {
echo "The message has not been tampered with.", PHP_EOL;
}
echo PHP_EOL,"-------------------------------------------------------",PHP_EOL;
/**
* Example 4: Symmetric encryption with secret key.
*/
$mySecret = $crypt->generateEncryptionSecret();
$message = "This is a test message.";
$encrypted = $crypt->encryptWithSecret($message, $mySecret);
echo $encrypted, PHP_EOL;
$decrypted = $crypt->decryptWithSecret($encrypted, $mySecret);
echo $decrypted, PHP_EOL;
$mySecret = random_bytes(32);
$message = "This is another test message.";
// Password is raw binary data.
$encrypted = $crypt->encryptWithSecret($message, $mySecret, false);
echo $encrypted, PHP_EOL;
$decrypted = $crypt->decryptWithSecret($encrypted, $mySecret, false);
echo $decrypted, PHP_EOL;
echo PHP_EOL,"-------------------------------------------------------",PHP_EOL;
/**
* Example 5: Asymmetric encryption with public key and signed by sender.
*/
$aliceKeypair = $crypt->generateEncryptionKeypair("alice_secret");
$bobKeypair = $crypt->generateEncryptionKeypair("bob_secret");
$message = "Hello Bob! This is a secret message from Alice.";
$encrypted = $crypt->encryptWithKey($message, $bobKeypair['publicKey'], $aliceKeypair['privateKey']);
echo $encrypted, PHP_EOL;
$decrypted = $crypt->decryptWithKey($encrypted, $bobKeypair['privateKey'], $aliceKeypair['publicKey']);
echo $decrypted, PHP_EOL;
echo PHP_EOL,"-------------------------------------------------------",PHP_EOL;
/**
* Example 6: Anonymous asymmetric encryption to recipient with public key only.
*/
$bobKeypair = $crypt->generateEncryptionKeypair("bob_secret");
$message = "Hello Bob! This is a secret message from an unknown sender.";
$encrypted = $crypt->encryptWithKey($message, $bobKeypair['publicKey']);
echo $encrypted, PHP_EOL;
$decrypted = $crypt->decryptWithKey($encrypted, $bobKeypair['keypair']);
echo $decrypted, PHP_EOL;
echo PHP_EOL,"-------------------------------------------------------",PHP_EOL;