Fix cryptographic random bias in getUnbiasedRandomInRange

Reworked getUnbiasedRandomInRange() to eliminate modulo bias by using
rejection sampling combined with bucket indexing instead of the `%` operator.
Added getUnbiasedRandomFloat() to safely generate unbiased floats for
sizeVariation and sizeMultiplier.
This commit is contained in:
lockbitchat
2025-10-20 00:34:17 -04:00
parent 4233ba3d7e
commit 60e4bb6b8a
3 changed files with 44 additions and 24 deletions
+19 -9
View File
@@ -8193,13 +8193,17 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
this._secureLog("error", "\u274C Failed to initialize enhanced security", { errorType: error.constructor.name }); this._secureLog("error", "\u274C Failed to initialize enhanced security", { errorType: error.constructor.name });
} }
} }
// Helper function to generate unbiased random values in a range // Helper function: unbiased integer in [min, max]
getUnbiasedRandomInRange(min, max) { getUnbiasedRandomInRange(min, max) {
if (!Number.isInteger(min) || !Number.isInteger(max)) {
throw new Error("getUnbiasedRandomInRange requires integer min and max");
}
const range = max - min + 1; const range = max - min + 1;
if (range <= 0) throw new Error("Invalid range"); if (range <= 0) throw new Error("Invalid range");
const bytesNeeded = Math.ceil(Math.log2(range) / 8); const bytesNeeded = Math.max(1, Math.ceil(Math.log2(range) / 8));
const maxValue = Math.pow(256, bytesNeeded); const maxValue = Math.pow(256, bytesNeeded);
const threshold = maxValue - maxValue % range; const threshold = maxValue - maxValue % range;
const bucketSize = threshold / range;
let randomValue; let randomValue;
do { do {
const randomBytes = crypto.getRandomValues(new Uint8Array(bytesNeeded)); const randomBytes = crypto.getRandomValues(new Uint8Array(bytesNeeded));
@@ -8208,16 +8212,22 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
randomValue = randomValue << 8 | randomBytes[i]; randomValue = randomValue << 8 | randomBytes[i];
} }
} while (randomValue >= threshold); } while (randomValue >= threshold);
return randomValue % range + min; return min + Math.floor(randomValue / bucketSize);
} }
// Generate fingerprint mask for anti-fingerprinting with enhanced randomization // Helper: unbiased float in [minFloat, maxFloat] with optional precision
getUnbiasedRandomFloat(minFloat, maxFloat, scale = 100) {
const minInt = Math.round(minFloat * scale);
const maxInt = Math.round(maxFloat * scale);
const intVal = this.getUnbiasedRandomInRange(minInt, maxInt);
return intVal / scale;
}
// Generate fingerprint mask
generateFingerprintMask() { generateFingerprintMask() {
const cryptoRandom = crypto.getRandomValues(new Uint8Array(128)); const cryptoRandom = crypto.getRandomValues(new Uint8Array(128));
const mask = { const mask = {
timingOffset: this.getUnbiasedRandomInRange(0, 1500), timingOffset: this.getUnbiasedRandomInRange(0, 1500),
// 01500ms sizeVariation: this.getUnbiasedRandomFloat(0.75, 1.25),
sizeVariation: this.getUnbiasedRandomInRange(75, 125) / 100, // float, unbiased
// 0.751.25
noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(64))), noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(64))),
headerVariations: [ headerVariations: [
"X-Client-Version", "X-Client-Version",
@@ -8233,8 +8243,8 @@ var EnhancedSecureWebRTCManager = class _EnhancedSecureWebRTCManager {
"X-Private" "X-Private"
], ],
noiseIntensity: this.getUnbiasedRandomInRange(50, 150), noiseIntensity: this.getUnbiasedRandomInRange(50, 150),
// 50150% sizeMultiplier: this.getUnbiasedRandomFloat(0.75, 1.25),
sizeMultiplier: this.getUnbiasedRandomInRange(75, 125) / 100, // float, unbiased
timingVariation: this.getUnbiasedRandomInRange(100, 1100) timingVariation: this.getUnbiasedRandomInRange(100, 1100)
}; };
return mask; return mask;
+2 -2
View File
File diff suppressed because one or more lines are too long
+22 -12
View File
@@ -4481,15 +4481,18 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
} }
} }
// Helper function to generate unbiased random values in a range // Helper function: unbiased integer in [min, max]
getUnbiasedRandomInRange(min, max) { getUnbiasedRandomInRange(min, max) {
const range = max - min + 1; if (!Number.isInteger(min) || !Number.isInteger(max)) {
throw new Error('getUnbiasedRandomInRange requires integer min and max');
}
const range = max - min + 1;
if (range <= 0) throw new Error('Invalid range'); if (range <= 0) throw new Error('Invalid range');
// Use rejection sampling to avoid modulo bias const bytesNeeded = Math.max(1, Math.ceil(Math.log2(range) / 8));
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
const maxValue = Math.pow(256, bytesNeeded); const maxValue = Math.pow(256, bytesNeeded);
const threshold = maxValue - (maxValue % range); const threshold = maxValue - (maxValue % range);
const bucketSize = threshold / range; // exact integer
let randomValue; let randomValue;
do { do {
@@ -4498,32 +4501,39 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
for (let i = 0; i < bytesNeeded; i++) { for (let i = 0; i < bytesNeeded; i++) {
randomValue = (randomValue << 8) | randomBytes[i]; randomValue = (randomValue << 8) | randomBytes[i];
} }
} while (randomValue >= threshold); // discard biased values } while (randomValue >= threshold);
return (randomValue % range) + min; // Use bucket index instead of modulo
return min + Math.floor(randomValue / bucketSize);
} }
// Helper: unbiased float in [minFloat, maxFloat] with optional precision
getUnbiasedRandomFloat(minFloat, maxFloat, scale = 100) {
const minInt = Math.round(minFloat * scale);
const maxInt = Math.round(maxFloat * scale);
const intVal = this.getUnbiasedRandomInRange(minInt, maxInt);
return intVal / scale;
}
// Generate fingerprint mask for anti-fingerprinting with enhanced randomization // Generate fingerprint mask
generateFingerprintMask() { generateFingerprintMask() {
const cryptoRandom = crypto.getRandomValues(new Uint8Array(128)); const cryptoRandom = crypto.getRandomValues(new Uint8Array(128));
const mask = { const mask = {
timingOffset: this.getUnbiasedRandomInRange(0, 1500), // 01500ms timingOffset: this.getUnbiasedRandomInRange(0, 1500),
sizeVariation: this.getUnbiasedRandomInRange(75, 125) / 100, // 0.751.25 sizeVariation: this.getUnbiasedRandomFloat(0.75, 1.25), // float, unbiased
noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(64))), noisePattern: Array.from(crypto.getRandomValues(new Uint8Array(64))),
headerVariations: [ headerVariations: [
'X-Client-Version', 'X-Session-ID', 'X-Request-ID', 'X-Timestamp', 'X-Signature', 'X-Client-Version', 'X-Session-ID', 'X-Request-ID', 'X-Timestamp', 'X-Signature',
'X-Secure', 'X-Encrypted', 'X-Protected', 'X-Safe', 'X-Anonymous', 'X-Private' 'X-Secure', 'X-Encrypted', 'X-Protected', 'X-Safe', 'X-Anonymous', 'X-Private'
], ],
noiseIntensity: this.getUnbiasedRandomInRange(50, 150), // 50150% noiseIntensity: this.getUnbiasedRandomInRange(50, 150),
sizeMultiplier: this.getUnbiasedRandomInRange(75, 125) / 100, sizeMultiplier: this.getUnbiasedRandomFloat(0.75, 1.25), // float, unbiased
timingVariation: this.getUnbiasedRandomInRange(100, 1100) timingVariation: this.getUnbiasedRandomInRange(100, 1100)
}; };
return mask; return mask;
} }
// Security configuration - all features enabled by default // Security configuration - all features enabled by default
configureSecurityForSession() { configureSecurityForSession() {
this._secureLog('info', '🔧 Configuring security - all features enabled by default'); this._secureLog('info', '🔧 Configuring security - all features enabled by default');