3v4l.org

run code in 300+ PHP versions simultaneously
<?php if (PHP_VERSION_ID < 70000) { die('no'); } /** * Use HKDF to derive multiple keys from one. * http://tools.ietf.org/html/rfc5869 * * @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 $salt * @return string * @throws Exception */ function hash_hkdf(string $algo, string $ikm, int $length, string $info = '', $salt = null) { $digest_length = mb_strlen(hash_hmac($algo, '', '', true), '8bit'); // Sanity-check the desired output length. if (empty($length) || !is_int($length) || $length < 0 || $length > 255 * $digest_length) { throw new Exception("Bad output length requested of HKDF."); } // "if [salt] not provided, is set to a string of HashLen zeroes." if ($salt === null) { $salt = str_repeat("\x00", $digest_length); } // HKDF-Extract: // PRK = HMAC-Hash(salt, IKM) // The salt is the HMAC key. $prk = hash_hmac($algo, $ikm, $salt, true); // HKDF-Expand: // This check is useless, but it serves as a reminder to the spec. if (mb_strlen($prk, '8bit') < $digest_length) { throw new Exception('HKDF-Expand failed'); } // T(0) = '' $t = ''; $last_block = ''; for ($block_index = 1; mb_strlen($t, '8bit') < $length; ++$block_index) { // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??) $last_block = hash_hmac( $algo, $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 $orm = mb_substr($t, 0, $length, '8bit'); if ($orm === false) { throw new Exception('Unexpected error'); } return $orm; } /** * Encrypt a message with AES-256-CTR + HMAC-SHA256 * @param string $message * @param string $key * @return string */ function aes256ctr_hmacsha256_encrypt(string $message, string $key) { $iv = random_bytes(16); $salt = random_bytes(16); $eKey = hash_hkdf('sha256', $key, 32, 'Encryption Key', $salt); $aKey = hash_hkdf('sha256', $key, 32, 'Authentication Key', $salt); $ciphertext = $iv . $salt . openssl_encrypt( $message, 'aes-256-ctr', $eKey, OPENSSL_RAW_DATA, $iv ); $mac = hash_hmac('sha256', $ciphertext, $aKey, true); return base64_encode($mac . $ciphertext); } /** * Decrypt a message with AES-256-CTR + HMAC-SHA256 * @param string $message * @param string $key * @return string * @throws Exception */ function aes256ctr_hmacsha256_decrypt(string $ciphertext, string $key) { $decode = base64_decode($ciphertext); if ($decode === false) { throw new Exception("Encoding error"); } $mac = mb_substr($decode, 0, 32, '8bit'); $iv = mb_substr($decode, 32, 16, '8bit'); $salt = mb_substr($decode, 48, 16, '8bit'); $ciphertext = mb_substr($decode, 64, null, '8bit'); $aKey = hash_hkdf('sha256', $key, 32, 'Authentication Key', $salt); $calcMac = hash_hmac('sha256', $iv . $salt . $ciphertext, $aKey, true); if (!hash_equals($calcMac, $mac)) { throw new Exception("Invalid message"); } $eKey = hash_hkdf('sha256', $key, 32, 'Encryption Key', $salt); return openssl_decrypt( $ciphertext, 'aes-256-ctr', $eKey, OPENSSL_RAW_DATA, $iv ); } $key = random_bytes(32); $message = "This code rocks"; $ciphertext = aes256ctr_hmacsha256_encrypt($message, $key); var_dump($ciphertext); $plaintext = aes256ctr_hmacsha256_decrypt($ciphertext, $key); var_dump($plaintext);

preferences:
23.4 ms | 402 KiB | 5 Q