= 0 && $c > 0; $i-- ) { $sum = $b[ $i ] + ( $c & 0xFF ); $b[ $i ] = $sum & 0xFF; $c = ( $c >> 8 ) + ( $sum >> 8 ); } return pack( 'C16', ...$b ); } /** * Blocks consumed by a byte length (CTR advances one block per 16 bytes). * * @param int $bytes Byte length. * @return int */ public static function blocks( $bytes ) { return intdiv( $bytes, 16 ) + ( ( $bytes % 16 ) ? 1 : 0 ); } /** * Derive the 32-byte MAC subkey from the encryption key. Encrypt-then-MAC must NOT reuse the * cipher key as the MAC key, so we derive an independent key from it with a fixed label * (HKDF-Expand-style single-block). Both backup and restore hold the cipher key, so both * arrive at the same MAC key with no extra material stored. * * @param string $enc_key Raw 32-byte cipher key. * @return string Raw 32-byte MAC key. */ public static function mac_key( $enc_key ) { return hash_hmac( 'sha256', 'aisv-mac-v1', (string) $enc_key, true ); } /** * Keyed integrity tag (HMAC-SHA256) over a whole file, streamed so a multi-gigabyte volume * never sits in memory. This is the authentication half of Encrypt-then-MAC: it binds the * ciphertext to the password, so tampering can't be forged (unlike the unkeyed .sha256). * * @param string $path File to authenticate (the encrypted volume). * @param string $enc_key Raw 32-byte cipher key. * @return string Lowercase-hex tag, or '' if unavailable. */ public static function file_mac( $path, $enc_key ) { if ( ! function_exists( 'hash_hmac_file' ) || ! is_string( $path ) || ! is_file( $path ) || '' === (string) $enc_key ) { return ''; } $mac = @hash_hmac_file( 'sha256', $path, self::mac_key( $enc_key ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged return is_string( $mac ) ? $mac : ''; } }