03エントロピービット値表示 — 文字集合の大きさの値と文字数の値をかけた計算から強度の値が出る方式
文字集合の大きさの値と文字数の値をかけた計算から出るビット値が大きいほど強度の値が高く付けられます。
使いどころセキュリティ水準を数値で確かめさせる開発者向けアカウント設定、APIキー発行画面のシークレット値の強度表示
charsetSize(value)bitsOf(value)MAX_BITS = 100

<main class="stage">
<div class="eb is-demo" id="eb">
<label class="eb__field">
<input class="eb__input" id="ebInput" type="text" autocomplete="off" spellcheck="false" value="">
</label>
<div class="eb__bits"><b id="ebBits">0</b><span>bit</span></div>
<div class="eb__track" id="ebTrack">
<span class="eb__tick" style="--tick-at:20%"></span>
<span class="eb__tick" style="--tick-at:40%"></span>
<span class="eb__tick" style="--tick-at:60%"></span>
<span class="eb__tick" style="--tick-at:80%"></span>
<div class="eb__fill" id="ebFill"></div>
</div>
<div class="eb__meta">文字集合 <b id="ebSet">0</b> ・文字数 <b id="ebLen">0</b></div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// 계산 겹: 문자 집합 크기 값 × 글자 수 값에 log2 를 취해 비트 값을 만든다 —
// 채움 막대(scaleX)는 그 비트 값을 상한(100비트) 대비 비율로 보여줄 뿐이다.
@include stage($stage-orange);
.eb {
width: 100%;
max-width: 320px;
display: grid;
gap: $sp-3;
}
.eb__field { display: block; }
.eb__input {
width: 100%;
box-sizing: border-box;
padding: $sp-3 $sp-4;
border-radius: $r-control;
border: 0;
background: $subject-cream;
color: $subject-ink;
font: 500 16px/1.3 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: .04em;
-webkit-text-security: disc;
&:focus { outline: 2px solid $subject-ink; outline-offset: 2px; }
}
.eb__bits {
display: flex;
align-items: baseline;
gap: $sp-1;
color: $subject-ink;
b { font: 800 34px/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif; font-variant-numeric: tabular-nums; }
span { font: 600 13px/1 -apple-system, sans-serif; opacity: .75; }
}
.eb__track {
position: relative;
height: $sp-3;
border-radius: $r-sm;
background: rgba($subject-ink, .14);
overflow: hidden;
}
.eb__fill {
position: absolute;
inset: 0;
border-radius: $r-sm;
background: $subject-blue;
transform: scaleX(0);
transform-origin: left center;
transition: transform $dur-base $ease-out;
}
.eb__tick {
position: absolute;
top: 0;
left: var(--tick-at);
width: 2px;
height: 100%;
background: rgba($subject-ink, .3);
z-index: 1;
}
.eb__meta {
font: 600 13px/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: $subject-ink;
b { font-variant-numeric: tabular-nums; }
}
@media (max-width: 340px) {
.eb { max-width: 260px; }
.eb__input { padding: $sp-2 $sp-3; font-size: 14px; }
.eb__bits b { font-size: 26px; }
.eb__meta { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.eb__fill { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.eb {
width: 100%;
max-width: 320px;
display: grid;
gap: 12px;
}
.eb__field {
display: block;
}
.eb__input {
width: 100%;
box-sizing: border-box;
padding: 12px 16px;
border-radius: 12px;
border: 0;
background: #fff7e6;
color: #17141a;
font: 500 16px/1.3 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
letter-spacing: 0.04em;
-webkit-text-security: disc;
}
.eb__input:focus {
outline: 2px solid #17141a;
outline-offset: 2px;
}
.eb__bits {
display: flex;
align-items: baseline;
gap: 4px;
color: #17141a;
}
.eb__bits b {
font: 800 34px/1 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-variant-numeric: tabular-nums;
}
.eb__bits span {
font: 600 13px/1 -apple-system, sans-serif;
opacity: 0.75;
}
.eb__track {
position: relative;
height: 12px;
border-radius: 8px;
background: rgba(23, 20, 26, 0.14);
overflow: hidden;
}
.eb__fill {
position: absolute;
inset: 0;
border-radius: 8px;
background: #2f6df6;
transform: scaleX(0);
transform-origin: left center;
transition: transform 300ms ease-out;
}
.eb__tick {
position: absolute;
top: 0;
left: var(--tick-at);
width: 2px;
height: 100%;
background: rgba(23, 20, 26, 0.3);
z-index: 1;
}
.eb__meta {
font: 600 13px/1.2 -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
color: #17141a;
}
.eb__meta b {
font-variant-numeric: tabular-nums;
}
@media (max-width: 340px) {
.eb {
max-width: 260px;
}
.eb__input {
padding: 8px 12px;
font-size: 14px;
}
.eb__bits b {
font-size: 26px;
}
.eb__meta {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.eb__fill {
transition: none;
}
}(function () {
var root = document.getElementById('eb');
var input = document.getElementById('ebInput');
var bitsEl = document.getElementById('ebBits');
var fill = document.getElementById('ebFill');
var setEl = document.getElementById('ebSet');
var lenEl = document.getElementById('ebLen');
var MAX_BITS = 100; // 막대 눈금의 오른쪽 끝 — 이 値 이상은 꽉 찬 막대로 표시
// 계산 겹: 섞인 문자 종류로 文字集合 크기 값을 구하고, 文字数 값을 곱한 뒤
// 밑이 2인 로그를 취해 비트 값을 만든다 (엔트로피 = 文字数 × log2(文字集合 크기)).
function charsetSize(value) {
var size = 0;
if (/[a-z]/.test(value)) size += 26;
if (/[A-Z]/.test(value)) size += 26;
if (/[0-9]/.test(value)) size += 10;
if (/[^A-Za-z0-9]/.test(value)) size += 32;
return size;
}
function bitsOf(value) {
var size = charsetSize(value);
if (!value.length || size === 0) return { bits: 0, size: size };
return { bits: value.length * (Math.log(size) / Math.log(2)), size: size };
}
function paint(value) {
var r = bitsOf(value);
var bits = Math.round(r.bits);
bitsEl.textContent = String(bits);
fill.style.transform = 'scaleX(' + Math.min(1, r.bits / MAX_BITS) + ')';
setEl.textContent = String(r.size);
lenEl.textContent = String(value.length);
}
input.addEventListener('input', function () {
root.classList.remove('is-demo');
stopAutoplay();
paint(input.value);
});
paint(input.value);
// 자동 루프: 시드 고정 문자열 배열로 길이·문자 집합을 함께 늘렸다 줄인다.
var SEQ = ['', 'abc', 'abcdef', 'abcdefgh12', 'abcdefgh12AB', 'abcdefgh12AB!@',
'abcdefgh12AB!@#$%^', 'abcdefgh12AB!@', 'abcdefgh12AB', 'abcdefgh12', 'abcdef', 'abc', ''];
var si = 0;
var autoTimer = null;
function autoTick() { input.value = SEQ[si]; paint(SEQ[si]); si = (si + 1) % SEQ.length; }
function stopAutoplay() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null; } }
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduce) { autoTimer = setInterval(autoTick, 320); autoTick(); }
})();import { useEffect, useRef } from "react";
import styles from "./EntropyGauge.module.scss";
type Box<T> = { current: T };
const MAX_BITS = 100;
export default function EntropyGauge() {
const rootRef = useRef(null) as unknown as Box<HTMLDivElement | null>;
useEffect(() => {
const root = rootRef.current;
if (!root) return;
const input = root.querySelector(`.${styles.input}`) as HTMLInputElement | null;
const bitsEl = root.querySelector(`.${styles.bitsNum}`) as HTMLElement | null;
const fill = root.querySelector(`.${styles.fill}`) as HTMLElement | null;
const setEl = root.querySelector(`.${styles.setSize}`) as HTMLElement | null;
const lenEl = root.querySelector(`.${styles.len}`) as HTMLElement | null;
if (!input || !bitsEl || !fill || !setEl || !lenEl) return;
// 계산 겹: 섞인 문자 종류로 문자 집합 크기 값을 구하고, 글자 수 값을 곱한 뒤
// 밑이 2인 로그를 취해 비트 값을 만든다.
function charsetSize(value: string) {
let size = 0;
if (/[a-z]/.test(value)) size += 26;
if (/[A-Z]/.test(value)) size += 26;
if (/[0-9]/.test(value)) size += 10;
if (/[^A-Za-z0-9]/.test(value)) size += 32;
return size;
}
function bitsOf(value: string) {
const size = charsetSize(value);
if (!value.length || size === 0) return { bits: 0, size };
return { bits: value.length * (Math.log(size) / Math.log(2)), size };
}
function paint(value: string) {
const r = bitsOf(value);
bitsEl!.textContent = String(Math.round(r.bits));
fill!.style.transform = "scaleX(" + Math.min(1, r.bits / MAX_BITS) + ")";
setEl!.textContent = String(r.size);
lenEl!.textContent = String(value.length);
}
function onInput() {
root!.classList.remove(styles["is-demo"]);
stopAutoplay();
paint(input!.value);
}
input.addEventListener("input", onInput);
paint(input.value);
// 자동 루프: 시드 고정 문자열 배열로 길이·문자 집합을 함께 늘렸다 줄인다.
const SEQ = ["", "abc", "abcdef", "abcdefgh12", "abcdefgh12AB", "abcdefgh12AB!@",
"abcdefgh12AB!@#$%^", "abcdefgh12AB!@", "abcdefgh12AB", "abcdefgh12", "abcdef", "abc", ""];
let si = 0;
let autoTimer: number | null = null;
function autoTick() { input!.value = SEQ[si]; paint(SEQ[si]); si = (si + 1) % SEQ.length; }
function stopAutoplay() { if (autoTimer !== null) { window.clearInterval(autoTimer); autoTimer = null; } }
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduce) { autoTimer = window.setInterval(autoTick, 320); autoTick(); }
return () => {
stopAutoplay();
input.removeEventListener("input", onInput);
};
}, []);
return (
<div ref={rootRef} className={`${styles.stage} ${styles.eb} ${styles["is-demo"]}`}>
<label className={styles.field}>
<input className={styles.input} type="text" autoComplete="off" spellCheck={false} defaultValue="" />
</label>
<div className={styles.bits}><b className={styles.bitsNum}>0</b><span>bit</span></div>
<div className={styles.track}>
<span className={styles.tick} style={{ left: "20%" }} />
<span className={styles.tick} style={{ left: "40%" }} />
<span className={styles.tick} style={{ left: "60%" }} />
<span className={styles.tick} style={{ left: "80%" }} />
<div className={styles.fill} />
</div>
<div className={styles.meta}>문자 집합 <b className={styles.setSize}>0</b> · 글자 수 <b className={styles.len}>0</b></div>
</div>
);
}03 エントロピービット値表示 — 文字集合の大きさの値と文字数の値をかけた計算から強度の値が出る方式 — 計測 累積面積 4.297% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワード強度チェック UI 9選 — 強度の値が計算される仕組み
このデモが属する記事
パスワード強度チェック UI 9選 — 強度の値が計算される仕組み9つのうち1つは文字種の値がいくつ混ざっているかで強度の値が決まり、1つはよくある値のリストと同じ状態になると強度の値が減り、もう1つは確認入力の値と元の値が食い違い始める位置の値をリアルタイムで見つけます。パスワード強度チェック、9つの作 この記事のデモをすべて見る


