Browser fingerprints
A web client runs in a browser, and the browser is part of its identity. opentele2-browser
generates consistent browser HTTP headers and full browser fingerprints (screen, navigator,
codecs, plugins, WebGL card, fonts and more), sampled from Bayesian networks the way browserforge
samples them. The output is data: opentele2 does not drive a browser or inject anything, so you
hand the values to the automation tool you already use.
One identity, one seed. Seed the generator with an id you store with the account, and the same browser comes back on every run, in every language. Two accounts that share a seed share a browser.
Generate headers
from opentele2.browser import HeaderGenerator
headers = HeaderGenerator(browser="chrome", os="windows").generate(seed="my-id")
print(headers["User-Agent"])
# Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36The class takes browserforge’s argument names and defaults, so browserforge code keeps working.
import { generateHeaders } from 'opentele2'
const headers = await generateHeaders({ browsers: 'chrome', os: 'windows' }, 'my-id')
console.log(headers['User-Agent']) // the same user agent as Python for the same seed// cargo add opentele2-browser
use opentele2_browser::{Browser, HeaderOptions};
fn main() -> opentele2_browser::Result<()> {
let options = HeaderOptions {
browsers: vec![Browser::new("chrome")],
os: vec!["windows".to_owned()],
..HeaderOptions::default()
};
let headers = opentele2_browser::generate_headers(Some("my-id"), &options)?;
println!("{:?}", headers.get("User-Agent"));
Ok(())
}The headers come in sending order, from sec-ch-ua to Accept-Language, identical in all three
languages for one seed and options.
Narrow the options
| Python | Node | Rust (HeaderOptions) | Default |
|---|---|---|---|
browser | browsers | browsers | chrome, firefox, safari, edge |
os | os | os | windows, macos, linux, android, ios |
device | devices | devices | desktop, mobile |
locale | locales | locales | en-US; at most ten, most preferred first |
http_version | httpVersion | http_version | 2 |
strict | strict | strict | off: relax the options when nothing fits |
A browser can carry bounds on its major version: Browser(name, min_version, max_version, http_version) in Python, { name, minVersion, maxVersion } in Node, opentele2_browser::Browser
in Rust.
from opentele2.browser import Browser, HeaderGenerator
generator = HeaderGenerator(
browser=[Browser("firefox", min_version=120)], os="linux", locale=["de-DE", "en-US"]
)
headers = generator.generate(seed=7, request_dependent_headers={"Referer": "https://web.telegram.org/"})
print(headers["User-Agent"]) # ... Firefox/150.0
print(headers["Accept-Language"]) # de-DE;q=1.0, en-US;q=0.9generate(...) takes the same options again to override the generator’s for one call, plus
user_agent and request_dependent_headers.
import { generateHeaders } from 'opentele2'
const headers = await generateHeaders(
{
browsers: [{ name: 'firefox', minVersion: 120 }],
os: 'linux',
locales: ['de-DE', 'en-US'],
requestDependentHeaders: { Referer: 'https://web.telegram.org/' },
},
7,
)
console.log(headers['Accept-Language']) // de-DE;q=1.0, en-US;q=0.9use opentele2_browser::{Browser, HeaderOptions};
fn main() -> opentele2_browser::Result<()> {
let mut firefox = Browser::new("firefox");
firefox.min_version = Some(120);
let options = HeaderOptions {
browsers: vec![firefox],
os: vec!["linux".to_owned()],
locales: vec!["de-DE".to_owned(), "en-US".to_owned()],
..HeaderOptions::default()
};
let headers = opentele2_browser::generate_headers(Some("7"), &options)?;
println!("{:?}", headers.get("Accept-Language"));
Ok(())
}Overrides such as the user agent and request-dependent headers go to
HeaderGenerator::new(options)?.generate(&mut Rng::from_seed(seed), &overrides).
Generate a full fingerprint with screen constraints
A fingerprint is drawn for a set of headers and carries them. Screen bounds are inclusive, in CSS
pixels; without strict, a bound nothing meets is dropped instead of failing.
from opentele2.browser import FingerprintGenerator, Screen
generator = FingerprintGenerator(screen=Screen(min_width=1280, max_width=1920), os="windows")
fingerprint = generator.generate(seed=42)
print(fingerprint.screen.width, fingerprint.screen.height) # 1366 768
print(fingerprint.navigator.platform, fingerprint.navigator.language) # Win32 en-US
print(fingerprint.headers["User-Agent"] == fingerprint.navigator.userAgent) # TrueFingerprintGenerator(screen, strict, mock_webrtc, slim, **header_kwargs): the keyword arguments
of HeaderGenerator choose the headers. strict=True raises ValueError when no screen fits.
import { generateFingerprint } from 'opentele2'
const fingerprint = await generateFingerprint(
{ screen: { minWidth: 1280, maxWidth: 1920 }, header: { os: 'windows' } },
42,
)
console.log(fingerprint.screen.width, fingerprint.screen.height) // 1366 768
console.log(fingerprint.navigator.platform, fingerprint.navigator.language) // Win32 en-USheader takes the header options; strict: true rejects with OT_INVALID_INPUT when no screen
fits.
use opentele2_browser::{FingerprintOptions, HeaderOptions, Screen};
fn main() -> opentele2_browser::Result<()> {
let options = FingerprintOptions {
screen: Some(Screen { min_width: Some(1280), max_width: Some(1920), ..Screen::default() }),
header: HeaderOptions { os: vec!["windows".to_owned()], ..HeaderOptions::default() },
..FingerprintOptions::default()
};
let fingerprint = opentele2_browser::generate_fingerprint(Some("42"), &options)?;
println!("{} {}", fingerprint.screen.width, fingerprint.screen.height); // 1366 768
Ok(())
}Reproduce it with a seed
The same seed and options give byte-identical output on any platform, in Rust, Python and Node.
A seed is a string or an integer; an integer counts as its decimal text, so 42 and "42" are the
same seed. Without a seed every call draws from the operating system’s entropy.
from opentele2.browser import FingerprintGenerator
generator = FingerprintGenerator()
print(generator.generate(seed=42).dumps() == generator.generate(seed="42").dumps()) # True
print(generator.generate().dumps() == generator.generate().dumps()) # False: no seedimport { generateFingerprint } from 'opentele2'
const a = await generateFingerprint({}, 42)
const b = await generateFingerprint({}, '42')
console.log(JSON.stringify(a) === JSON.stringify(b)) // trueuse opentele2_browser::FingerprintOptions;
fn main() -> opentele2_browser::Result<()> {
let options = FingerprintOptions::default();
let a = opentele2_browser::generate_fingerprint(Some("42"), &options)?;
let b = opentele2_browser::generate_fingerprint(Some("42"), &options)?;
assert_eq!(a.to_json()?, b.to_json()?);
Ok(())
}Hand it to a browser automation tool
Take the values your tool sets (user agent, window and screen size, locale, extra headers) from
the fingerprint, and keep the whole fingerprint as JSON for a script that patches navigator and
screen in the page. mock_webrtc and slim are only carried in the output, as mockWebRTC and
slim, for such a script.
import json
from opentele2.browser import FingerprintGenerator, Screen
fingerprint = FingerprintGenerator(screen=Screen(max_width=1920), os="windows").generate(seed="my-id")
# Plain data: map these keys onto your tool's own launch or context options.
browser_settings = {
"user_agent": fingerprint.navigator.userAgent,
"screen": {"width": fingerprint.screen.width, "height": fingerprint.screen.height},
"locale": fingerprint.navigator.language,
"headers": {k: v for k, v in fingerprint.headers.items() if k.lower() != "user-agent"},
}
print(json.dumps(browser_settings, indent=2))
full = fingerprint.dumps() # browserforge's JSON, for an in-page scriptimport { generateFingerprint } from 'opentele2'
const fingerprint = await generateFingerprint(
{ screen: { maxWidth: 1920 }, header: { os: 'windows' } },
'my-id',
)
// Plain data: map these keys onto your tool's own launch or context options.
const { 'User-Agent': userAgent, ...headers } = fingerprint.headers
const browserSettings = {
userAgent,
screen: { width: fingerprint.screen.width, height: fingerprint.screen.height },
locale: fingerprint.navigator.language,
headers,
}
console.log(browserSettings)
const full = JSON.stringify(fingerprint) // for an in-page scriptuse opentele2_browser::{FingerprintOptions, HeaderOptions, Screen};
fn main() -> opentele2_browser::Result<()> {
let options = FingerprintOptions {
screen: Some(Screen { max_width: Some(1920), ..Screen::default() }),
header: HeaderOptions { os: vec!["windows".to_owned()], ..HeaderOptions::default() },
..FingerprintOptions::default()
};
let fingerprint = opentele2_browser::generate_fingerprint(Some("my-id"), &options)?;
println!("{}", fingerprint.navigator.user_agent);
println!("{}x{}", fingerprint.screen.width, fingerprint.screen.height);
for (name, value) in fingerprint.headers.iter() {
println!("{name}: {value}");
}
let _full = fingerprint.to_json()?; // compact JSON, as browserforge's dumps
Ok(())
}A Web A or Web K session announces a browser user agent as its device. A fingerprint drawn with the same seed is a separate draw, not that browser: when one account uses both, check that the browser you run agrees with the profile it announces. See web devices.
Next
- Browser fingerprints: every option, the data and the licence.
- Live client versions: the Chrome and Firefox majors behind the web devices.
- Reference: Python · Node · Rust.