true, 'alg' => 'A256GCM', 'payload' => base64_encode(qdb_aes256_gcm_encrypt_bytes($plainJson, $key)), ]; } function qdb_decrypt_sensitive_envelope(array $envelope, string $tokenHex): string { if (empty($envelope['encrypted']) || !isset($envelope['payload']) || !is_string($envelope['payload'])) { throw new InvalidArgumentException('Invalid encrypted envelope'); } $raw = base64_decode($envelope['payload'], true); if ($raw === false) { throw new InvalidArgumentException('Invalid base64 payload'); } $key = hkdf_session_key_from_token($tokenHex); $alg = isset($envelope['alg']) && is_string($envelope['alg']) ? $envelope['alg'] : 'A256CBC'; return match ($alg) { 'A256GCM' => qdb_aes256_gcm_decrypt_bytes($raw, $key), 'A256CBC' => aes256_cbc_decrypt_bytes($raw, $key), default => throw new InvalidArgumentException('Unsupported encrypted envelope algorithm'), }; } function qdb_aes256_gcm_encrypt_bytes(string $plain, string $key): string { $key = str_pad(substr($key, 0, 32), 32, "\0"); $iv = random_bytes(12); $tag = ''; $cipher = openssl_encrypt($plain, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, ''); if ($cipher === false || strlen($tag) !== 16) { throw new RuntimeException('openssl_encrypt failed'); } return $iv . $cipher . $tag; } function qdb_aes256_gcm_decrypt_bytes(string $data, string $key): string { $ivLen = 12; $tagLen = 16; if (strlen($data) <= $ivLen + $tagLen) { throw new InvalidArgumentException('cipher too short'); } $key = str_pad(substr($key, 0, 32), 32, "\0"); $iv = substr($data, 0, $ivLen); $tag = substr($data, -$tagLen); $ct = substr($data, $ivLen, -$tagLen); $plain = openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, ''); if ($plain === false) { throw new InvalidArgumentException('openssl_decrypt failed'); } return $plain; }