Last active
August 29, 2020 20:52
-
-
Save atoponce/e6a5b5b29223b905fe0193690c6f50a8 to your computer and use it in GitHub Desktop.
Clean room DiceKey web generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
<title>Secure DiceKey Generator</title> | |
<script> | |
function secureRandGen(count) { | |
let min = (-count >>> 0) % count; | |
let randBytes = new Uint32Array(1); | |
const crypto = window.crypto || window.msCrypto; | |
do { | |
crypto.getRandomValues(randBytes); | |
} while (randBytes[0] < min); | |
return randBytes[0] % count; | |
} | |
function shuffleDice() { | |
let chars = "ABCDEFGHIJKLMNOPRSTUVWXYZ".split(""); | |
for (let i=0; i<chars.length; i++) { | |
var randInt = secureRandGen(chars.length); | |
var tmp = chars[randInt]; | |
chars[randInt] = chars[i]; | |
chars[i] = tmp; | |
} | |
return chars; // start with string, return array | |
} | |
function rotateDice() { | |
for (let i=1; i<=25; i++) { | |
var cell = document.getElementById("cell" + i); | |
var randInt = secureRandGen(4); | |
if (randInt === 1) cell.classList.add("rotate90"); | |
else if (randInt === 2) cell.classList.add("rotate180"); | |
else if (randInt === 3) cell.classList.add("rotate270"); | |
} | |
} | |
function convertDecToBin(num) { | |
let res = num.toString(2); | |
return res.padStart(11, '0'); | |
} | |
function opticalBits(res) { | |
// black = 0, white = 1 | |
// [2^10, 2^9, 2^8, ..., 2^2, 2^1, 2^0] | |
const topBits = { | |
"A1":2002,"B1":1996,"C1":1946,"D1":1924,"E1":1954,"F1":1864,"G1":1878,"H1":1904,"I1":1844,"J1":1830,"K1":1846,"L1":1812,"M1":1798,"N1":1744,"O1":1762,"P1":1766,"R1":1698,"S1":1668,"T1":1710,"U1":1676,"V1":1694,"W1":1648,"X1":1622,"Y1":1642,"Z1":1656, | |
"A2":2018,"B2":1926,"C2":1952,"D2":1968,"E2":1944,"F2":1892,"G2":1908,"H2":1884,"I2":1816,"J2":1820,"K2":1848,"L2":1818,"M2":1800,"N2":1778,"O2":1772,"P2":1768,"R2":1708,"S2":1674,"T2":1696,"U2":1666,"V2":1680,"W2":1604,"X2":1624,"Y2":1636,"Z2":1654, | |
"A3":1984,"B3":1928,"C3":1938,"D3":1972,"E3":1942,"F3":1898,"G3":1870,"H3":1874,"I3":1814,"J3":1810,"K3":1794,"L3":1808,"M3":1842,"N3":1736,"O3":1750,"P3":1732,"R3":1678,"S3":1702,"T3":1690,"U3":1672,"V3":1706,"W3":1610,"X3":1634,"Y3":1608,"Z3":1612, | |
"A4":2020,"B4":1932,"C4":1960,"D4":1934,"E4":1868,"F4":1872,"G4":1856,"H4":1896,"I4":1836,"J4":1840,"K4":1804,"L4":1822,"M4":1748,"N4":1734,"O4":1752,"P4":1738,"R4":1716,"S4":1704,"T4":1684,"U4":1670,"V4":1640,"W4":1614,"X4":1644,"Y4":1606,"Z4":1602, | |
"A5":2004,"B5":1976,"C5":1958,"D5":1920,"E5":1912,"F5":1890,"G5":1866,"H5":1894,"I5":1826,"J5":1802,"K5":1824,"L5":1828,"M5":1760,"N5":1728,"O5":1746,"P5":1776,"R5":1722,"S5":1682,"T5":1720,"U5":1724,"V5":1618,"W5":1652,"X5":1630,"Y5":1660,"Z5":1646, | |
"A6":1986,"B6":1940,"C6":1930,"D6":1964,"E6":1862,"F6":1880,"G6":1860,"H6":1850,"I6":1832,"J6":1796,"K6":1838,"L6":1834,"M6":1764,"N6":1742,"O6":1756,"P6":1688,"R6":1712,"S6":1692,"T6":1718,"U6":1714,"V6":1628,"W6":1658,"X6":1616,"Y6":1650,"Z6":1632 | |
}; | |
const bottomBits = { | |
"A1":1038,"B1":1086,"C1":1114,"D1":1130,"E1":1146,"F1":1174,"G1":1190,"H1":1206,"I1":1222,"J1":1234,"K1":1248,"L1":1260,"M1":1272,"N1":1302,"O1":1320,"P1":1332,"R1":1348,"S1":1364,"T1":1376,"U1":1388,"V1":1400,"W1":1416,"X1":1432,"Y1":1444,"Z1":1456, | |
"A2":1046,"B2":1100,"C2":1116,"D2":1134,"E2":1148,"F2":1176,"G2":1194,"H2":1208,"I2":1224,"J2":1236,"K2":1250,"L2":1262,"M2":1274,"N2":1306,"O2":1322,"P2":1334,"R2":1350,"S2":1366,"T2":1378,"U2":1390,"V2":1402,"W2":1420,"X2":1434,"Y2":1446,"Z2":1458, | |
"A3":1050,"B3":1102,"C3":1122,"D3":1138,"E3":1150,"F3":1178,"G3":1196,"H3":1210,"I3":1226,"J3":1238,"K3":1252,"L3":1264,"M3":1276,"N3":1308,"O3":1324,"P3":1336,"R3":1354,"S3":1368,"T3":1380,"U3":1392,"V3":1404,"W3":1422,"X3":1436,"Y3":1448,"Z3":1460, | |
"A4":1068,"B4":1106,"C4":1124,"D4":1140,"E4":1162,"F4":1180,"G4":1198,"H4":1212,"I4":1228,"J4":1242,"K4":1254,"L4":1266,"M4":1290,"N4":1310,"O4":1326,"P4":1338,"R4":1356,"S4":1370,"T4":1382,"U4":1394,"V4":1410,"W4":1426,"X4":1438,"Y4":1450,"Z4":1462, | |
"A5":1076,"B5":1110,"C5":1126,"D5":1142,"E5":1166,"F5":1186,"G5":1200,"H5":1214,"I5":1230,"J5":1244,"K5":1256,"L5":1268,"M5":1294,"N5":1316,"O5":1328,"P5":1340,"R5":1358,"S5":1372,"T5":1384,"U5":1396,"V5":1412,"W5":1428,"X5":1440,"Y5":1452,"Z5":1464, | |
"A6":1084,"B6":1112,"C6":1128,"D6":1144,"E6":1172,"F6":1188,"G6":1202,"H6":1220,"I6":1232,"J6":1246,"K6":1258,"L6":1270,"M6":1298,"N6":1318,"O6":1330,"P6":1346,"R6":1360,"S6":1374,"T6":1386,"U6":1398,"V6":1414,"W6":1430,"X6":1442,"Y6":1454,"Z6":1466 | |
}; | |
return [topBits[res], bottomBits[res]]; | |
} | |
function generatePixels(bitString) { | |
let divs = ""; | |
for (let i=0; i<11; i++) { | |
if (bitString[i] == "0") divs += '<div class="black bit"></div>'; | |
else divs += '<div class="white bit"></div>'; | |
} | |
return divs; | |
} | |
function populateCells() { | |
let diceArray = shuffleDice(); | |
for (let i=1; i<=25; i++) { | |
var cell = document.getElementById("cell" + i); | |
var die = diceArray[i-1]; | |
var side = secureRandGen(6) + 1; | |
var res = die + side; | |
var topBits = opticalBits(res)[0]; | |
var topDivs = generatePixels(convertDecToBin(topBits)); | |
var topDiv = document.createElement("div"); | |
topDiv.className = "bits"; | |
topDiv.innerHTML = topDivs; | |
cell.appendChild(topDiv); | |
var face = document.createElement("div"); | |
face.className = "text flex"; | |
face.innerText = res; | |
cell.appendChild(face); | |
var bottomBits = opticalBits(res)[1]; | |
var bottomDivs = generatePixels(convertDecToBin(bottomBits)); | |
var bottomDiv = document.createElement("div"); | |
bottomDiv.className = "bits"; | |
bottomDiv.innerHTML = bottomDivs; | |
cell.appendChild(bottomDiv); | |
} | |
rotateDice(); | |
} | |
function resetCells() { | |
for (let i=1; i<=25; i++) { | |
var cell = document.getElementById("cell" + i); | |
cell.className = "tableCell"; | |
cell.removeChild(cell.childNodes[2]); | |
cell.removeChild(cell.childNodes[1]); | |
cell.removeChild(cell.childNodes[0]); | |
} | |
} | |
</script> | |
<style> | |
.flex { | |
align-items: center; | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: center; | |
} | |
#table { | |
background-color: #000033; | |
border-collapse: separate; | |
border-radius: 15px; | |
border-spacing: 20px; | |
display: table; | |
} | |
#tableBody { | |
display: table-row-group; | |
} | |
.tableRow { | |
display: table-row; | |
} | |
.tableCell { | |
background-color: white; | |
border-radius: 10px; | |
display: table-cell; | |
height: 100px; | |
vertical-align: middle; | |
width: 100px; | |
} | |
.bits { | |
background-color: black; | |
border: 1px solid black; | |
border-spacing: 0.5px; | |
display: table; | |
height: 5px; | |
margin: -6px auto; | |
padding: 2px; | |
} | |
.bit { | |
display: table-cell; | |
height: 5px; | |
margin: 0 auto; | |
width: 5px; | |
} | |
.black { | |
background-color: black; | |
} | |
.white { | |
background-color: white; | |
} | |
.text { | |
font-family: monospace; | |
font-size: 40px; | |
font-weight: bold; | |
height: 55px; | |
letter-spacing: 4px; | |
text-indent: 4px; | |
width: 100px; | |
} | |
.rotate90 { | |
transform: rotate(90deg); | |
} | |
.rotate180 { | |
transform: rotate(180deg); | |
} | |
.rotate270 { | |
transform: rotate(270deg); | |
} | |
.break { | |
flex-basis: 100%; | |
height: 0; | |
} | |
#howto { | |
align-self: stretch; | |
margin-left: 20px; | |
vertical-align: top; | |
width: 600px; | |
} | |
</style> | |
</head> | |
<body onload="populateCells()"> | |
<div id="container" class="flex"> | |
<div id="table"> | |
<div id="tableBody"> | |
<div class="tableRow"> | |
<div id="cell1" class="tableCell"></div> | |
<div id="cell2" class="tableCell"></div> | |
<div id="cell3" class="tableCell"></div> | |
<div id="cell4" class="tableCell"></div> | |
<div id="cell5" class="tableCell"></div> | |
</div> <!-- tableRow --> | |
<div class="tableRow"> | |
<div id="cell6" class="tableCell"></div> | |
<div id="cell7" class="tableCell"></div> | |
<div id="cell8" class="tableCell"></div> | |
<div id="cell9" class="tableCell"></div> | |
<div id="cell10" class="tableCell"></div> | |
</div> <!-- tableRow --> | |
<div class="tableRow"> | |
<div id="cell11" class="tableCell"></div> | |
<div id="cell12" class="tableCell"></div> | |
<div id="cell13" class="tableCell"></div> | |
<div id="cell14" class="tableCell"></div> | |
<div id="cell15" class="tableCell"></div> | |
</div> <!-- tableRow --> | |
<div class="tableRow"> | |
<div id="cell16" class="tableCell"></div> | |
<div id="cell17" class="tableCell"></div> | |
<div id="cell18" class="tableCell"></div> | |
<div id="cell19" class="tableCell"></div> | |
<div id="cell20" class="tableCell"></div> | |
</div> <!-- tableRow --> | |
<div class="tableRow"> | |
<div id="cell21" class="tableCell"></div> | |
<div id="cell22" class="tableCell"></div> | |
<div id="cell23" class="tableCell"></div> | |
<div id="cell24" class="tableCell"></div> | |
<div id="cell25" class="tableCell"></div> | |
</div> <!-- tableRow --> | |
</div> <!-- tableBody --> | |
</div> <!-- table --> | |
<div id="howto"> | |
<h3>How to turn this into a master password?</h3> | |
<p>This is a clean room implementation of the <a href="https://dicekeys.com">physical device</a>. Please purchase the actual device at <a href="https://www.crowdsupply.com/dicekeys/dicekeys">Crowd Supply</a>.</p> | |
<p><strong>For demonstration purposes only!</strong></p> | |
<p>Here are four different approaches. To define orientation of each die, you could use:</p> | |
<ul> | |
<li>"N" for north (pointing up)</li> | |
<li>"E" for east (resting on its right side)</li> | |
<li>"S" for south (upside-down)</li> | |
<li>"W" for west (resting on its left side)</li> | |
</ul> | |
<p>E.G., "A4W" for A4 oriented west, "L5S" for L5 oriented south, etc.</p> | |
<ol> | |
<li>Use the <a href="https://dicekeys.app">official upstream mobile app</a> to scan it. | |
<ul><li>Native Android and iOS apps coming soon.</li></ul> | |
</li> | |
<li>Record the results directly, using the orientation above: | |
<ul><li>E.G., "F1S Z3S B6W T6E F6N A2E I2S ..."</li></ul> | |
</li> | |
<li>For 138-bit security, 12-word passphrases by hand: | |
<ol> | |
<li>Record the alphabetic characters of two consecutive dice.</li> | |
<li>Record the digit of the first die.</li> | |
<li>Record the orientation of the second die.</li> | |
<li>Locate its index in <a href="https://gist.github.com/atoponce/648436a7b8492d13039040e38f87762b">this word list</a>.</li> | |
</ol> | |
</li> | |
<li>For 192-bit security, 12-word passphrases with software: | |
<ol> | |
<li>Record the results directly, as in approach 2 above.</li> | |
<li>Paste the result into <a href="https://gchq.github.io/CyberChef/#recipe=Shake('256',192)">this CyberChef recipe</a>.</li> | |
<li>Copy and paste the resulting hex string <a href="https://diracdeltas.github.io/niceware/">into Niceware</a>.</li> | |
</ol> | |
</li> | |
</ol> | |
<div class="button"> | |
<button onclick="resetCells(); populateCells()">Generate Random DiceKey</button> | |
</div> | |
</div> <!-- howto --> | |
</div> <!-- container --> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment