<div id="p1837">
<div class="v-flex in">
<div>合言葉</div>
<div class="h-flex">
<div class="f-grow v-flex">
<div><textarea class="words js-in w100" rows="4" spellcheck="false"></textarea></div>
<div class="seed ml-auto">seed: <span class="js-seed"></span></div></div>
<div class="v-flex">
<div><a href="#" class="btn js-clear">消去</a></div>
</div>
</div>
<div>パスワードの文字数</div>
<div class="h-flex">
<div class="f-grow"><input type="range" class="w100 js-range" min="4" max="40" value="8"></div>
<div class="num js-chars">8</div></div>
</div>
<div class="next-arrow">▼</div>
<div class="v-flex out">
<div>パスワード</div>
<div>
<div class="caption">0~9</div>
<div class="h-flex">
<div class="f-grow"><input type="text" class="gray js-out1" spellcheck="false" data-num="1" readonly></div>
<div><a href="#" class="btn js-copy" data-num="1">コピー</a></div>
</div>
</div>
<div>
<div class="caption">0~9, a~z</div>
<div class="h-flex">
<div class="f-grow"><input type="text" class="gray js-out2" spellcheck="false" data-num="2" readonly></div>
<div><a href="#" class="btn js-copy" data-num="2">コピー</a></div>
</div>
</div>
<div>
<div class="caption">0~9, a~z, A~Z</div>
<div class="h-flex">
<div class="f-grow"><input type="text" class="gray js-out3" spellcheck="false" data-num="3" readonly></div>
<div><a href="#" class="btn js-copy" data-num="3">コピー</a></div>
</div>
</div>
<div>
<div class="caption">0~9, a~z, A~Z, 記号</div>
<div class="h-flex">
<div class="f-grow"><input type="text" class="gray js-out4" spellcheck="false" data-num="4" readonly></div>
<div><a href="#" class="btn js-copy" data-num="4">コピー</a></div>
</div>
</div>
</div>
jQuery(($) => {
let seed;
main();
function main() {
$('.js-in')
.on('keyup', updateSeedAndPasswords)
.change(updateSeedAndPasswords);
$('.js-range')
.on('input', updatePasswords) // on dragging thumb
.change(updatePasswords); // on changed
$('.js-copy').click((ev) => {
const num = $(ev.currentTarget).data('num');
const pw = $(`input[data-num="${num}"]`).val();
console.log(pw);
copyToClipboard(pw);
});
$('.js-clear').click(() => {
$('.js-in').val('');
clearAll();
});
clearAll();
}
// クリップボードへテキストをコピー
function copyToClipboard(s) {
navigator.clipboard.writeText(s).then(() => {
alert('コピーしました。');
});
}
// 合言葉から seed を計算&パスワード作成
function updateSeedAndPasswords() {
const s = getIn();
if (s == '') {
clearAll();
} else {
seed = cyrb128(s);
printSeed(seed);
updatePasswords();
}
}
// 合言葉の取得&不要文字の削除
function getIn() {
let s = $('.js-in').val();
s = s.replace(/[ \t\r\n\u3000]/g, ''); // 空白文字の削除
return s;
}
// 空欄にする
function clearAll() {
$('.js-seed').val('');
for (let i = 1; i <= 4; i++) {
$(`.js-out${i}`).val('');
}
}
// seed の表示
function printSeed(seed) {
const out = [];
for (const v of seed) {
out.push(('00000000' + v.toString(16)).slice(-8));
}
$('.js-seed').text(out.join(' '));
}
// パスワード作成&表示
function updatePasswords() {
const NUM = '0123456789'; // 10
const LOWER = 'abcdefghijklmnopqrstuvwxyz'; // 26
const UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // 26
const MARK = '`~!@#$%^&*()_+-={}[]\\|:;"\'<>,.?/'; // 32
const pwLength = getChars();
$('.js-chars').text(pwLength);
const retry = 1000;
let pw;
pw = makePasswordByFamily(seed, pwLength, [NUM], retry);
$('.js-out1').val(pw);
pw = makePasswordByFamily(seed, pwLength, [NUM, LOWER], retry);
$('.js-out2').val(pw);
pw = makePasswordByFamily(seed, pwLength, [NUM, LOWER, UPPER], retry);
$('.js-out3').val(pw);
pw = makePasswordByFamily(seed, pwLength, [NUM, LOWER, UPPER, MARK], retry);
$('.js-out4').val(pw);
}
// パスワード文字数を取得
function getChars() {
return $('.js-range').val() | 0;
}
// 文字種からパスワードを作成
function makePasswordByFamily(seed, pwLength, family, retry = 1) {
const f = family.join('');
for (skip = 0; skip < retry; skip++) {
pw = makePasswordByLetters(seed, pwLength, f, skip);
if (checkPassword(pw, family)) return pw;
}
return '';
}
// パスワードの作成
function makePasswordByLetters(seed, pwLength, letters, skip = 0) {
const rand = sfc32(...seed);
const ltLength = letters.length;
let pw = '';
for (let i = 0; i < skip; i++) rand();
for (let i = 0; i < pwLength; i++) {
const j = rand() % ltLength;
pw += letters[j];
}
return pw;
}
// 各文字種から最低1文字は含めるようにする。
function checkPassword(pw, family) {
for (const s of family) {
if (!containChar(pw, s)) return false;
}
return true;
}
// subject の中に、chars のうちのどれか1文字が存在するか?
function containChar(subject, chars) {
for (const ch of chars) {
if (subject.indexOf(ch) != -1) return true;
}
return false;
}
// 文字列から seed を作成
function cyrb128(s) {
let h1 = 1779033703,
h2 = 3144134277,
h3 = 1013904242,
h4 = 2773480762;
for (let i = 0; i < s.length; i++) {
const k = s.charCodeAt(i);
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
}
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
return [
(h1 ^ h2 ^ h3 ^ h4) >>> 0,
(h2 ^ h1) >>> 0,
(h3 ^ h1) >>> 0,
(h4 ^ h1) >>> 0
];
}
// 指定された seed 値の乱数関数を作成
function sfc32(a, b, c, d) {
return function() {
a >>>= 0;
b >>>= 0;
c >>>= 0;
d >>>= 0;
let t = (a + b) | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
d = d + 1 | 0;
t = t + d | 0;
c = c + t | 0;
return t >>> 0;
}
}
});