Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Crypt with OpenSSL

Single purpose class for encrypt and decrypt text with signing. Use at your own risk, I'm not a security guru. But I hope it is strong enough, it is more or less extracted from https://paragonie.com/ blog articles.

I used to use mcrypt on legacy projects, but it is dropped now in PHP 7.2. Until PHP 7.2 came to be mainstream with Sodium, I wrote this simple OpenSSL wrapper to be usable with PHP 5.6 to 7.2.

Usage:

$crypt = new Milo\Crypt;

$secretKey = 'Lkd:LKuweusn,AKjkjhskjmmNBEWKJhs';  # needs to be 32 bytes long
$signKey = 'By Milo lkjaj3yiujkeqr987m,na,msnd';  # this is secret too

$msg = $crypt->encrypt('Nazdar bazar.', $secretKey, $signKey);
echo "Encrypted: $msg\n";
echo "Decrypted: " . $crypt->decrypt($msg, $secretKey, $signKey);

Message encrypted in this way can be transferred via public medium, e.g. via URL. The $secretKey and $signKey are private and must not be published.

<?php
/**
* Copyright (c) 2015 Miloslav Hůla (https://github.com/milo)
*/
namespace Milo;
final class Crypt
{
const
CIPHER_METHOD = 'AES-256-CTR',
CRYPT_KEY_LENGTH = 32,
HASH_ALGORITHM = 'sha256',
HASH_LENGTH = 64;
public function __construct()
{
if (PHP_VERSION_ID < 50600) {
throw new CryptException('PHP 5.6.0 or newer is required.');
} elseif (!extension_loaded('openssl')) {
throw new CryptException('Missing OpenSSL PHP module.');
} elseif (!in_array(self::CIPHER_METHOD, openssl_get_cipher_methods(), true)) {
throw new CryptException('Missing OpenSSL encryption method ' . self::CIPHER_METHOD . '.');
}
}
/**
* @param string
* @param string
* @param string
* @param string
* @return string
* @throws CryptException
*/
public function encryptBinary($text, $encryptKey, $signatureKey, $iv = null)
{
if (strlen($encryptKey) !== self::CRYPT_KEY_LENGTH) {
throw new CryptException('Encryption key needs to be ' . self::CRYPT_KEY_LENGTH . ' bytes long, but ' . strlen($encryptKey) . ' given.');
}
if ($iv === null) {
$iv = self::randomBytes(openssl_cipher_iv_length(self::CIPHER_METHOD));
} elseif (($ivLen = strlen($iv)) !== ($ivNeedLen = openssl_cipher_iv_length(self::CIPHER_METHOD))) {
throw new CryptException("IV needs to be exactly $ivNeedLen bytes long, $ivLen given.");
}
$ciphered = $iv . openssl_encrypt(
$text,
self::CIPHER_METHOD,
$encryptKey,
OPENSSL_RAW_DATA,
$iv
);
return hash_hmac(self::HASH_ALGORITHM, $ciphered, $signatureKey) . $ciphered;
}
/**
* @param string
* @param string
* @param string
* @param string
* @return string
* @throws CryptException
*/
public function encrypt($text, $encryptKey, $signatureKey, $iv = null)
{
return base64_encode($this->encryptBinary($text, $encryptKey, $signatureKey, $iv));
}
/**
* @param string
* @param string
* @param string
* @return string
* @throws CryptException
*/
public function decryptBinary($text, $encryptKey, $signatureKey)
{
if (strlen($text) < self::HASH_LENGTH) {
throw new CryptException('Signed encrypted message is only ' . strlen($text) . ' bytes long, expected more.');
}
$hmac = substr($text, 0, self::HASH_LENGTH);
$text = substr($text, self::HASH_LENGTH);
if (!hash_equals(hash_hmac(self::HASH_ALGORITHM, $text, $signatureKey), $hmac)) {
throw new CryptException('Message signature does not match.');
}
$ivLength = openssl_cipher_iv_length(self::CIPHER_METHOD);
if (strlen($text) < $ivLength) {
throw new CryptException('Encrypted message is only ' . strlen($text) . ' bytes long, expected more.');
}
$iv = substr($text, 0, $ivLength);
$text = substr($text, $ivLength);
$decrypted = openssl_decrypt(
$text,
self::CIPHER_METHOD,
$encryptKey,
OPENSSL_RAW_DATA,
$iv
);
if ($decrypted === false) {
throw new CryptException('Message decryption failed.');
}
return $decrypted;
}
/**
* @param string
* @param string
* @param string
* @return string
* @throws CryptException
*/
public function decrypt($text, $encryptKey, $signatureKey)
{
return $this->decryptBinary(base64_decode($text), $encryptKey, $signatureKey);
}
private static function randomBytes($length)
{
if ($length < 1) {
throw new CryptException('Length must be greater then zero.');
}
if (!defined('PHP_WINDOWS_VERSION_BUILD') && is_readable('/dev/urandom')) {
return file_get_contents('/dev/urandom', false, null, -1, $length);
}
$bytes = openssl_random_pseudo_bytes($length, $secure);
if ($secure !== true) {
throw new CryptException('Random bytes are not cryptographically strong.');
}
return $bytes;
}
}
class CryptException extends \RuntimeException
{
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment