PHP Classes

File: src/Symmetric/Crypto.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   Halite   src/Symmetric/Crypto.php   Download  
File: src/Symmetric/Crypto.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Halite
Perform cryptography operations with libsodium
Author: By
Last change: For version 2, let's use strict types!
Merge branch 'v2.0' of https://github.com/paragonie/halite into v2.0

Conflicts:
src/Symmetric/Crypto.php
Update Crypto.php
Date: 8 years ago
Size: 8,139 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace
ParagonIE\Halite\Symmetric;

use \
ParagonIE\Halite\Alerts as CryptoException;
use \
ParagonIE\Halite\{
   
Contract\SymmetricKeyCryptoInterface,
   
Config,
   
Halite,
   
Symmetric\Config as SymmetricConfig,
   
Util as CryptoUtil
};

abstract class
Crypto implements SymmetricKeyCryptoInterface
{
   
/**
     * Authenticate a string
     *
     * @param string $message
     * @param AuthenticationKey $secretKey
     * @param boolean $raw
     * @throws CryptoException\InvalidKey
     * @return string
     */
   
public static function authenticate(
       
string $message,
       
AuthenticationKey $secretKey,
       
bool $raw = false
   
): string {
       
$config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'auth');
       
$mac = self::calculateMAC($message, $secretKey->get(), $config);
        if (
$raw) {
            return
$mac;
        }
        return \
Sodium\bin2hex($mac);
    }
   
   
/**
     * Decrypt a message using the Halite encryption protocol
     *
     * @param string $ciphertext
     * @param EncryptionKey $secretKey
     * @param boolean $raw Don't hex decode the input?
     * @return string
     * @throws CryptoException\InvalidMessage
     */
   
public static function decrypt(
       
string $ciphertext,
       
EncryptionKey $secretKey,
       
bool $raw = false
   
): string {
        if (!
$raw) {
           
// We were given hex data:
           
$ciphertext = \Sodium\hex2bin($ciphertext);
        }
        list(
$version, $config, $salt, $nonce, $xored, $auth) =
           
self::unpackMessageForDecryption($ciphertext);
       
       
// Split our keys
       
list($eKey, $aKey) = self::splitKeys($secretKey, $salt, $config);
       
       
// Check the MAC first
       
if (!self::verifyMAC(
           
$auth,
           
$version . $salt . $nonce . $xored,
           
$aKey
       
)) {
            throw new
CryptoException\InvalidMessage(
               
'Invalid message authentication code'
           
);
        }
       
       
// Down the road, do whatever logic around $version here, in case we
        // need to upgrade our protocol.
       
        // Add version logic above
       
$plaintext = \Sodium\crypto_stream_xor($xored, $nonce, $eKey);
        if (
$plaintext === false) {
            throw new
CryptoException\InvalidMessage(
               
'Invalid message authentication code'
           
);
        }
        return
$plaintext;
    }
   
   
/**
     * Encrypt a message using the Halite encryption protocol
     * (Encrypt then MAC -- Xsalsa20 then HMAC-SHA-512/256)
     *
     * @param string $plaintext
     * @param EncryptionKey $secretKey
     * @param boolean $raw Don't hex encode the output?
     * @return string
     */
   
public static function encrypt(
       
string $plaintext,
       
EncryptionKey $secretKey,
       
bool $raw = false
   
): string {
       
$config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'encrypt');
       
       
// Generate a nonce and HKDF salt:
       
$nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES);
       
$salt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN);
       
       
// Split our keys according to the HKDF salt:
       
list($eKey, $aKey) = self::splitKeys($secretKey, $salt, $config);
       
       
// Encrypt our message with the encryption key:
       
$xored = \Sodium\crypto_stream_xor($plaintext, $nonce, $eKey);
       
       
// Calculate an authentication tag:
       
$auth = self::calculateMAC(
           
Halite::HALITE_VERSION . $salt . $nonce . $xored,
           
$aKey
       
);
       
        \
Sodium\memzero($eKey);
        \
Sodium\memzero($aKey);
        if (!
$raw) {
            return \
Sodium\bin2hex(
               
Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth
           
);
        }
        return
Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth;
    }
   
   
