PHP Classes

File: src/Util.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   PHP PASeTo   src/Util.php   Download  
File: src/Util.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP PASeTo
Encrypt and decrypt data with PaSeTO protocol
Author: By
Last change:
Date: 4 years ago
Size: 6,127 bytes
 

Contents

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

use
ParagonIE\ConstantTime\{
   
Base64UrlSafe,
   
Binary
};
use
ParagonIE\Paseto\Exception\PasetoException;

/**
 * Class Util
 * @package ParagonIE\Paseto
 */
abstract class Util
{
   
/**
     * Computes the HKDF key derivation function specified in
     * http://tools.ietf.org/html/rfc5869.
     *
     * Adapted from defuse/php-encryption
     * @ref https://github.com/defuse/php-encryption/blob/aa72b8bc85311dbcc56c080823f0be12d78331c7/src/Core.php#L116-L190
     *
     * @param string $hash Hash Function
     * @param string $ikm Initial Keying Material
     * @param int $length How many bytes?
     * @param string $info What sort of key are we deriving?
     * @param string|null $salt
     *
     * @return string
     * @psalm-suppress MixedInferredReturnType This always returns a string!
     * @throws PasetoException
     * @throws \TypeError
     */
   
public static function HKDF(
       
string $hash,
       
string $ikm,
       
int $length,
       
string $info = '',
       
string $salt = null
   
): string {
        static
$nativeHKDF = null;
        if (
$nativeHKDF === null) {
           
$nativeHKDF = \is_callable('\\hash_hkdf');
        }
        if (
$nativeHKDF) {
           
/**
             * @psalm-suppress UndefinedFunction
             * This is wrapped in an is_callable() check.
             */
           
return (string) \hash_hkdf($hash, $ikm, $length, $info, $salt ?? '');
        }

       
$digest_length = Binary::safeStrlen(
            \
hash_hmac($hash, '', '', true)
        );

       
// Sanity-check the desired output length.
       
if (empty($length) || $length < 0 || $length > 255 * $digest_length) {
            throw new
PasetoException(
               
'Bad output length requested of HKDF.'
           
);
        }

       
// "if [salt] not provided, is set to a string of HashLen zeroes."
       
if (\is_null($salt)) {
           
$salt = \str_repeat("\x00", $digest_length);
        }

       
// HKDF-Extract:
        // PRK = HMAC-Hash(salt, IKM)
        // The salt is the HMAC key.
       
$prk = \hash_hmac($hash, $ikm, $salt, true);

       
// HKDF-Expand:

        // This check is useless, but it serves as a reminder to the spec.
       
if (Binary::safeStrlen($prk) < $digest_length) {
            throw new
PasetoException(
               
'An unexpected condition occurred in the HKDF internals'
           
);
        }

       
// T(0) = ''
       
$t = '';
       
$last_block = '';
        for (
$block_index = 1; Binary::safeStrlen($t) < $length; ++$block_index) {
           
// T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
           
$last_block = \hash_hmac(
               
$hash,
               
$last_block . $info . \chr($block_index),
               
$prk,
               
true
           
);
           
// T = T(1) | T(2) | T(3) | ... | T(N)
           
$t .= $last_block;
        }

       
// ORM = first L octets of T
        /** @var string $orm */
       
$orm = Binary::safeSubstr($t, 0, $length);
        return (string)
$orm;
    }

   
/**
     * Format the Additional Associated Data.
     *
     * Prefix with the length (64-bit unsigned little-endian integer)
     * followed by each message. This provides a more explicit domain
     * separation between each piece of the message.
     *
     * Each length is masked with PHP_INT_MAX using bitwise AND (&) to
     * clear out the MSB of the total string length.
     *
     * @param string ...$pieces
     * @return string
     */
   
public static function preAuthEncode(string ...$pieces): string
   
{
       
$accumulator = \ParagonIE_Sodium_Core_Util::store64_le(\count($pieces) & PHP_INT_MAX);
        foreach (
$pieces as $piece) {
           
$len = Binary::safeStrlen($piece);
           
$accumulator .= \ParagonIE_Sodium_Core_Util::store64_le($len & PHP_INT_MAX);
           
$accumulator .= $piece;
        }
        return
$accumulator;
    }

   
/**
     * If a footer was included with the message, first verify that
     * it's equivalent to the one we expect, then remove it from the
     * token payload.
     *
     * @param string $payload
     * @return string
     * @throws \TypeError
     */
   
public static function extractFooter(string $payload): string
   
{
       
/** @var array<int, string> $pieces */
       
$pieces = \explode('.', $payload);
        if (\
count($pieces) > 3) {
            return
Base64UrlSafe::decode((string) \array_pop($pieces));
        }
        return
'';
    }

   
/**
     * If a footer was included with the message, first verify that
     * it's equivalent to the one we expect, then remove it from the
     * token payload.
     *
     * @param string $payload
     * @return string
     * @throws \TypeError
     */
   
public static function removeFooter(string $payload): string
   
{
       
$pieces = \explode('.', $payload);
        if (\
count($pieces) > 3) {
            return \
implode('.', \array_slice($pieces, 0, 3));
        }
        return
$payload;
    }

   
/**
     * If a footer was included with the message, first verify that
     * it's equivalent to the one we expect, then remove it from the
     * token payload.
     *
     * @param string $payload
     * @param string $footer
     * @return string
     * @throws PasetoException
     * @throws \TypeError
     */
   
public static function validateAndRemoveFooter(
       
string $payload,
       
string $footer = ''
   
): string {
        if (empty(
$footer)) {
            return
$payload;
        }
       
$footer = Base64UrlSafe::encodeUnpadded($footer);
       
$payload_len = Binary::safeStrlen($payload);
       
$footer_len = Binary::safeStrlen($footer) + 1;

       
$trailing = Binary::safeSubstr(
           
$payload,
           
$payload_len - $footer_len,
           
$footer_len
       
);
        if (!\
hash_equals('.' . $footer, $trailing)) {
            throw new
PasetoException('Invalid message footer');
        }
        return
Binary::safeSubstr($payload, 0, $payload_len - $footer_len);
    }
}