PHP Classes

File: src/Operations/PKE/PKEv3.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   PASERK PHP   src/Operations/PKE/PKEv3.php   Download  
File: src/Operations/PKE/PKEv3.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PASERK PHP
Extend PASETO to wrap and serialize keys
Author: By
Last change:
Date: 1 year ago
Size: 5,937 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace
ParagonIE\Paserk\Operations\PKE;

use
ParagonIE\ConstantTime\{
   
Base64UrlSafe,
   
Binary,
   
Hex
};
use
ParagonIE\EasyECC\{
   
EasyECC,
   
ECDSA\PublicKey,
   
ECDSA\SecretKey
};
use
ParagonIE\Paseto\{
   
ProtocolInterface,
   
Keys\SymmetricKey,
   
Protocol\Version3
};
use
ParagonIE\Paserk\Operations\Key\{
   
SealingPublicKey,
   
SealingSecretKey
};
use
ParagonIE\Paserk\Operations\{
   
PKE,
   
PKEInterface
};
use
ParagonIE\Paserk\PaserkException;
use
ParagonIE\Paserk\Util;
use
Exception;
use
TypeError;
use function
   
hash,
   
hash_equals,
   
hash_hmac,
   
openssl_decrypt,
   
openssl_encrypt;

/**
 * Class PKEv3
 * @package ParagonIE\Paserk\Operations\PKE
 */
class PKEv3 implements PKEInterface
{
    use
PKETrait;

   
/**
     * @return string
     */
   
public static function header(): string
   
{
        return
'k3.seal.';
    }

   
/**
     * @return ProtocolInterface
     */
   
public static function getProtocol(): ProtocolInterface
   
{
        return new
Version3();
    }

   
/**
     * @link https://github.com/paseto-standard/paserk/blob/master/operations/PKE.md#v3-encryption
     *
     * @param SymmetricKey $ptk
     * @param SealingPublicKey $pk
     * @return string
     *
     * @throws Exception
     */
   
public function seal(SymmetricKey $ptk, SealingPublicKey $pk): string
   
{
       
$header = self::header();
       
$easyECC = new EasyECC('P384');

       
// Step 1:
       
$this->assertKeyVersion($pk);
       
$eph_sk = SecretKey::generate('P384');
       
/** @var PublicKey $eph_pk */
       
$eph_pk = $eph_sk->getPublicKey();
       
$seal_pk = PublicKey::importPem($pk->raw());

       
$pk_compressed = Hex::decode($seal_pk->toString());
       
$eph_pk_compressed = Hex::decode($eph_pk->toString());

       
// Step 2:
       
$xk = $easyECC->scalarmult($eph_sk, $seal_pk);

       
// Step 3:
       
$tmp = hash(
           
'sha384',
           
PKE::DOMAIN_SEPARATION_ENCRYPT . $header . $xk . $eph_pk_compressed . $pk_compressed,
           
true
       
);
       
/// @SPEC DETAIL: Prefix must be 0x01 for encryption keys
       
$Ek = Binary::safeSubstr($tmp, 0, 32);
       
$nonce = Binary::safeSubstr($tmp, 32, 16);

       
// Step 4:
       
$Ak = hash(
           
'sha384',
           
PKE::DOMAIN_SEPARATION_AUTH . $header . $xk . $eph_pk_compressed . $pk_compressed,
           
true
       
);
       
/// @SPEC DETAIL: Prefix must be 0x02 for authentication keys

        // Step 5:
       
$edk = openssl_encrypt(
           
$ptk->raw(),
           
'aes-256-ctr',
           
$Ek,
           
OPENSSL_RAW_DATA | OPENSSL_NO_PADDING,
           
$nonce
       
);

       
// Step 6:
       
$tag = hash_hmac(
           
'sha384',
           
$header . $eph_pk_compressed . $edk,
           
$Ak,
           
true
       
);
       
/// @SPEC DETAIL: h || epk || edk

       
Util::wipe($tmp);
       
Util::wipe($Ek);
       
Util::wipe($nonce);
       
Util::wipe($xk);
       
Util::wipe($Ak);

       
// Step 7:
       
return Base64UrlSafe::encodeUnpadded($tag . $eph_pk_compressed . $edk);
    }

   
/**
     * @link https://github.com/paseto-standard/paserk/blob/master/operations/PKE.md#v3-decryption
     *
     * @param string $header
     * @param string $encoded
     * @param SealingSecretKey $sk
     * @return SymmetricKey
     *
     * @throws PaserkException
     * @throws Exception
     */
   
public function unseal(string $header, string $encoded, SealingSecretKey $sk): SymmetricKey
   
{
        if (!
hash_equals($header, self::header())) {
            throw new
PaserkException('Header mismatch');
        }
       
$this->assertKeyVersion($sk);

       
$bin = Base64UrlSafe::decode($encoded);
       
$tag = Binary::safeSubstr($bin, 0, 48);
       
$eph_pk_compressed = Binary::safeSubstr($bin, 48, 49);
       
$edk = Binary::safeSubstr($bin, 97);

       
// Step 1:
       
$easyECC = new EasyECC('P384');
       
$seal_sk = SecretKey::importPem($sk->raw());
       
$eph_pk = PublicKey::fromString(Hex::encode($eph_pk_compressed), 'P384');

       
$xk = $easyECC->scalarmult($seal_sk, $eph_pk);

       
/** @var PublicKey $pk_obj */
       
$pk_obj = $seal_sk->getPublicKey();
        if (!(
$pk_obj instanceof PublicKey)) {
            throw new
TypeError("An unexpected type violation occurred");
        }
       
$pk_compressed = Hex::decode($pk_obj->toString());

       
// Step 2:
       
$Ak = hash(
           
'sha384',
           
PKE::DOMAIN_SEPARATION_AUTH . $header . $xk . $eph_pk_compressed . $pk_compressed,
           
true
       
);
       
/// @SPEC DETAIL: Prefix must be 0x02 for authentication keys

        // Step 3:
       
$t2 = hash_hmac(
           
'sha384',
           
$header . $eph_pk_compressed . $edk,
           
$Ak,
           
true
       
);
       
/// @SPEC DETAIL: h || epk || edk

        // Step 4:
       
if (!hash_equals($t2, $tag)) {
           
Util::wipe($t2);
           
Util::wipe($Ak);
            throw new
PaserkException('Invalid auth tag');
        }
       
/// @SPEC DETAIL: This must be a constant-time compare.

        // Step 5:
       
$tmp = hash(
           
'sha384',
           
PKE::DOMAIN_SEPARATION_ENCRYPT . $header . $xk . $eph_pk_compressed . $pk_compressed,
           
true
       
);
       
/// @SPEC DETAIL: Prefix must be 0x01 for encryption keys
       
$Ek = Binary::safeSubstr($tmp, 0, 32);
       
$nonce = Binary::safeSubstr($tmp, 32, 16);

       
// Step 6:
       
$ptk = openssl_decrypt(
           
$edk,
           
'aes-256-ctr',
           
$Ek,
           
OPENSSL_NO_PADDING | OPENSSL_RAW_DATA,
           
$nonce
       
);

       
Util::wipe($tmp);
       
Util::wipe($Ek);
       
Util::wipe($nonce);
       
Util::wipe($xk);
       
Util::wipe($Ak);
       
Util::wipe($t2);

       
// Step 7:
       
return new SymmetricKey($ptk, new Version3());
    }
}