/**
     * Split a key using a variant of HKDF that used a keyed BLAKE2b hash rather
     * than an HMAC construct
     *
     * @param EncryptionKey $master
     * @param string $salt
     * @param Config $config
     * @return array
     */
   
public static function splitKeys(
       
EncryptionKey $master,
       
string $salt = '',
       
Config $config = null
   
) {
       
$binary = $master->get();
        return [
           
CryptoUtil::hkdfBlake2b(
               
$binary,
                \
Sodium\CRYPTO_SECRETBOX_KEYBYTES,
               
$config->HKDF_SBOX,
               
$salt
           
),
           
CryptoUtil::hkdfBlake2b(
               
$binary,
                \
Sodium\CRYPTO_AUTH_KEYBYTES,
               
$config->HKDF_AUTH,
               
$salt
           
)
        ];
    }
   
   
/**
     * Unpack a message string into an array.
     *
     * @param string $ciphertext
     * @return array
     */
   
public static function unpackMessageForDecryption(string $ciphertext): array
    {
       
$length = CryptoUtil::safeStrlen($ciphertext);
       
       
// The first 4 bytes are reserved for the version size
       
$version = CryptoUtil::safeSubstr($ciphertext, 0, Halite::VERSION_TAG_LEN);
       
$config = SymmetricConfig::getConfig($version, 'encrypt');
       
       
// The HKDF is used for key splitting
       
$salt = CryptoUtil::safeSubstr(
           
$ciphertext,
           
Halite::VERSION_TAG_LEN,
           
$config->HKDF_SALT_LEN
       
);
       
       
// This is the nonce (we authenticated it):
       
$nonce = CryptoUtil::safeSubstr(
           
$ciphertext,
           
// 36:
           
Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN,
           
// 24:
           
\Sodium\CRYPTO_STREAM_NONCEBYTES
       
);
       
       
// This is the crypto_stream_xor()ed ciphertext
       
$xored = CryptoUtil::safeSubstr(
           
$ciphertext,
           
// 60:
               
Halite::VERSION_TAG_LEN +
               
$config->HKDF_SALT_LEN +
                \
Sodium\CRYPTO_STREAM_NONCEBYTES,
           
// $length - 92:
           
$length - (
               
Halite::VERSION_TAG_LEN +
               
$config->HKDF_SALT_LEN +
                \
Sodium\CRYPTO_STREAM_NONCEBYTES +
                \
Sodium\CRYPTO_AUTH_BYTES
           
)
        );
       
       
// $auth is the last 32 bytes
       
$auth = CryptoUtil::safeSubstr($ciphertext, $length - \Sodium\CRYPTO_AUTH_BYTES);
       
       
// We don't need this anymore.
       
\Sodium\memzero($ciphertext);
        return [
$version, $config, $salt, $nonce, $xored, $auth];
    }
   
   
/**
     * Verify a MAC, given a MAC key
     *
     * @param string $message
     * @param AuthenticationKey $secretKey
     * @param string $mac
     * @param boolean $raw
     * @return boolean
     */
   
public static function verify(
       
string $message,
       
AuthenticationKey $secretKey,
       
string $mac,
       
bool $raw = false
   
): bool {
        if (!
$raw) {
           
$mac = \Sodium\hex2bin($mac);
        }
        return
self::verifyMAC(
           
$mac,
           
$message,
           
$secretKey->get()
        );
    }
   
   
/**
     * Calculate a MAC
     *
     * @param string $message
     * @param string $authKey
     * @return string
     */
   
protected static function calculateMAC(
       
string $message,
       
string $authKey
   
): string {
        return \
Sodium\crypto_auth(
           
$message,
           
$authKey
       
);
    }
   
   
/**
     * Verify a MAC
     *
     * @param string $mac
     * @param string $message
     * @param string $aKey
     * @return bool
     */
   
protected static function verifyMAC(
       
string $mac,
       
string $message,
       
string $aKey
   
): bool {
        if (
CryptoUtil::safeStrlen($mac) !== \Sodium\CRYPTO_AUTH_BYTES) {
            throw new
CryptoException\InvalidSignature(
               
'Message Authentication Code is not the correct length; is it encoded?'
           
);
        }
        return \
Sodium\crypto_auth_verify(
           
$mac,
           
$message,
           
$aKey
       
);
    }
}