<?php
declare(strict_types=1);
namespace ParagonIE\Halite\Asymmetric;
use \ParagonIE\Halite\{
Contract\AsymmetricKeyCryptoInterface,
Contract\KeyInterface,
Alerts as CryptoException,
Util as CryptoUtil,
Symmetric\Crypto as SymmetricCrypto,
Symmetric\EncryptionKey
};
abstract class Crypto implements AsymmetricKeyCryptoInterface
{
/**
* Encrypt a string using asymmetric cryptography
* Wraps SymmetricCrypto::encrypt()
*
* @param string $source Plaintext
* @param EncryptionSecretKey $ourPrivateKey Our private key
* @param EncryptionPublicKey $theirPublicKey Their public key
* @param boolean $raw Don't hex encode the output?
*
* @return string
*/
public static function encrypt(
string $source,
EncryptionSecretKey $ourPrivateKey,
EncryptionPublicKey $theirPublicKey,
bool $raw = false
): string {
$ecdh = new EncryptionKey(
self::getSharedSecret($ourPrivateKey, $theirPublicKey)
);
$ciphertext = SymmetricCrypto::encrypt($source, $ecdh, $raw);
unset($ecdh);
return $ciphertext;
}
/**
* Decrypt a string using asymmetric cryptography
* Wraps SymmetricCrypto::decrypt()
*
* @param string $source Ciphertext
* @param EncryptionSecretKey $ourPrivateKey Our private key
* @param EncryptionPublicKey $theirPublicKey Their public key
* @param boolean $raw Don't hex decode the input?
* @return string
*/
public static function decrypt(
string $source,
EncryptionSecretKey $ourPrivateKey,
EncryptionPublicKey $theirPublicKey,
bool $raw = false
): string {
$ecdh = new EncryptionKey(
self::getSharedSecret($ourPrivateKey, $theirPublicKey)
);
$ciphertext = SymmetricCrypto::decrypt($source, $ecdh, $raw);
unset($ecdh);
return $ciphertext;
}
/**
* Diffie-Hellman, ECDHE, etc.
*
* Get a shared secret from a private key you possess and a public key for
* the intended message recipient
*
* @param EncryptionSecretKey $privateKey
* @param EncryptionPublicKey $publicKey
* @param bool $get_as_object Get as a Key object?
* @return string
*/
public static function getSharedSecret(
KeyInterface $privateKey,
KeyInterface $publicKey,
bool $get_as_object = false
) {
if ($get_as_object) {
return new EncryptionKey(
\Sodium\crypto_scalarmult(
$privateKey->get(),
$publicKey->get()
)
);
}
return \Sodium\crypto_scalarmult(
$privateKey->get(),
$publicKey->get()
);
}
/**
* Encrypt a message with a target users' public key
*
* @param string $source Message to encrypt
* @param EncryptionPublicKey $publicKey
* @param boolean $raw Don't hex encode the output?
* @return string
* @throws CryptoException\CannotPerformOperation
*/
public static function seal(
string $source,
EncryptionPublicKey $publicKey,
bool $raw = false
): string {
if (!$publicKey instanceof EncryptionPublicKey) {
throw new CryptoException\InvalidKey(
'Argument 2: Expected an instance of EncryptionPublicKey'
);
}
if (!function_exists('\\Sodium\\crypto_box_seal')) {
throw new CryptoException\CannotPerformOperation(
'crypto_box_seal is not available'
);
}
$sealed = \Sodium\crypto_box_seal($source, $publicKey->get());
if ($raw) {
return $sealed;
}
return \Sodium\bin2hex($sealed);
}
/**
* Sign a message with our private key
*
* @param string $message Message to sign
* @param SignatureSecretKey $privateKey
* @param boolean $raw Don't hex encode the output?
* @return string Signature (detached)
*/
public static function sign(
string $message,
SignatureSecretKey $privateKey,
bool $raw = false
): string {
$signed = \Sodium\crypto_sign_detached(
$message,
$privateKey->get()
);
if ($raw) {
return $signed;
}
return \Sodium\bin2hex($signed);
}
/**
* Decrypt a sealed message with our private key
*
* @param string $source Encrypted message (string or resource for a file)
* @param EncryptionSecretKey $privateKey
* @param boolean $raw Don't hex decode the input?
* @return string
* @throws CryptoException\InvalidKey
*/
public static function unseal(
string $source,
EncryptionSecretKey $privateKey,
bool $raw = false
): string {
if (!$raw) {
$source = \Sodium\hex2bin($source);
}
// Get a box keypair (needed by crypto_box_seal_open)
$secret_key = $privateKey->get();
$public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key);
$kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey(
$secret_key,
$public_key
);
// Wipe these immediately:
\Sodium\memzero($secret_key);
\Sodium\memzero($public_key);
// Now let's open that sealed box
$message = \Sodium\crypto_box_seal_open($source, $kp);
// Always memzero after retrieving a value
\Sodium\memzero($kp);
if ($message === false) {
throw new CryptoException\InvalidKey(
'Incorrect secret key for this sealed message'
);
}
// We have our encrypted message here
return $message;
}
/**
* Verify a signed message with the correct public key
*
* @param string $message Message to verify
* @param SignaturePublicKey $publicKey
* @param string $signature
* @param boolean $raw Don't hex decode the input?
* @return bool
* @throws CryptoException\InvalidSignature
*/
public static function verify(
string $message,
SignaturePublicKey $publicKey,
string $signature,
bool $raw = false
): bool {
if (!$raw) {
$signature = \Sodium\hex2bin($signature);
}
if (CryptoUtil::safeStrlen($signature) !== \Sodium\CRYPTO_SIGN_BYTES) {
throw new CryptoException\InvalidSignature(
'Signature is not the correct length; is it encoded?'
);
}
return \Sodium\crypto_sign_verify_detached(
$signature,
$message,
$publicKey->get()
);
}
}
|