Crypto Randomness Observability and Browser Fingerprints
Compare Web Crypto getRandomValues with crypto-js random generation using a reproducible fixture and privacy-bounded fingerprint observations.
BotBrowser Team
Want the structured docs for Fingerprint?
This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.
BotBrowser can replay an authorized, same-origin fixture that records API availability and bounded output metadata, but it cannot certify entropy quality, identify a person, or make a random value a stable browser fingerprint. The useful comparison is between the browser-owned crypto.getRandomValues() contract and the public crypto-js source path that supplies random bytes to a library-level generator.
TL;DR
crypto.getRandomValues()is a browser API for filling a typed array with random values; it does not expose a reusable browser identity.crypto-jsis application code. Its random-number path depends on the version, build, runtime globals, and the source branch actually loaded.- Compare capability, source version, array shape, and coarse output metadata. Never publish random values, seeds, account data, or raw entropy samples.
- A repeated fixture can reveal a changed implementation or a broken fallback, but one output cannot prove entropy quality or fingerprint uniqueness.
Contents
- Separate the two random sources
- Use a reproducible fixture
- Reviewing a browser release
- Read observations without overclaiming
- Keep fingerprints and randomness distinct
- Practical conclusion
Separate the two random sources
The Web Cryptography specification defines Crypto.getRandomValues() as a browser-mediated operation on an integer typed array. MDN's reference documents the supported array types, the synchronous return value, and the quota boundary. The browser owns the underlying source and may reject an invalid view; page code does not select a seed.
crypto-js follows a different ownership model. Its public source tree contains a random-word helper used by library algorithms, with runtime feature checks and a fallback path that have changed across releases. Read the exact tagged source that your bundle contains, then record whether it selected a browser crypto object, another runtime provider, or a fallback. A package name alone is not evidence about the branch.
The comparison is therefore about observable contracts, not about reproducing internal entropy. A browser fixture can report that crypto.getRandomValues exists, that a typed array was filled, and that a loaded crypto-js build produced a value of the expected shape. It cannot turn either result into a stable device identifier. For related signal checks, see Browser fingerprint verification and Browser API integrity across realms.
Keep the public source links beside the fixture: crypto-js random source, crypto-js releases, and BotBrowser automation consistency. The matching docs page covers consistency checks, but not this source-level comparison or its redaction rules.
Use a reproducible fixture
Run the same HTTPS page in a fresh declared profile. The fixture below records only branch names, lengths, and a short digest of the generated bytes; it never stores the random value itself.
async function observeRandomness() {
const record = {
fixtureId: 'crypto-randomness-01',
browserCrypto: typeof globalThis.crypto?.getRandomValues === 'function',
cryptoJsLoaded: typeof globalThis.CryptoJS !== 'undefined',
browserLength: null,
libraryLength: null,
digestPrefix: null,
outcome: 'pending',
};
try {
if (record.browserCrypto) {
const bytes = new Uint8Array(32);
globalThis.crypto.getRandomValues(bytes);
record.browserLength = bytes.byteLength;
record.digestPrefix = Array.from(bytes.slice(0, 4), value => value.toString(16).padStart(2, '0')).join('');
}
if (record.cryptoJsLoaded && typeof CryptoJS.lib.WordArray.random === 'function') {
const words = CryptoJS.lib.WordArray.random(16);
record.libraryLength = words.sigBytes;
}
record.outcome = record.browserCrypto || record.cryptoJsLoaded ? 'observed' : 'unsupported';
} catch (error) {
record.outcome = error.name === 'QuotaExceededError' ? 'quota-error' : 'api-error';
}
return record;
}
The fixture is reproducible because its inputs are fixed: fixtureId, a 32-byte browser array, a 16-byte library request, and the same loaded package version. The output is intentionally not deterministic. A useful JSON handoff looks like this:
{
"fixtureId": "crypto-randomness-01",
"browserCrypto": true,
"cryptoJsLoaded": true,
"browserLength": 32,
"libraryLength": 16,
"digestPrefix": "redacted",
"outcome": "observed"
}
Repeat the fixture after changing one variable: browser build, profile, package version, bundler, or runtime. Keep browserVersion, os, profile, cryptoJsVersion, and testedOn in the private run record. Do not compare the random bytes themselves; compare the first changed capability or shape field.
Read observations without overclaiming
Keep each Web Crypto request within the byte quota
The Web Crypto specification requires getRandomValues() to throw QuotaExceededError when the typed array's byteLength is greater than 65,536. Check bytes, not the number of elements: a Uint32Array(16_384) is exactly 65,536 bytes, while one more element exceeds the limit. This helper accepts only Uint8Array, so each subarray's element count is also its byte length; convert wider-element views through their underlying byte buffer before using it:
function fillRandomBytes(output) {
if (!(output instanceof Uint8Array)) {
throw new TypeError('output must be a Uint8Array');
}
const maxBytesPerCall = 65_536;
for (let offset = 0; offset < output.byteLength; offset += maxBytesPerCall) {
const length = Math.min(maxBytesPerCall, output.byteLength - offset);
globalThis.crypto.getRandomValues(output.subarray(offset, offset + length));
}
return output;
}
This handles the API's per-call size boundary; it does not make a larger buffer deterministic, validate entropy quality, or make the random bytes appropriate to publish. Keep the failure classification from the fixture: an oversized request is a request-shape error, not evidence of a weak source or a browser identity.
| Observation | What it supports | What it does not prove |
|---|---|---|
browserCrypto: true | the page can call the browser API | entropy quality, hardware source, or user identity |
libraryLength: 16 | the loaded library returned the requested shape | which provider branch supplied the bytes |
quota-error | the browser rejected the requested typed-array size | a broken profile or weak randomness |
changed cryptoJsVersion branch | a source or dependency change worth reviewing | a browser fingerprint or security verdict |
If the browser field changes from true to false, inspect context and page isolation before blaming a profile. If only the library field changes, inspect the resolved package and bundled source. If a digest prefix repeats, treat that as a redacted fixture artifact requiring a rerun, not as proof that the browser has a stable identity. The Web Crypto recommendation and the crypto-js source answer different questions and should remain separate citations.
Keep fingerprints and randomness distinct
A fingerprint is a collection of relatively stable or policy-controlled observations such as user-agent data, screen metrics, or feature availability. Random output is deliberately variable. Joining random samples to a profile can create an application identifier, but that identifier is created by the application data model, not returned by getRandomValues().
For privacy-minimized telemetry, retain the fixture id, API branch, requested lengths, package version, coarse outcome, and timestamp. Drop random values, seeds, full digests, account identifiers, and cross-site joins. BotBrowser can replay the authorized fixture and compare those bounded fields; it cannot certify a third-party entropy test, infer a person from a sample, or guarantee that a library fallback is suitable for cryptographic use.
Next reading
Compare this API boundary with deterministic browser behavior when a test must repeat, and with privacy-preserving browser validation when a diagnostic record must stay small. The first link covers controlled variation; the second covers evidence minimization. Neither turns random output into a person identifier.
Practical conclusion
Compare browser-owned getRandomValues() with the exact public crypto-js source that your bundle loads. Use a fixed fixture, preserve only capability and shape metadata, and change one variable per rerun. The result can explain a source or runtime change; it cannot turn randomness into a fingerprint or prove entropy quality.
Treat the fixture as a release artifact with an owner and a retention rule. The goal is to answer whether a browser build or dependency changed an observable branch, not to collect a large sample. Declare the browser build, operating system, profile lifecycle, package version, bundler, and test date in the private run header. A fresh profile and a persistent profile can differ in permissions, injected globals, and cached application code, so compare those headers before changing the fixture.
Keep browser and library fields separate. browserCrypto answers whether the page can call the browser method, while cryptoJsLoaded answers whether the global library was present. The length fields answer shape questions; they do not identify a provider, measure entropy, or show that two values are independent. Use the exact dependency production loads and record its resolved version or source commit privately.
Review source branches rather than returned bytes. Look for the browser crypto branch, another runtime provider, and fallback branch in the exact tagged source. A fallback may be acceptable for a convenience token yet unsuitable for a key, nonce, session secret, or reset value. The application security owner must make that decision from library documentation and review.
Run one controlled change at a time. Repeat the same page, build, profile, and package first; then change only the browser build, dependency version, or bundler output. A capability change calls for context, secure-origin, realm, and isolation checks. A library-only change calls for a resolved-package and initialization-order check. This ordering gives support a short causal chain.
The coarse outcomes are intentional. observed means a requested path was available and reached the recording branch. unsupported means neither path was available. quota-error means the browser rejected a requested typed-array size. api-error means another exception occurred. None of these names certifies entropy, uniqueness, or cryptographic suitability.
Do not use a digest prefix as a fingerprint. A short prefix can become identifying when joined with a profile, timestamp, origin, and account, so the public handoff marks it redacted. The same applies to seeds: a deterministic test input is not evidence about production entropy. If a harness needs deterministic behavior, inject a test provider outside the production path and label the run as a test double.
For support handoffs, retain the first changed field and next owner. Browser capability belongs with the browser or context owner; a package branch belongs with the dependency owner; a quota error belongs with the request-size review. An authorized BotBrowser run can capture the bounded record and compare a release candidate with a baseline, but it cannot certify a third-party entropy test, infer a user from a sample, or guarantee a fallback for cryptographic material.
A browser API contract is not a library implementation, a token policy, or a fingerprint. Keeping those layers separate prevents a changed result from becoming an unsupported identity or security claim. If no browser run was performed, say so; the evidence JSON for this draft records a source review and a fixture ready for a future authorized run, not measured entropy.
Start with the fixture header, not with the random output. Confirm the page origin, browser build, profile label, package version, and realm. A missing secure context, an iframe policy, a worker-only global, or a stale bundle can change the availability field while the browser itself is unchanged. The correct next action is to restore the declared context and rerun the same fixture.
If browserCrypto is false, check whether the page is running in the intended window or worker and whether the test loaded the same origin as the reference page. Check the property at call time rather than relying on a copied capability flag. Do not silently substitute Math.random() in a path that expects cryptographic material. A convenience fallback should have a separate name and an explicit security owner.
If cryptoJsLoaded is false, inspect script order, module format, content security policy, and the resolved bundle. A package can be installed while the global CryptoJS object is absent because the build uses modules or tree-shaking. That is a loading observation, not evidence that the library's random path is broken. Record the loaded module entry point and rerun with the same artifact that production serves.
If libraryLength is null while the library is present, check the method path and the versioned source. The public API can be wrapped, renamed, or removed by a bundler. The fixture should report api-error for an exception and preserve the original fields; it should not catch everything and label the result unsupported. Those outcomes route to different owners.
If the browser reports quota-error, keep the requested size in the private record and lower it only when the product requirement allows that change. A smaller request may make the fixture pass while hiding an application assumption about batch size. The public article uses 32 bytes to exercise a bounded, ordinary request; it does not claim that every size is accepted in every context.
If a release changes only `digestPrefix`, rerun once with the same header before opening a regression. Random output is expected to vary. If the redacted field is needed for a security investigation, move that evidence to a restricted system with a short retention period. Never paste random values into issue comments, analytics records, or customer-facing logs.
The smallest useful matrix has a baseline row and one row for each independent change. Keep the page and fixture constant. Change the browser build in row B, the profile lifecycle in row C, the crypto-js version in row D, and the bundler output in row E. The result field should say which first observable field changed. This is enough to route a review without implying a statistical conclusion.
| Row | One change | Primary fields | Review owner |
|---|---|---|---|
| A | baseline | all fixture fields | test owner |
| B | browser build | browserCrypto, browserLength, outcome | browser/context owner |
| C | profile lifecycle | capability and outcome fields | profile owner |
| D | crypto-js version | cryptoJsLoaded, libraryLength, outcome | dependency owner |
| E | bundler output | loading and method path | build owner |
Do not add a row for every random sample. Additional samples answer a statistical question that this article deliberately does not claim to answer. If an entropy assessment is required, use a reviewed cryptographic test plan with controlled inputs, private outputs, and an owner qualified to interpret the result. The browser fixture remains a compatibility and observability check.
Pin the source URL to the version that production uses. The development branch of an upstream repository can move after the article is written, so pair the public link with a release tag or commit in the private evidence record. When a release changes the random helper, update the source citation, rerun the fixture, and write the first changed field in the release note. Do not infer behavior from a minified function name alone.
Keep the browser specification and library source as different evidence classes. The specification describes the browser contract and exception boundary. The source describes one implementation's provider selection and fallback. Neither source says that a random sample is a fingerprint. A product may create a per-session token from randomness, but that is an application identifier with its own consent, retention, and access policy.
The same separation helps privacy review. A capability field can often be retained for a release comparison. A random value usually cannot be retained without a purpose and access control. An account identifier should not be joined to either field for a public diagnostic. A profile label can be useful when it is synthetic and non-customer. These distinctions belong in the data schema before the fixture is run.
For a support ticket, ask for the run header, first changed field, and outcome category. Do not ask for a random value, seed, full digest, cookie, or account identifier. A ticket that has only api-error and no browser build is incomplete; a ticket that includes raw entropy is over-collected. The bounded JSON record gives support enough context to reproduce the branch while preserving privacy.
Feature availability can be one input to a fingerprint, but it is rarely sufficient by itself. The same browser build can expose or hide a method because of origin, permissions, policy, worker type, or a managed environment. Conversely, two profiles can expose the same method while differing in unrelated surfaces. A randomness observation therefore should not be added to a fingerprint score without an explicit product and privacy review.
Randomness also has no promise of persistence. A fresh value can differ on every call, across tabs, or across browser launches. A repeated digest prefix can be an artifact of redaction, a fixture bug, or a logging mistake. It is not a reason to claim that a profile is stable. If the product needs continuity, issue a separate application token with rotation and revocation rules, and label that token as application state.
Finally, browser consistency is not entropy certification. BotBrowser can keep an authorized profile and context stable enough to compare the same fixture across releases. It cannot inspect a proprietary operating-system source, prove a hardware random number generator, or certify a provider's security claim. The article's value is the boundary: it tells a release reviewer what was observed, what changed, and what remains unmeasured.
For related operational patterns, compare the evidence style in Browser test evidence and reporting quality and the profile scope in Headless and headed browser profile consistency. Those articles do not replace the source comparison here; they provide adjacent reporting and profile controls.
Reviewing a browser release
For a release review, compare the same fixture revision, browser context, and coarse outcome states. A changed random value is expected; a changed availability or fallback branch is the actionable regression.
Public sources
This article is part of the guide: What Is a Browser Fingerprint?
Related Articles
Client Hints Fingerprinting: HTTP Headers as Identity
FingerprintDeterministic Mode: Reproducible Browser Fingerprints
FingerprintWhat Is a Browser Fingerprint?
FingerprintCanvas Fingerprinting: How It Works and What Helps
FingerprintBattery Status API: Privacy, Availability, and Safer Web Decisions
FingerprintNoise Seed Reproducibility for Repeatable Fingerprint Runs
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.