<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Password Generator</title>
<style>
#password {
float: left;
font-size: 18pt;
border: 1px solid gray;
padding: 10px;
}
#password.placeholder {
color: grey;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
form {
clear: both;
}
:disabled + label {
color: grey;
}
#chars {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(30px, 1fr));
grid-auto-rows: 30px;
grid-gap: 2px;
}
#chars input[type=checkbox] {
display: none;
}
#chars div {
border: 1px solid gray;
}
#chars label {
display: block;
width: 100%;
height: 100%;
font: 14pt monospace;
background-color: lightgrey;
text-align: center;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
/
checked style /
#chars input[type=checkbox]:checked + label {
background-color: white;
}
#faq {
margin-top: 50px;
}
</style>
</head>
<body>
<h1>Generate an ASCII password</h1>
<h1>
https://silverhammermba.github.io/password/</h1>
<pre id="password" class="placeholder">Password</pre>
<form>
<button type="button" onclick="generate_password()">Generate</button>
<div>
<label>Length <input id="length" type="number" min="1" step="1" value="12" oninput="schedule_update(this)"></label>
<label>Bits of entropy <input id="entropy" type="number" min="1" step="1" oninput="schedule_update(this)" value="78"></label>
</div>
<h2>Options</h2>
<div id="classes">
<h3>Allowed character classes</h3>
<div>
<input type="checkbox" id="numbers" value="numbers" onclick="classClicked(this)" checked="checked">
<label for="numbers"> Numbers (0–9)</label>
</div><div>
<input type="checkbox" id="lowercase" value="lowercase" onclick="classClicked(this)" checked="checked">
<label for="lowercase"> Lowercase (a–z)</label>
</div><div>
<input type="checkbox" id="uppercase" value="uppercase" onclick="classClicked(this)" checked="checked">
<label for="uppercase"> Uppercase (A–Z)</label>
</div><div>
<input type="checkbox" id="other" value="other" onclick="classClicked(this)" checked="checked">
<label for="other"> Other (!, #, ~, etc.)</label>
</div>
</div>
<div id="otheropts">
<h3>Other Options</h3>
<div>
<input type="checkbox" id="spaces" value="spaces" onclick="schedule_update()">
<label for="spaces"> Avoid leading/trailing/consecutive spaces (more readable)</label>
<div></div>
<input type="checkbox" id="secure" value="secure">
<label for="secure"> Use secure PRNG</label>
</div>
</div>
</form>
<h2>Allowed characters</h2>
<div id="chars">
</div>
<div id="about">
</div>
<script>
'use strict';
function getLength() {
return parseInt(document.getElementById('length').value, 10);
}
function setLength(length) {
length = Math.max(1, Math.ceil(length));
document.getElementById('length').value = length;
return length;
}
function getEntropy() {
return parseInt(document.getElementById('entropy').value, 10);
}
function setEntropy(entropy) {
entropy = Math.max(1, Math.floor(entropy));
document.getElementById('entropy').value = entropy;
return entropy;
}
function isCharEnabled(codePoint) {
return document.getElementById("char" + codePoint).checked;
}
function setCharEnabled(codePoint, enabled) {
var current = isCharEnabled(codePoint);
if (enabled != current) {
document.getElementById('char' + codePoint).click();
}
}
function codeStr(i) {
var str = String.fromCodePoint(i);
if (str === " ") return "␠";
return str;
}
function getRandomInt(min, max) {
if (document.getElementById("secure").checked)
{
var mask = Math.pow(2, (max - 1).toString(2).length) - 1;
var byteArray = new Uint8Array(1);
do {
window.crypto.getRandomValues(byteArray);
byteArray[0] &= mask;
} while(byteArray[0] < min || byteArray[0] >= max);
return byteArray[0];
}
return Math.floor(Math.random() * (max - min)) + min;
}
function availableCharacters() {
var available = [];
for (var codePoint = mincode; codePoint <= maxcode; ++codePoint) {
if (isCharEnabled(codePoint)) available.push(codePoint);
}
return available;
}
function generate_password() {
clearTimeout(update_timeout);
update_entropy();
var available = availableCharacters();
if (available.length === 0) return;
var length = getLength();
var password;
if (available.length > 1 && available[0] === 32 && document.getElementById("spaces").checked) {
password = generate_nice_spaces(available, length);
} else {
password = generate_random(available, length);
}
var element = document.getElementById("password");
element.textContent = password;
element.classList.remove('placeholder');
}
function generate_nice_spaces(available, length) {
var password = [];
for (var i = 0; i < length; ++i) {
var minindex = (i === 0 || i === length - 1 || i > 0 && password[i - 1] === 32) ? 1 : 0;
password.push(available[getRandomInt(minindex, available.length)]);
}
return String.fromCodePoint.apply(this, password);
}
function generate_random(available, length) {
var password = [];
for (var i = 0; i < length; ++i) {
password.push(available[getRandomInt(0, available.length)]);
}
return String.fromCodePoint.apply(this, password);
}
function letterClicked(checkbox) {
var value = parseInt(checkbox.value, 10);
for (var klass in classes) {
if (!classes[klass].some(function(range) { return range[0] <= value && range[1] >= value; })) continue;
var classcheck = document.getElementById(klass);
if (checkbox.checked == classcheck.checked) continue;
var enabled = !classcheck.checked;
search:
for (var i = 0; i < classes[klass].length; ++i) {
for (var codePoint = classes[klass][i][0]; codePoint <= classes[klass][i][1]; ++codePoint) {
if (isCharEnabled(codePoint) != enabled) {
enabled = undefined;
break search;
}
}
}
if (enabled !== undefined) {
document.getElementById(klass).checked = enabled;
}
}
if (value == 32) {
document.getElementById("spaces").disabled = !checkbox.checked;
}
schedule_update();
}
function classClicked(checkbox) {
var ranges = classes[checkbox.value];
var enabled = checkbox.checked;
for (var i = 0; i < ranges.length; ++i) {
for (var codePoint = ranges[i][0]; codePoint <= ranges[i][1]; ++codePoint) {
setCharEnabled(codePoint, enabled);
}
}
schedule_update();
}
function schedule_update(input) {
clearTimeout(update_timeout);
if (input) fixed_input = input.id;
update_timeout = setTimeout(update_entropy, update_delay);
}
function length_to_entropy(num_chars, length) {
return Math.floor(Math.log(Math.pow(num_chars, length)) / Math.log(2));
}
function entropy_to_length(num_chars, entropy) {
return Math.ceil(Math.log(Math.pow(2, entropy)) / Math.log(num_chars));
}
function update_entropy() {
var available = availableCharacters();
if (fixed_input === "entropy") {
setLength(entropy_to_length(available.length, getEntropy()));
}
setEntropy(length_to_entropy(available.length, getLength()));
}
var update_timeout;
var update_delay = 1000;
var fixed_input = "length";
var minmax = " ~";
var mincode = minmax.codePointAt(0);
var maxcode = minmax.codePointAt(1);
var classes = {
"numbers": [[48, 57]],
"lowercase": [[97, 122]],
"uppercase": [[65, 90]],
"other": [[32, 47], [58, 64], [91, 96], [123, 126]]
}
for (var klass in classes) {
document.getElementById(klass).checked = true;
}
for (var i = mincode; i <= maxcode; ++i) {
var template = document.createElement('template');
var id = 'char' + i;
template.innerHTML = '<div><input type="checkbox" id="' + id + '" value="' + i + '" onclick="letterClicked(this)" checked><label for="' + id + '">' + codeStr(i) + '</label></div>';
document.getElementById('chars').appendChild(template.content.firstChild);
}
update_entropy();
if (!window.crypto) {
var secure = document.getElementById("secure");
secure.disabled = true;
secure.checked = false;
}
</script>
</body></html>