Skip to content

Instantly share code, notes, and snippets.

@SagePtr
Last active March 20, 2026 19:04
Show Gist options
  • Select an option

  • Save SagePtr/861343de04877e8fffde5135779eff06 to your computer and use it in GitHub Desktop.

Select an option

Save SagePtr/861343de04877e8fffde5135779eff06 to your computer and use it in GitHub Desktop.
Mini QUIC generator (dirty code version, PHP)
<?php declare(strict_types=1);
/**
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <https://unlicense.org>
**/
function quic_int(int $x): string {
if ($x < 0x40) {
$msb = 0x00;
$data = chr($x);
} else if ($x < 0x40_00) {
$msb = 0x40;
$data = pack('n', $x);
} else if ($x < 0x40_00_00_00) {
$msb = 0x80;
$data = pack('N', $x);
} else {
$msb = 0xC0;
$data = pack('J', $x);
}
$data[0] = chr(ord($data[0]) | $msb);
return $data;
}
function quic_str(string $str): string {
return chr(strlen($str)) . $str;
}
// single round of HMAC, as QUIC key derivation doesn't need more
function quic_derive_secret(string $secret, int $length, string $label, string $context = ''): string {
$label = 'tls13 ' . $label;
$data = pack('n', $length) . quic_str($label) . quic_str($context);
$hmac = hash_hmac('sha256', $data . "\x01", $secret, true);
return substr($hmac, 0, $length);
}
function quic_crypto_frame(string $data, int $offset = 0): string {
return "\x06" . quic_int($offset) . quic_int(strlen($data)) . $data;
}
function quic_initial_mini(string $dcid, string $scid = "", $token = "", string $pkn = "\0", string $payload = ''): string {
// generate header
$header = chr(0xC0 | (strlen($pkn) - 1));
$header .= "\0\0\0\x01";
$header .= quic_str($dcid);
$header .= quic_str($scid);
$header .= quic_str($token);
$header .= quic_int(strlen($payload) + strlen($pkn) + 16);
$header .= $pkn;
// derive keys
$salt = "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a";
$init_secret = hash_hmac('sha256', $dcid, $salt, true);
$csecret = quic_derive_secret($init_secret, 32, 'client in');
$quic_key = quic_derive_secret($csecret, 16, 'quic key');
$quic_iv = quic_derive_secret($csecret, 12, 'quic iv');
$quic_hp = quic_derive_secret($csecret, 16, 'quic hp');
// xor nonce with packet number
$iv = $quic_iv;
for ($i = 0; $i < strlen($pkn); $i++) {
$offset = strlen($iv) - strlen($pkn) + $i;
$iv[$offset] = $iv[$offset] ^ $pkn[$i];
}
// encrypt payload
$cpayload = openssl_encrypt($payload, 'aes-128-gcm', $quic_key, OPENSSL_RAW_DATA, $iv, $tag, $header);
$cpayload .= $tag;
// derive header protection mask
$sample = substr($cpayload, 4 - strlen($pkn), 16);
$mask = openssl_encrypt($sample, 'aes-128-ecb', $quic_hp, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING);
// protect header
$header[0] = $header[0] ^ ($mask[0] & "\x0F");
for ($i = 0; $i < strlen($pkn); $i++) {
$offset = strlen($header) - strlen($pkn) + $i;
$header[$offset] = $header[$offset] ^ $mask[$i + 1];
}
return $header . $cpayload;
}
function quic_tls_ext(int $code, string $content = ''): string {
$data = pack('n', $code);
$data .= pack('n', strlen($content));
$data .= $content;
return $data;
}
function quic_tls_ext_sni(string $sni): string {
$codedsni = "\0" . pack('n', strlen($sni)) . $sni;
$sniext = pack('n', strlen($codedsni)) . $codedsni;
return quic_tls_ext(0, $sniext);
}
function quic_tls_clienthello(string $sni = 'localhost'): string {
$exts = quic_tls_ext_sni($sni);
$ch = implode('', ["\x03\x03", random_bytes(32), "\0\0\0\0", pack('n', strlen($exts)), $exts]);
return "\x01" . substr(pack('N', strlen($ch)), 1, 3) . $ch;
}
function generate(string $sni): string {
$dcid = random_bytes(1);
$scid = '';
$pkn = "\0";
$payload = quic_crypto_frame(quic_tls_clienthello($sni));
return quic_initial_mini($dcid, $scid, pkn: $pkn, payload: $payload);
}
$sni = filter_input(INPUT_GET, 'sni', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?: 'w3.org';
$quic = generate($sni);
$parts = [substr($quic, 0, 31), 22, substr($quic, 53, -16), 16];
$zebra = '';
foreach ($parts as $p) {
if (is_int($p)) $zebra .= "<r $p>";
else $zebra .= '<b 0x' . bin2hex($p) . '>';
}
header('Content-Type: application/json');
echo json_encode([
"sni" => $sni,
"awg" => $zebra,
"hex" => bin2hex($quic)
], JSON_PRETTY_PRINT);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment