PHP Classes

File: src/Util.php

Recommend this page to a friend!
  Packages of Scott Arciszewski   Halite   src/Util.php   Download  
File: src/Util.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!
Date: 10 years ago
Size: 4,940 bytes
 

Contents

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

abstract class Util
{
    /**
     * Use a derivative of HKDF to derive multiple keys from one.
     * http://tools.ietf.org/html/rfc5869
     * 
     * This is a variant from hash_hkdf() and instead uses BLAKE2b provided by
     * libsodium.
     * 
     * Important: instead of a true HKDF (from HMAC) construct, this uses the 
     * \Sodium\crypto_generichash() key parameter. This is *probably* okay.
     * 
     * @param string $ikm Initial Keying Material
     * @param int $length How many bytes?
     * @param string $info What sort of key are we deriving?
     * @param string $salt
     * @return string
     * @throws \ParagonIE\Halite\Alerts\InvalidDigestLength
     * @throws \ParagonIE\Halite\Alerts\CannotPerformOperation
     */
    public static function hkdfBlake2b(
        string $ikm,
        int $length,
        string $info = '',
        string $salt = ''
    ): string {
        // Sanity-check the desired output length.
        if (empty($length)
            || !\is_int($length)
            || $length < 0
            || $length > 255 * \Sodium\CRYPTO_GENERICHASH_KEYBYTES
        ) {
            throw new \ParagonIE\Halite\Alerts\InvalidDigestLength(
                'Bad HKDF Digest Length'
            );
        }
        // "If [salt] not provided, is set to a string of HashLen zeroes."
        if (empty($salt)) {
            $salt = \str_repeat("\x00", \Sodium\CRYPTO_GENERICHASH_KEYBYTES);
        }

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

        // HKDF-Expand:
        // This check is useless, but it serves as a reminder to the spec.
        if (self::safeStrlen($prk) < \Sodium\CRYPTO_GENERICHASH_KEYBYTES) {
            throw new \ParagonIE\Halite\Alerts\CannotPerformOperation(
                'An unknown error has occurred'
            );
        }
        // T(0) = ''
        $t = '';
        $last_block = '';
        for ($block_index = 1; self::safeStrlen($t) < $length; ++$block_index) {
            // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
            $last_block = \Sodium\crypto_generichash(
                $last_block . $info . \chr($block_index),
                $prk
            );
            // T = T(1) | T(2) | T(3) | ... | T(N)
            $t .= $last_block;
        }
        // ORM = first L octets of T
        $orm = self::safeSubstr($t, 0, $length);
        if ($orm === false) {
            throw new \ParagonIE\Halite\Alerts\CannotPerformOperation(
                'An unknown error has occurred'
            );
        }
        return $orm;
    }
    
    /**
     * Safe string length
     * 
     * @ref mbstring.func_overload
     *
     * @staticvar boolean $exists
     * @param string $str
     * @return int
     * @throws \ParagonIE\Halite\Alerts\CannotPerformOperation
     */
    public static function safeStrlen($str)
    {
        static $exists = null;
        if ($exists === null) {
            $exists = \function_exists('mb_strlen');
        }
        if (!\is_string($str)) {
            throw new Alerts\InvalidType(
                'A string was expected.'
            );
        }
        if ($exists) {
            $length = \mb_strlen($str, '8bit');
            if ($length === false) {
                throw new Alerts\CannotPerformOperation(
                    'mb_strlen() failed unexpectedly'
                );
            }
        } else {
            // If we reached here, we can rely on strlen to count bytes:
            $length = \strlen($str);
            if ($length === false) {
                throw new Alerts\CannotPerformOperation(
                    'strlen() failed unexpectedly'
                );
            }
        }
        return $length;
    }
    
    /**
     * Safe substring
     *
     * @staticvar boolean $exists
     * @param string $str
     * @param int $start
     * @param int $length
     * @return string
     */
    public static function safeSubstr($str, $start, $length = null)
    {
        static $exists = null;
        if ($exists === null) {
            $exists = \function_exists('mb_substr');
        }
        if ($exists) {
            // mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP
            // 5.3, so we have to find the length ourselves.
            if ($length === null) {
                if ($start >= 0) {
                    $length = self::safeStrlen($str) - $start;
                } else {
                    $length = -$start;
                }
            }
            return \mb_substr($str, $start, $length, '8bit');
        }
        // Unlike mb_substr(), substr() doesn't accept NULL for length
        if ($length !== null) {
            return \substr($str, $start, $length);
        } else {
            return \substr($str, $start);
        }
    }
}