Skip to content

Http request and response

HTTP fingerprints follow the p0f request and response layout. The matcher compares header order and a few header values to p0f.fp. The User-Agent or Server substring in expsw is not part of the match. It only flags a header that disagrees with the signature.

For HTTP traffic, signature layout is as follows:

sig = ver : horder : habsent : expsw
KeyDescription
ver0 HTTP/1.0, 1 HTTP/1.1, 2 HTTP/2, 3 HTTP/3, or * for any. The analyzer parses HTTP/1 and HTTP/2.
horderComma-separated, ordered list of headers that should appear in matching traffic. Substrings to match within each of these headers may be specified using a name=[value] notation. The signature will be matched even if other headers appear in between, as long as the list itself is matched in the specified sequence. Headers that usually do appear in the traffic, but may go away (e.g. Accept-Language if the user has no languages defined, or Referer if no referring site exists) should be prefixed with ’?’, e.g. ”?Referer”. P0f will accept their disappearance, but will not allow them to appear at any other location.
habsentComma-separated list of headers that must *not* appear in matching traffic. This is particularly useful for noting the absence of standard headers (e.g. ‘Host’), or for differentiating between otherwise very similar signatures.
expswExpected substring in ‘User-Agent’ or ‘Server’. This is not used to match traffic, and merely serves to detect dishonest software. If you want to explicitly match User-Agent, you need to do this in the ‘horder’ section.

A request fingerprint is the client’s header order. The label, when the database has one, is a browser.

Terminal window
[HTTP Request] 1.2.3.4:1524 → 4.3.2.1:80
Browser: Firefox
Lang: English
Params: none
UA/OS: consistent (Windows)
Sig: 1:Host,User-Agent,Accept=[,*/*;q=],?Accept-Language=[;q=],Accept-Encoding=[gzip, deflate],?DNT=[1],Connection=[keep-alive],?Referer:Accept-Charset,Keep-Alive:Firefox/
  • Browser: label.name when it is set. This sample matches s:!:Firefox:10.x or newer; the line prints Firefox, and 10.x or newer is browser.variant. HTTP matching is exact only (Specific / Generic); there is no fuzzy tier. No match prints ???.
  • Lang: The detected language from Accept-Language header.
  • Params: Match notes (none, dishonest, anonymous, generic). expsw never rejects; a contradicting User-Agent sets dishonest.
  • UA/OS: Printed consistent (Windows), divergent (ua=…, net=…), or not checked (…). The enum is Consistent / Divergent / NotChecked. See Matching.
  • Sig: The HTTP request signature showing header order and values.

A response fingerprint is the server’s header order. The label, when the database has one, is a web server.

Terminal window
[HTTP Response] 91.189.91.21:80 → 192.168.1.22:58494
Server: nginx
Params: none
Sig: 1:Server,Date,Content-Type,?Content-Length,?Last-Modified,Connection=[close]:Keep-Alive,Accept-Ranges:nginx/

The arrow is the host that sent the response, then the client. Server is label.name (s:!:nginx:1.x → nginx; flavor 1.x is web_server.variant). The Server header text is the expsw field (nginx/), not the Server: line. anonymous is a different case: the response had no Server header at all.

  • Server: The identified web server, when a matcher is attached.
  • Params: Match notes (none, dishonest, anonymous, generic), same rules as requests.
  • Sig: The HTTP response signature showing header order and values.

The p0f HTTP signature is coarser than the Akamai string. Headers named in horder must show up in that order. Headers that are not in the list may sit between them, and a ? header may be missing. That still identifies a browser family more often than one build.

Akamai HTTP/2 fingerprinting works at a lower level and is considerably more precise. The fingerprint reflects four components of the raw HTTP/2 connection setup:

  • SETTINGS frame parameters and their order
  • WINDOW_UPDATE value
  • PRIORITY frames (stream weights and dependencies)
  • Pseudo-header order (:method, :path, :authority, :scheme)

The pseudo-header order is the key distinction. HTTP/2 clients send pseudo-headers in a specific order that is determined by the implementation, not by the application layer. Chrome, Firefox, Safari, and curl each produce a different order, and that order is stable across versions. Because it sits below the application, it cannot be spoofed by simply reordering headers in user code.

Akamai HTTP/2 (in huginn-net-http) is a parser / library API: given the connection preface signals (SETTINGS, flow control, PRIORITY behaviour, pseudo-header order), it produces the printable pipe-separated string and a stable 32-character hex hash. It does not capture packets by itself. You call it from tests, a proxy, or any pipeline that already sees the client HTTP/2.

Shape: SETTINGS | WINDOW_UPDATE | PRIORITY | pseudo-headers, plus an optional stable hash (32 hex)

Unpacked. Pipe-separated segments:

1:65536;2:0;3:1000;4:6291456;5:16384;6:262144SETTINGS (id:value;…)
|
15663105WINDOW_UPDATE
|
0PRIORITY
|
m,p,a,s:method :path …

With PRIORITY frames (test_akamai_fingerprint_with_priorities): when priorities are present, the middle segment encodes streams such as 1:0:0:221,3:0:0:201 (stream id, dependency, exclusive flag, weight).

Example (Chrome): 1:65536;2:0;3:1000;4:6291456;5:16384;6:262144|15663105|0|m,p,a,s

SegmentRole
SETTINGSHTTP/2 SETTINGS parameters (id:value pairs, semicolon-separated).
WINDOW_UPDATEInitial flow-control / window increment observed on the connection.
PRIORITY0 if none; otherwise stream dependencies and weights (see tests above).
Pseudo-headersOrder of :method, :path, :authority, :scheme abbreviated as m, p, a, s. The client picks that order. Reordering headers in application code does not change it.

Integrators sometimes forward the computed value as HTTP header x-huginn-net-akamai (e.g. a TLS terminator). That header is not produced by huginn-net alone. It comes from whatever wires the parser output into your stack. See TLS termination and the Akamai fingerprint.

TLS termination and the Akamai fingerprint

Section titled “TLS termination and the Akamai fingerprint”

huginn-net-http only implements the Akamai fingerprint computation from HTTP/2 preface inputs you provide. Capture and TLS termination live outside this crate.

The Akamai-style string is meaningless if those inputs were already rewritten. That happens after a hop that rebuilds the HTTP/2 connection and drops the original pseudo-header order.

A TLS-terminating reverse proxy, for example huginn-proxy, is one integration: it observes client HTTP/2 at the terminator, runs the fingerprint logic, and injects x-huginn-net-akamai so backends receive the signal.

The key constraint is always input fidelity: the parser produces a meaningful fingerprint only when the HTTP/2 preface it receives still reflects what the client originally sent.