Skip to content

Instantly share code, notes, and snippets.

@atoponce
Last active January 28, 2021 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atoponce/d7e8f7b378f80875f6c61120737de1b0 to your computer and use it in GitHub Desktop.
Save atoponce/d7e8f7b378f80875f6c61120737de1b0 to your computer and use it in GitHub Desktop.
JavaScript entropy proof-of-concept
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<title>JavaScript Entropy Proof-of-Concept</title>
<script language='javascript'>
function draw_disco(s) {
const canvas = document.getElementById('canvas')
const context = canvas.getContext('2d')
const radius = 30
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
const centerX = 60 * j + 30
const centerY = 60 * i + 30
if (s[ i * 8 + j] === '0') context.fillStyle = '#801515'
if (s[ i * 8 + j] === '1') context.fillStyle = '#D4916A'
if (s[ i * 8 + j] === '2') context.fillStyle = '#805215'
if (s[ i * 8 + j] === '3') context.fillStyle = '#D4B56A'
if (s[ i * 8 + j] === '4') context.fillStyle = '#806D15'
if (s[ i * 8 + j] === '5') context.fillStyle = '#D4CF6A'
if (s[ i * 8 + j] === '6') context.fillStyle = '#697B15'
if (s[ i * 8 + j] === '7') context.fillStyle = '#9AC361'
if (s[ i * 8 + j] === '8') context.fillStyle = '#116611'
if (s[ i * 8 + j] === '9') context.fillStyle = '#458A79'
if (s[ i * 8 + j] === 'a') context.fillStyle = '#123652'
if (s[ i * 8 + j] === 'b') context.fillStyle = '#515E91'
if (s[ i * 8 + j] === 'c') context.fillStyle = '#261758'
if (s[ i * 8 + j] === 'd') context.fillStyle = '#6F4D8F'
if (s[ i * 8 + j] === 'e') context.fillStyle = '#530E53'
if (s[ i * 8 + j] === 'f') context.fillStyle = '#B45A81'
context.beginPath()
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false)
context.fill()
}
}
}
function csprng_entropy(buf, crypto) {
// collect cryptographic quality randomness
const csprng = new Uint32Array(8)
crypto.getRandomValues(csprng)
console.log('System entropy: ', csprng)
for (let i = 0; i < 8; i++) {
buf[i] = csprng[i]
}
return buf
}
function timing_entropy(buf, crypto) {
// if the CSPRNG is not trustworthy, mix some timing entropy
const timings = new Uint32Array(256)
const isPrime = num => {
for(let i = 2, s = Math.sqrt(num); i <= s; i++)
if(num % i === 0) return false;
return num > 1;
}
for (let i = 0; i < 256; i++) {
const start_a = performance.now()
let j = 0, a = 0
while (performance.now() === start_a) {
if (isPrime(a * i * j)) {
a = a * i * j
}
j++
}
timings[i] = j
}
console.log('Raw timing data: ', timings)
crypto.subtle.digest('SHA-256', timings)
.then(function(hash) {
results = new Uint32Array(hash)
console.log('Timing entropy: ', results)
for (let i = 0; i < results.length; i++) {
buf[i] ^= results[i]
}
})
.catch(function(e){
console.error(e)
})
return buf
}
function mouse_entropy(buf, crypto) {
// if the CSPRNG is not trustworthy, mix some mouse entropy
const coords = new Uint32Array(256)
const mouseEntropy = function(e) {
this.ctr = this.ctr || 0
if (this.ctr > 255) {
this.ctr = 0
window.removeEventListener('mousemove', mouseEntropy)
console.log('Raw mouse data: ', coords)
crypto.subtle.digest('SHA-256', coords)
.then(function(hash) {
results = new Uint32Array(hash)
console.log('Mouse entropy: ', results)
for (let i = 0; i < results.length; i++) {
buf[i] ^= results[i]
}
console.log('Final entropy: ', buf)
})
.catch(function(e){
console.error(e)
})
const mouse_id = document.getElementById('mouse')
const entropy_results = document.getElementById('entropy_results')
const artwork = document.getElementById('artwork')
let hex = ''
for (let i = 0; i < buf.length; i++) {
hex += ('00000000'+(Number(buf[i]).toString(16))).slice(-8)
}
mouse_id.innerText = 'Stop!';
artwork.innerHTML = 'You could take a screenshot of this to use as a <a href="https://keepass.info/help/base/keys.html#keyfiles">KeePass key file</a>:'
draw_disco(hex)
return
}
else {
coords[this.ctr] = window.screen.width * e.y + e.x
this.ctr++
}
}
window.addEventListener('mousemove', mouseEntropy)
return buf
}
function reset_entropies() {
console.clear()
// the main crytpographic object
const crypto = window.crypto || window.mscrypto
// the final collected entropy after all the mixings
let finals = new Uint32Array(8)
finals = csprng_entropy(finals, crypto)
finals = timing_entropy(finals, crypto)
// wait 1 second to get the mouse away from the reset button to reduce predicting the starting event
setTimeout(function() {
finals = mouse_entropy(finals, crypto)
}, 1000)
const mouse_id = document.getElementById('mouse')
const entropy_results = document.getElementById('entropy_results')
const artwork = document.getElementById('artwork')
const canvas = document.getElementById('canvas')
const context = canvas.getContext('2d')
mouse_id.innerText = 'Keep moving your mouse...'
entropy_results.innerText = ''
artwork.innerText = ''
context.clearRect(0, 0, canvas.width, canvas.height)
}
</script>
</head>
<body onload='reset_entropies()')>
<h1>JavaScript Entropy Collector Proof-of-Concept</h1>
<p>Just a JavaScript entropy proof-of-concept (<a href='https://gist.github.com/atoponce/d7e8f7b378f80875f6c61120737de1b0'>source code</a>). In practice, whenever you need safe, secure, cryptographic-quality randomness, you should always use the Web Crypto API CSPRNG.</p>
<p>However, if you suspect that your CSPRNG is not trustworthy, this page demonstrates a possible improvement by mixing in additional entropy from external sources.</p>
<p>This was inspired by how <a href='https://www.veracrypt.fr/en/Random%20Number%20Generator.html'>VeraCrypt</a> creates volumes.</p>
<p>The procedure is as follows:
<ol>
<li>Generate 256-bits of entropy from the <tt>window.crypto.getRandomValues()</tt> CSPRNG.</li>
<li>Generate 256-bits of entropy from 256 high-precision timing events (assumes 1-bit per event).</li>
<li>Generate 256-bits of entropy from 256 collected mouse movement events (assumes 1-bit per event).</li>
<li>Calculate <tt>final = csprng xor sha-256(timings) xor sha-256(mouse)</tt>, convert to hexadecimal, and print.</li>
</ol>
</p>
<p style='font-weight: bold;'>This implementation is not yet mobile device friendly.</p>
<button onclick='reset_entropies()'>Reset</button>
<p id='mouse'></p>
<p id='entropy_results' style='color: green; font-size: 20px;'></p>
<p id='artwork'></p>
<canvas id='canvas' width='480' height='480'></canvas>
</body>
<html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment