PHP Classes

File: src/Operations/PBKW/PBKWv3.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   PASERK PHP   src/Operations/PBKW/PBKWv3.php   Download  
File: src/Operations/PBKW/PBKWv3.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,426 bytes
 

Contents

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

use
ParagonIE\ConstantTime\{
   
Base64UrlSafe,
   
Binary
};
use
ParagonIE\HiddenString\HiddenString;
use
ParagonIE\Paserk\Util;
use
ParagonIE\Paserk\Operations\{
   
PBKW,
   
PBKWInterface
};
use
ParagonIE\Paserk\PaserkException;
use
ParagonIE\Paseto\KeyInterface;
use
ParagonIE\Paseto\Keys\{
   
AsymmetricSecretKey,
   
SymmetricKey
};
use
ParagonIE\Paseto\Protocol\Version3;
use
ParagonIE\Paseto\ProtocolInterface;
use
Exception;
use
TypeError;
use function
   
hash,
   
hash_equals,
   
hash_hmac,
   
hash_pbkdf2,
   
openssl_decrypt,
   
openssl_encrypt,
   
pack,
   
random_bytes,
   
unpack;

/**
 * Class PBKWv3
 * @package ParagonIE\Paserk\Operations\PBKW
 */
class PBKWv3 implements PBKWInterface
{
   
/**
     * @return string
     */
   
public static function localHeader(): string
   
{
        return
'k3.local-pw.';
    }

   
/**
     * @return string
     */
   
public static function secretHeader(): string
   
{
        return
'k3.secret-pw.';
    }

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

   
/**
     * @param KeyInterface $key
     * @param HiddenString $password
     * @param array $options
     * @return string
     *
     * @throws Exception
     * @throws PaserkException
     */
   
public function wrapWithPassword(
       
KeyInterface $key,
       
HiddenString $password,
        array
$options = []
    ):
string {
        if (
$key instanceof SymmetricKey) {
           
$header = static::localHeader();
        } elseif (
$key instanceof AsymmetricSecretKey) {
           
$header = static::secretHeader();
        } else {
            throw new
PaserkException('Invalid key type');
        }

       
// Step 1:
       
$salt = random_bytes(32);
       
$iterations = $options['iterations'] ?? 100000;
       
$iterPack = pack('N', $iterations);

       
// Step 2:
       
$preKey = hash_pbkdf2('sha384', $password->getString(), $salt, $iterations, 32, true);

       
// Step 3:
       
$Ek = Binary::safeSubstr(
           
hash(
               
'sha384',
               
PBKW::DOMAIN_SEPARATION_ENCRYPT . $preKey,
               
true
           
),
           
0,
           
32
       
);
       
/// @SPEC DETAIL: Must be prefixed with 0xFF

        // Step 4:
       
$Ak = hash('sha384', PBKW::DOMAIN_SEPARATION_AUTH . $preKey, true);
       
/// @SPEC DETAIL: ^ Must be prefixed with 0xFE

        // Step 5:
       
$nonce = random_bytes(16);

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

       
// Step 7:
       
$tag = hash_hmac(
           
'sha384',
           
$header . $salt . $iterPack . $nonce . $edk,
           
$Ak,
           
true
       
);

       
// Step 8:
       
return Base64UrlSafe::encodeUnpadded(
           
$salt .
           
$iterPack .
           
$nonce .
           
$edk .
           
$tag
       
);
    }

   
/**
     * @param string $header
     * @param string $wrapped
     * @param HiddenString $password
     * @return KeyInterface
     *
     * @throws Exception
     */
   
public function unwrapWithPassword(
       
string $header,
       
string $wrapped,
       
HiddenString $password
   
): KeyInterface {
       
$decoded = Base64UrlSafe::decode($wrapped);
       
$decodedLen = Binary::safeStrlen($decoded);

       
// Split into components
       
$salt = Binary::safeSubstr($decoded, 0, 32);
       
$iterPack = Binary::safeSubstr($decoded, 32, 4);
       
$nonce = Binary::safeSubstr($decoded, 36, 16);
       
$edk = Binary::safeSubstr($decoded, 52, $decodedLen - 100);
       
$tag = Binary::safeSubstr($decoded, $decodedLen - 48, 48);

       
$iterations = unpack('N', $iterPack)[1];

       
// Step 2:
       
$preKey = hash_pbkdf2('sha384', $password->getString(), $salt, $iterations, 32, true);

       
// Step 3:
       
$Ak = hash('sha384', PBKW::DOMAIN_SEPARATION_AUTH . $preKey, true);
       
/// @SPEC DETAIL: ^ Must be prefixed with 0xFE

        // Step 4:
       
$t2 = hash_hmac(
           
'sha384',
           
$header . $salt . $iterPack . $nonce . $edk,
           
$Ak,
           
true
       
);

       
// Step 5:
       
if (!hash_equals($t2, $tag)) {
           
Util::wipe($t2);
           
Util::wipe($Ak);
            throw new
PaserkException('Invalid password or wrapped key');
        }
       
/// @SPEC DETAIL: This check must be constant-time.

        // Step 6:
       
$Ek = Binary::safeSubstr(
           
hash('sha384', PBKW::DOMAIN_SEPARATION_ENCRYPT . $preKey, true),
           
0,
           
32
       
);
       
/// @SPEC DETAIL: Must be prefixed with 0xFF

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

       
// Step 8:
       
if (hash_equals($header, static::localHeader())) {
            return new
SymmetricKey($ptk, static::getProtocol());
        }
        if (
hash_equals($header, static::secretHeader())) {
            return new
AsymmetricSecretKey($ptk, static::getProtocol());
        }
        throw new
TypeError();
    }
}