Skip to content

Instantly share code, notes, and snippets.

@atoponce
Last active August 29, 2020 20:52
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save atoponce/e6a5b5b29223b905fe0193690c6f50a8 to your computer and use it in GitHub Desktop.
Save atoponce/e6a5b5b29223b905fe0193690c6f50a8 to your computer and use it in GitHub Desktop.
Clean room DiceKey web generator
<!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