Open Thorin-Oakenpants opened 3 years ago
for (let i=0; i < getTest.length; i++) {
let x = getTest[i]
sum2 += x
sum3 += Math.abs(x)
if (i > 4499 && i < 5000) {sum += Math.abs(x)}
}
console.log("sum: "+ sum +"\nsum2: "+ sum2 +"\nsum3: "+ sum3)
my machine
REAL
====
chromium
sum: 124.04347527516074
sum2: -0.11990348991866995
sum3: 10748.96479334074
firefox
sum: 35.7383295930922
sum2: -0.019596409052610397
sum3: 3188.503997568041
LIES
====
FF+canvas blocker
sum: 35.738326121121645
sum2: -0.019551539269741625
sum3: 3188.504004060698
FF+chameleon
sum: 35.73832958500134
sum2: -0.01932726310678845
sum3: 3188.5039971747838
FF+cydec: sum2+3 match
sum: 1035.8892937898636
sum2: 88414.9803841114
sum3: 88414.9803841114
chrome+cydec: sum2+3 match
sum: 998.6193348169327
sum2: 88129.88009166718
sum3: 88129.88009166718
brave lies
1: strict: sum2+3 match
sum: 24.038141147364513
sum2: 2224.6813276491157
sum3: 2224.6813276491157
2: standard
sum: 123.75700060309464
sum2: -0.1196279372202298
sum3: 10724.140386573516
note to self:
currently leveraging
the current test needs a massive upgrade
polyfill
@abrahamjuliot add here as you get results
blink: also see https://fingerprintjs.com/blog/audio-fingerprinting/
124.04344884395687 : chromeOS 84
124.04347527516074 : windows + android10
124.04347721464 : chromeOS 89
124.04347657808103 : macOS BigSur 11.3 + ungoogled chromium
124.0434806260746 : macOS 11
124.08074500028306 : android 7, 9
124.08075528279005 : android 10+11
firefox hmmm that's interesting that the channel hash changes
windows 7, 64bit browser (nightly 91)
ba40f4d0c7290cd5143ac224e7f0ef363eee1e31e10e2a5bf7424ec4f1825a3b - channels
35.7383295930922
c33ad9d6e032d6998313599cab9a861401814f5740d61b2601a92010f5865018 - oscillatorNode RFP=OFF
0bbc175d5d5d7c24106a8a672a89df54da406afaf96f6baa2321885d02af078b - OscillatorNode/DynamicsCompressor RFP=OFF
7fbaffe2fb30e675a99e7c0019f873fdf3acfbcb585c3d588612dcbd3da2b7ff - oscillatorNode RFP=ON
c75f5fb5369fd65b44334164fcd8e2369f75bfacd905607e2eb519f38b9a119a - OscillatorNode/DynamicsCompressor RFP=ON
macOS BigSur 11.3 (nightly 91)
eda9581d019a3389ed8eb0b77a7c74ce2505a3fec0e2f6484687e6e80495b56d
35.7383295930922
c33ad9d6e032d6998313599cab9a861401814f5740d61b2601a92010f5865018 - oscillatorNode RFP=OFF
bf2eaae195d501f43acb9dcd384f5099cb75533a0719cec7eca1aa16dc3981dd - OscillatorNode/DynamicsCompressor RFP=ON
https://github.com/arkenfox/user.js/issues/1216#issuecomment-883590247
Android RFP ON
- 639354199cf12c28fb7f14cabdb77872a040bd93
- 35.7499681673944
- 63848d42fb8da033b1bd6861a5198ed01f4b214b [20 keys]
- 9c39676511544e169d0b40366dd8f7bf167eb437
- 7072df3640caea7df394a560e30aada4b416ca95
Android RFP OFF
- 639354199cf12c28fb7f14cabdb77872a040bd93
- 35.7499681673944
- d908755de877d8ebd96651bc5286bcf85fbd42d3 [20 keys]
- 9c39676511544e169d0b40366dd8f7bf167eb437
- 7072df3640caea7df394a560e30aada4b416ca95
Ubuntu RFP OFF
- 4460bdcbe1cb161b6f9bd804565c8ecbaa2c4da6
- 35.73833402246237
- 506774fdd1f5cf63c96db512f3efe5f335d656b2 [20 keys]
- 247e9af1788fd5dca82f2139619cc7bd7e3be6ea
- d7db6f9e9102bc7989a56dc42ab48cd9a1be60b2
Ubuntu RFP ON
- 4460bdcbe1cb161b6f9bd804565c8ecbaa2c4da6
- 35.73833402246237
- fdb6f2cb10e8f42e073ef8b99345d739986ec390 [20 keys]
- 247e9af1788fd5dca82f2139619cc7bd7e3be6ea
- d7db6f9e9102bc7989a56dc42ab48cd9a1be60b2
FYI: current min/max
if (isFF) {
min = 35.738329
max = 35.78334
}
reminder to self: split up the output better, so RFP protected keys are split from the rest: when I rebuild the audio section
Here's a back door to get audio entropy using DynamicsCompressorNode.reduction. I tested in FF and Chrome on Windows, Android, and ChromeOS. It's also unprotected in Brave.
By default, it's 0
, but it's rendered in the oncomplete
event (line 258)
context.oncomplete = function(event) {
const compressorGainReduction = pxi_compressor.reduction
//...
I have not examined this yet, but the result might mirror the entropy in the sum fingerprint. So, if the reduction is a known value, we can infer the sum fingerprint.
FF on Windows:
Some observations in Brave...
Here's a quick function to detect Brave's standard farbling (based on the FPJS fudge factor detection):
const getNoiseFactor = () => {
const buffer = new AudioBuffer({
length: 1,
sampleRate: 44100
})
buffer.getChannelData(0)[0] = 1
return buffer.getChannelData(0)[0]
}
const noiseFactor = getNoiseFactor()
console.log(`Audio Noise Detected: ${noiseFactor != 1 ? noiseFactor : 'none'}`)
Brave strict mode has a max unique rate, so it too can be detected
const bins = buffer.getChannelData(0)
const numOfUniqueValues = new Set([...bins]).size // Brave strict returns the length of the buffer
nice. I need to finish my revamped domrect first
And then it's onto audio. I already had ideas on how other ways to detect lies, but the more in the arsenal, the better. I'm just not sure how far down this rabbit hole I want to go as I believe entropy is SFA
Will check out your code bits when I get back to this
@sgmenda - I'm not too far away from rebuilding the audio section - maybe a week
I plan on building a stand-alone test first which
There will be a run button, as this allows us to run audio tests behind a user gesture
So something similar to the webgl test you and @tomrittervg set up , but one where we can eliminate the noise. The standalone test would be minus the submission which can be added on tom's site. And then you drill down into the data by OS, by RFP, and even by version (which shouldn't matter but can weed out old releases), and even see the correlation between the math entropy.
FWIW, it seems as if audio math results are a subset of math audio entropy [edited]
If you're interested in chipping in here and collaborating when and if you have time, rather than us all doing our own thing ... I can add you as an admin - just confirm with me via email
PS: the webgl parameters is also not far away: see
FWIW, it seems as if audio results are a subset of math entropy
that's the other way round: math is a subset of audio
OK, am onto it in theory .. getting a stable known result across all platforms, that audio randomizes are triggered into adding noise to should be totally doable with a non-sinusoidal periodic waveform .. e.g. a square wave, perhaps a sawtooth wave, or a flat line ... that said the triangle wave is also non-sinusoidal (but we don't really have any data sets on if this wave contains any entropy within a platform - but note that CB's tests include it, but for testing randomizing, not entropy)
flat wave .. I wonder how hard it would be to generate one at a specific "height" and the sum is shitloads of decimal places, you know like 4/3 = 1.333333333333333333333333333333333...
class discuss
homework for @abrahamjuliot : https://en.wikipedia.org/wiki/Rectangular_function
alrighty .. now we're cooking
Firefox audio FPs taken from appendix C and figure 10
@abrahamjuliot .. you can add that paper to your collection and repo, to save me typing this shit out twice, TIA - edit, you can also add https://www.cse.chalmers.se/~andrei/secweb21.pdf which is interesting
π homework for abraham: calculate Pi to 62.8tn decimal places for max entropy
Hmmm ... Pi is used in fourier transformations AFAIK, so if Pi has entropy in android ... something does in cbrt: 1p1
, which is cbrt(Math.PI)
, so it's either Math.pow
or Math.PI
function cbrt(x) {
let y = Math.pow(Math.abs(x), 1 / 3)
return x < 0 ? -y : y
}
https://github.com/arkenfox/TZP/issues/67#issuecomment-896621330
I now have the code they used (not sure if I can share it yet). At quick glance, it looks like it's has combined the audio tests behind user gestures e.g. OscillatorNode etc .. and I think it has also included the audioContext keys
I also have all the data, including the sums, such as 35.7383295930922
, e.g, running the test matches TZP sum etc, so that's handy, all those known gecko/blink results
@sgmenda - have you built the web package yet: I'm kinda lost on how to: I just want the js
Sorry for the late response.
@sgmenda - have you built the web package yet: I'm kinda lost on how to: I just want the js
I haven't yet had a chance to. I will try to take a look over the weekend and update here if I figure it out.
cool ... https://en.wikipedia.org/wiki/User:LucasVB/Gallery#Waves
really cool ... https://bl.ocks.org/jinroh/7524988 .. with code
@abrahamjuliot
see https://github.com/arkenfox/user.js/issues/1701#issuecomment-1684892567 - basically mozilla have standardized the math library in audio - read https://bugzilla.mozilla.org/show_bug.cgi?id=1358149#c56 in FF118+
but in my ToDo #169 under audio I have
π¦ audio
- [ ] audio: see #67
- expand methods: see https://github.com/arkenfox/TZP/issues/67#issuecomment-896621330
- detect known lie: see https://github.com/arkenfox/TZP/issues/67#issuecomment-882366270
- MOAR: see
DynamicsCompressorNode.reduction
- https://github.com/arkenfox/TZP/issues/67#issuecomment-857305813
now a lot of that can be ignored, now we know audio is RFP protected, so I don't need to do sawtooth, or other tests, but I may do a standalone more comprehensive test: I have the code from those researchers too.
but I was interested in some of creepy's code: for example I do not have these
gain: -31.502185821533203
freq: 167700.7530517578
time: 148.475412953645
trap: 0.3772195888637472
AnalyserNode.context.listener.forwardX.maxValue: undefined
BiquadFilterNode.gain.maxValue: 3.4028234663852886e+38
BiquadFilterNode.frequency.defaultValue: 350
BiquadFilterNode.frequency.maxValue: 22050
DynamicsCompressorNode.attack.defaultValue: 0.003000000026077032
DynamicsCompressorNode.knee.defaultValue: 30
DynamicsCompressorNode.knee.maxValue: 40
DynamicsCompressorNode.ratio.defaultValue: 12
DynamicsCompressorNode.ratio.maxValue: 20
DynamicsCompressorNode.release.defaultValue: 0.25
DynamicsCompressorNode.release.maxValue: 1
DynamicsCompressorNode.threshold.defaultValue: -24
DynamicsCompressorNode.threshold.minValue: -100
OscillatorNode.detune.maxValue: 3.4028234663852886e+38
OscillatorNode.detune.minValue: -3.4028234663852886e+38
OscillatorNode.frequency.defaultValue: 440
OscillatorNode.frequency.maxValue: 22050
OscillatorNode.frequency.minValue: -22050
is there any point in adding more? also what to make of https://github.com/arkenfox/TZP/issues/67#issuecomment-857305813
@karlt FYI
Here's a simplified concept that gets the gain, freq, time. From what I recall, all the default Node values produce no entropy connected with the audio rendering or system configuration, but there might be something there I missed.
function getRenderedAudioData() {
// samples get filled around 200, so we could speed this up within
// 200-5000 bins
const context = new OfflineAudioContext(1, 5000, 44100)
const analyser = context.createAnalyser()
const dynamicsCompressor = context.createDynamicsCompressor()
const oscillator = context.createOscillator()
const biquadFilter = context.createBiquadFilter() // optional if we want the values
oscillator.type = 'triangle'
oscillator.frequency.value = 10000
dynamicsCompressor.threshold.value = -50
dynamicsCompressor.knee.value = 40
dynamicsCompressor.attack.value = 0
analyser.connect(context.destination);
dynamicsCompressor.connect(analyser);
dynamicsCompressor.connect(context.destination);
oscillator.connect(dynamicsCompressor);
oscillator.start(0);
context.startRendering()
return new Promise((resolve) => {
context.oncomplete = (event) => {
const channelData = event.renderedBuffer.getChannelData(0)
const freqData = new Float32Array(analyser.frequencyBinCount)
const timeData = new Float32Array(analyser.fftSize)
analyser.getFloatFrequencyData(freqData)
analyser.getFloatTimeDomainData(timeData)
dynamicsCompressor.disconnect()
oscillator.disconnect()
// reduction gets rendered with the audio
// value is not implemented older WebKit, so we fallback to reduction
const { reduction } = dynamicsCompressor
const gainReduction = Number(reduction.value || reduction) || 0;
// Optionally collect or hash the values below
/*
analyser.channelCount
analyser.channelCountMode
analyser.channelInterpretation
analyser.context.sampleRate
analyser.fftSize
analyser.frequencyBinCount
analyser.maxDecibels
analyser.minDecibels
analyser.numberOfInputs
analyser.numberOfOutputs
analyser.smoothingTimeConstant
analyser.context.listener.forwardX.maxValue
biquadFilter.gain.maxValue
biquadFilter.frequency.defaultValue
biquadFilter.frequency.maxValue
dynamicsCompressor.attack.defaultValue
dynamicsCompressor.knee.defaultValue
dynamicsCompressor.knee.maxValue
dynamicsCompressor.ratio.defaultValue
dynamicsCompressor.ratio.maxValue
dynamicsCompressor.release.defaultValue
dynamicsCompressor.release.maxValue
dynamicsCompressor.threshold.defaultValue
dynamicsCompressor.threshold.minValue
oscillator.detune.maxValue
oscillator.detune.minValue
oscillator.frequency.defaultValue
oscillator.frequency.maxValue
oscillator.frequency.minValue
*/
// Simplify to one sample per field
return resolve([
channelData.slice(-1)[0],
gainReduction,
freqData.slice(-1)[0],
timeData.slice(-1)[0],
]);
};
});
}
getRenderedAudioData().then(data => console.log(data))
The trap concept code is somewhat difficult to parse⦠I forgot what I was thinking. I added some comments to better explain it.
// Returns a random integer between min and max
const getRandFromRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
// Copies data from an audio buffer to a float array and modifies it with randomness
// Returns an array of unique and non-zero values from the modified data
const getCopyFrom = (rand, buffer, copy) => {
const { length } = buffer // get the length of the buffer
const max = 20; // number of bins to modify
const start = getRandFromRange(275, length - (max + 1)); // audio usually begins after 275
const mid = start + max / 2; // the middle bin
const end = start + max; // the last bin
// Set data to randomness at the start, middle and end bins
buffer.getChannelData(0)[start] = rand
buffer.getChannelData(0)[mid] = rand
buffer.getChannelData(0)[end] = rand
buffer.copyFromChannel(copy, 0) // copy the data from the buffer to the array
// Modify the data with randomness if it is zero at the start, middle and end bins
const attack = [
buffer.getChannelData(0)[start] === 0 ? Math.random() : 0,
buffer.getChannelData(0)[mid] === 0 ? Math.random() : 0,
buffer.getChannelData(0)[end] === 0 ? Math.random() : 0,
]
// Read back unique data and filter non-zero values
return [...new Set([...buffer.getChannelData(0), ...copy, ...attack])].filter((x) => x !== 0)
}
// Copies data from a float array to an audio buffer and modifies it with randomness
// Returns an array of values from the modified data that are different from the frequency value
const getCopyTo = (rand, buffer, copy) => {
buffer.copyToChannel(copy.map(() => rand), 0) // copy the data from the array to the buffer with randomness
const frequency = buffer.getChannelData(0)[0] // get the frequency value from the first bin
const dataAttacked = [...buffer.getChannelData(0)] // get the modified data as an array
.map((x) => x !== frequency || !x ? Math.random() : x) // modify the data with randomness if it is different from the frequency or zero
return dataAttacked.filter((x) => x !== frequency) // filter out the frequency values
}
// Generates a noise factor based on two audio buffers and two float arrays
// Returns a number that represents the noise factor or zero if there is an error
const getNoiseFactor = () => {
const length = 2000 // length of the audio buffers and arrays
const rand = Math.random() // use to modify with noise
try {
const result = [...new Set([ // get unique values from both functions
...getCopyFrom(
rand,
new AudioBuffer({ length, sampleRate: 44100 }), // create an empty audio buffer with length and sample rate
new Float32Array(length), // create an empty float array with length
),
...getCopyTo(
rand,
new AudioBuffer({ length, sampleRate: 44100 }), // create another empty audio buffer with length and sample rate
new Float32Array(length), // create another empty float array with length
),
])]
return Number(
result.length !== 1 && // check if there is more than one value in the result array
result.reduce((acc, n) => acc += +n, 0) // sum up all the values in the result array
)
} catch (error) {
console.error(error)
return 0
}
}
const noiseFactor = getNoiseFactor()
console.log(noiseFactor)
is there any point in adding more? also what to make of https://github.com/arkenfox/TZP/issues/67#issuecomment-857305813
It can be useful for sanity checks, but it seems there's not much to classify if the values are genuine.
Thanks π this is now on my todo, to at least explore if they're 1. stable and 2. worth it and 3. where to add, under auto or click
by "worth it" for example, I see duplication? equivalency?
BiquadFilterNode.gain.maxValue: 3.4028234663852886e+38
OscillatorNode.detune.maxValue: 3.4028234663852886e+38
OscillatorNode.detune.minValue: -3.4028234663852886e+38
BiquadFilterNode.frequency.maxValue: 22050 // 44100 (which the test sets) / channel count ?
OscillatorNode.frequency.maxValue: 22050
OscillatorNode.frequency.minValue: -22050
some of the others look suspiciously common or related to context keys
remembering that TZP is gecko only, this is a nice to have to finish the section off for RFP FF118+ (don't care about older releases, except ESR but I'm hoping it might get backported upstream for TB, we haven't asked yet). But at this stage, I'm inclined to trust the moz engineers have hooked up all the math and we're not going to get any extra entropy. Twould be nice to close an issue for once :~p
function getRenderedAudioData()
so this is very similar to the existing onlineAudioContext test that gets the sum and channel hashes, so rather than run a separate test I can probably just piggy back on that one?
anyway, using your standalone test (on file:// scheme) I do not get the same results as creepy
context.oncomplete = (event) => {
// snip
let oData = {
"freq": freqData.slice(-1)[0],
"gain": gainReduction,
"time": timeData.slice(-1)[0],
"wtf_is_this": channelData.slice(-1)[0],
}
console.log(performance.now() - t0, oData)
return resolve()
};
// what I get
{
'freq': -190.46719360351562
β 'gain': -31.502185821533203
β 'time': -0.06294857710599899
β 'wtf_is_this': -0.21359434723854065
}
// what creepy reports (no RFP or anything)
{
'freq': 167700.7530517578
'gain': -31.502185821533203
'time': 148.475412953645
'trap': 0.7879753706889067
}
so gain
is the same but everything else is different, and I didn't hook up any trap code
gain seems to be directly related to dynamicsCompressor.reduction
I can probably just piggy back on that one
nope, because we need the analyser
This should align the results, but I might be missing something...
function slice(arr, start, end) {
const collection = []
for (let i = start; i < end; i++) {
collection.push(arr[i])
}
return collection
}
function toSum(arr) {
return !arr ? 0 : [...arr].reduce((acc, curr) => (acc += Math.abs(curr)), 0)
}
let oData = {
"freq": toSum(freqData)
"gain": gainReduction,
"time": toSum(timeData),
"wtf_is_this": toSum(slice([...channelData], 4500, bufferLen /* 5000 */)), // fpjs sum
}
@kkapsner : just FYI on the CB inconsistencies: not that I think it matters, as anyone wanting to NOT protect one of get vs copy is a bit silly and not likely in the slightest, but it does make testing it a bit weird, or otherwise I'm missing something
just some notes
have leveraged
isBraveMode
locally, but have found prototype lies to be unreliable. This can be said for any of prototype lies, while it may detect the extension is willing to use them, it doesn't mean it doesI would rather go down the road of double executions, mathematical proofs and equivalency
prototype lie results: trying to work out which lies MUST be present for a lie on the automatic audio test