| 1 |
(function () { |
| 2 |
'use strict'; |
| 3 |
|
| 4 |
// ── Tiny MD5 (pure JS, no external deps) ──────────────────────────── |
| 5 |
// Based on the public-domain MD5 algorithm by MD5 Specification (RFC 1321) |
| 6 |
function md5(str) { |
| 7 |
function safeAdd(x, y) { var lsw=(x&0xffff)+(y&0xffff); return (((x>>16)+(y>>16)+(lsw>>16))<<16)|(lsw&0xffff); } |
| 8 |
function bitRotateLeft(num, cnt) { return (num<<cnt)|(num>>>(32-cnt)); } |
| 9 |
function md5cmn(q,a,b,x,s,t){ return safeAdd(bitRotateLeft(safeAdd(safeAdd(a,q),safeAdd(x,t)),s),b); } |
| 10 |
function md5ff(a,b,c,d,x,s,t){ return md5cmn((b&c)|((~b)&d),a,b,x,s,t); } |
| 11 |
function md5gg(a,b,c,d,x,s,t){ return md5cmn((b&d)|(c&(~d)),a,b,x,s,t); } |
| 12 |
function md5hh(a,b,c,d,x,s,t){ return md5cmn(b^c^d,a,b,x,s,t); } |
| 13 |
function md5ii(a,b,c,d,x,s,t){ return md5cmn(c^(b|(~d)),a,b,x,s,t); } |
| 14 |
|
| 15 |
var bs = []; |
| 16 |
for (var i=0;i<str.length*8;i+=8){ bs[i>>5]|=(str.charCodeAt(i/8)&0xff)<<(i%32); } |
| 17 |
var len=str.length*8; |
| 18 |
bs[len>>5]|=0x80<<(len%32); |
| 19 |
bs[(((len+64)>>>9)<<4)+14]=len; |
| 20 |
|
| 21 |
var a=1732584193,b=-271733879,c=-1732584194,d=271733878; |
| 22 |
for(var blk=0;blk<bs.length;blk+=16){ |
| 23 |
var oa=a,ob=b,oc=c,od=d,M=bs.slice(blk,blk+16); |
| 24 |
a=md5ff(a,b,c,d,M[0], 7,-680876936); d=md5ff(d,a,b,c,M[1],12,-389564586); c=md5ff(c,d,a,b,M[2],17, 606105819); b=md5ff(b,c,d,a,M[3],22,-1044525330); |
| 25 |
a=md5ff(a,b,c,d,M[4], 7,-176418897); d=md5ff(d,a,b,c,M[5],12,1200080426); c=md5ff(c,d,a,b,M[6],17,-1473231341);b=md5ff(b,c,d,a,M[7],22,-45705983); |
| 26 |
a=md5ff(a,b,c,d,M[8], 7,1770035416); d=md5ff(d,a,b,c,M[9],12,-1958414417); c=md5ff(c,d,a,b,M[10],17,-42063); b=md5ff(b,c,d,a,M[11],22,-1990404162); |
| 27 |
a=md5ff(a,b,c,d,M[12],7,1804603682); d=md5ff(d,a,b,c,M[13],12,-40341101); c=md5ff(c,d,a,b,M[14],17,-1502002290);b=md5ff(b,c,d,a,M[15],22,1236535329); |
| 28 |
a=md5gg(a,b,c,d,M[1], 5,-165796510); d=md5gg(d,a,b,c,M[6], 9,-1069501632);c=md5gg(c,d,a,b,M[11],14, 643717713);b=md5gg(b,c,d,a,M[0],20,-373897302); |
| 29 |
a=md5gg(a,b,c,d,M[5], 5,-701558691); d=md5gg(d,a,b,c,M[10],9, 38016083); c=md5gg(c,d,a,b,M[15],14,-660478335);b=md5gg(b,c,d,a,M[4],20,-405537848); |
| 30 |
a=md5gg(a,b,c,d,M[9], 5, 568446438); d=md5gg(d,a,b,c,M[14],9,-1019803690);c=md5gg(c,d,a,b,M[3],14,-187363961); b=md5gg(b,c,d,a,M[8],20,1163531501); |
| 31 |
a=md5gg(a,b,c,d,M[13],5,-1444681467);d=md5gg(d,a,b,c,M[2], 9,-51403784); c=md5gg(c,d,a,b,M[7],14,1735328473); b=md5gg(b,c,d,a,M[12],20,-1926607734); |
| 32 |
a=md5hh(a,b,c,d,M[5], 4,-378558); d=md5hh(d,a,b,c,M[8],11,-2022574463);c=md5hh(c,d,a,b,M[11],16,1839030562);b=md5hh(b,c,d,a,M[14],23,-35309556); |
| 33 |
a=md5hh(a,b,c,d,M[1], 4,-1530992060);d=md5hh(d,a,b,c,M[4],11,1272893353); c=md5hh(c,d,a,b,M[7],16,-155497632); b=md5hh(b,c,d,a,M[10],23,-1094730640); |
| 34 |
a=md5hh(a,b,c,d,M[13],4, 681279174); d=md5hh(d,a,b,c,M[0],11,-358537222); c=md5hh(c,d,a,b,M[3],16,-722521979); b=md5hh(b,c,d,a,M[6],23,76029189); |
| 35 |
a=md5hh(a,b,c,d,M[9], 4,-640364487); d=md5hh(d,a,b,c,M[12],11,-421815835);c=md5hh(c,d,a,b,M[15],16,530742520); b=md5hh(b,c,d,a,M[2],23,-995338651); |
| 36 |
a=md5ii(a,b,c,d,M[0], 6,-198630844); d=md5ii(d,a,b,c,M[7],10,1126891415); c=md5ii(c,d,a,b,M[14],15,-1416354905);b=md5ii(b,c,d,a,M[5],21,-57434055); |
| 37 |
a=md5ii(a,b,c,d,M[12],6, 1700485571);d=md5ii(d,a,b,c,M[3],10,-1894986606); c=md5ii(c,d,a,b,M[10],15,-1051523); b=md5ii(b,c,d,a,M[1],21,-2054922799); |
| 38 |
a=md5ii(a,b,c,d,M[8], 6, 1873313359);d=md5ii(d,a,b,c,M[15],10,-30611744); c=md5ii(c,d,a,b,M[6],15,-1560198380);b=md5ii(b,c,d,a,M[13],21,1309151649); |
| 39 |
a=md5ii(a,b,c,d,M[4], 6,-145523070); d=md5ii(d,a,b,c,M[11],10,-1120210379);c=md5ii(c,d,a,b,M[2],15, 718787259); b=md5ii(b,c,d,a,M[9],21,-343485551); |
| 40 |
a=safeAdd(a,oa); b=safeAdd(b,ob); c=safeAdd(c,oc); d=safeAdd(d,od); |
| 41 |
} |
| 42 |
var hex='',vals=[a,b,c,d]; |
| 43 |
for(var vi=0;vi<4;vi++){ |
| 44 |
var v=vals[vi]; |
| 45 |
for(var bi=0;bi<4;bi++){ var byte_=((v>>>(bi*8))&0xff); hex+=(byte_<16?'0':'')+byte_.toString(16); } |
| 46 |
} |
| 47 |
return hex; |
| 48 |
} |
| 49 |
|
| 50 |
// ── SHA via Web Crypto API ─────────────────────────────────────────── |
| 51 |
function shaHash(algorithm, text) { |
| 52 |
var encoder = new TextEncoder(); |
| 53 |
var data = encoder.encode(text); |
| 54 |
var algo = algorithm === 'SHA-1' ? 'SHA-1' : algorithm; // SHA-256, SHA-512 |
| 55 |
return crypto.subtle.digest(algo, data).then(function(buf) { |
| 56 |
var arr = Array.from(new Uint8Array(buf)); |
| 57 |
return arr.map(function(b){ return (b<16?'0':'')+b.toString(16); }).join(''); |
| 58 |
}); |
| 59 |
} |
| 60 |
|
| 61 |
// ── Bootstrap ──────────────────────────────────────────────────────── |
| 62 |
function typoCssVarsForEl(typo, prefix, el) { |
| 63 |
if (!typo || typeof typo !== 'object') return; |
| 64 |
var map = { |
| 65 |
family:'font-family', weight:'font-weight', style:'font-style', |
| 66 |
decoration:'text-decoration', transform:'text-transform', |
| 67 |
sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m', |
| 68 |
lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m', |
| 69 |
letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m', |
| 70 |
wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m' |
| 71 |
}; |
| 72 |
Object.keys(map).forEach(function(k) { |
| 73 |
if (typo[k] !== undefined && typo[k] !== '') { |
| 74 |
var v = typo[k]; |
| 75 |
if (['sizeDesktop','sizeTablet','sizeMobile'].indexOf(k) !== -1) v = v + (typo.sizeUnit || 'px'); |
| 76 |
else if (['lineHeightDesktop','lineHeightTablet','lineHeightMobile'].indexOf(k) !== -1) v = v + (typo.lineHeightUnit || ''); |
| 77 |
else if (['letterSpacingDesktop','letterSpacingTablet','letterSpacingMobile'].indexOf(k) !== -1) v = v + (typo.letterSpacingUnit || 'px'); |
| 78 |
else if (['wordSpacingDesktop','wordSpacingTablet','wordSpacingMobile'].indexOf(k) !== -1) v = v + (typo.wordSpacingUnit || 'px'); |
| 79 |
el.style.setProperty(prefix + map[k], String(v)); |
| 80 |
} |
| 81 |
}); |
| 82 |
} |
| 83 |
|
| 84 |
document.querySelectorAll('.bkbg-hg-app').forEach(function(root) { |
| 85 |
var opts; |
| 86 |
try { opts = JSON.parse(root.getAttribute('data-opts') || '{}'); } |
| 87 |
catch(e) { return; } |
| 88 |
|
| 89 |
var o = { |
| 90 |
title: opts.title || 'Hash Generator', |
| 91 |
subtitle: opts.subtitle || '', |
| 92 |
defaultAlgorithm: opts.defaultAlgorithm || 'SHA-256', |
| 93 |
defaultInput: opts.defaultInput || '', |
| 94 |
showMD5: opts.showMD5 !== false, |
| 95 |
showSHA1: opts.showSHA1 !== false, |
| 96 |
showSHA256: opts.showSHA256 !== false, |
| 97 |
showSHA512: opts.showSHA512 === true, |
| 98 |
showCompare: opts.showCompare !== false, |
| 99 |
autoHash: opts.autoHash !== false, |
| 100 |
accentColor: opts.accentColor || '#6366f1', |
| 101 |
sectionBg: opts.sectionBg || '#f5f3ff', |
| 102 |
cardBg: opts.cardBg || '#ffffff', |
| 103 |
inputBg: opts.inputBg || '#f9fafb', |
| 104 |
hashBg: opts.hashBg || '#1e1b4b', |
| 105 |
hashColor: opts.hashColor || '#a5b4fc', |
| 106 |
titleColor: opts.titleColor || '#111827', |
| 107 |
subtitleColor: opts.subtitleColor || '#6b7280', |
| 108 |
labelColor: opts.labelColor || '#374151', |
| 109 |
titleFontSize: parseInt(opts.titleFontSize) || 26, |
| 110 |
contentMaxWidth: parseInt(opts.contentMaxWidth) || 700 |
| 111 |
}; |
| 112 |
|
| 113 |
var enabledAlgos = []; |
| 114 |
if (o.showMD5) enabledAlgos.push('MD5'); |
| 115 |
if (o.showSHA1) enabledAlgos.push('SHA-1'); |
| 116 |
if (o.showSHA256) enabledAlgos.push('SHA-256'); |
| 117 |
if (o.showSHA512) enabledAlgos.push('SHA-512'); |
| 118 |
|
| 119 |
if (!enabledAlgos.length) return; |
| 120 |
var activeAlgo = enabledAlgos.indexOf(o.defaultAlgorithm) >= 0 ? o.defaultAlgorithm : enabledAlgos[0]; |
| 121 |
|
| 122 |
// ── Render skeleton ──────────────────────────────────────────── |
| 123 |
var wrap = root; |
| 124 |
typoCssVarsForEl(opts.typoTitle, '--bkbg-hg-tt-', wrap); |
| 125 |
typoCssVarsForEl(opts.typoSubtitle, '--bkbg-hg-st-', wrap); |
| 126 |
|
| 127 |
root.innerHTML = |
| 128 |
'<div class="bkbg-hg-wrap" style="background:' + o.sectionBg + ';max-width:' + o.contentMaxWidth + 'px;--hg-accent:' + o.accentColor + '">' + |
| 129 |
(o.title ? '<h3 class="bkbg-hg-title" style="color:' + o.titleColor + '">' + o.title + '</h3>' : '') + |
| 130 |
(o.subtitle ? '<p class="bkbg-hg-subtitle" style="color:' + o.subtitleColor + '">' + o.subtitle + '</p>' : '') + |
| 131 |
|
| 132 |
'<div class="bkbg-hg-field">' + |
| 133 |
'<label class="bkbg-hg-field-label" style="color:' + o.labelColor + '">Input Text</label>' + |
| 134 |
'<textarea id="hg-input" class="bkbg-hg-textarea" style="background:' + o.inputBg + ';color:' + o.labelColor + '" placeholder="Type or paste text here…">' + (o.defaultInput || '') + '</textarea>' + |
| 135 |
'</div>' + |
| 136 |
|
| 137 |
'<div class="bkbg-hg-algos" id="hg-algos">' + |
| 138 |
enabledAlgos.map(function(a) { |
| 139 |
return '<button class="bkbg-hg-algo-btn' + (a === activeAlgo ? ' active' : '') + '" data-algo="' + a + '"' + |
| 140 |
(a === activeAlgo ? ' style="background:' + o.accentColor + ';border-color:' + o.accentColor + ';color:#fff"' : 'style="color:' + o.labelColor + '"') + '>' + a + '</button>'; |
| 141 |
}).join('') + |
| 142 |
'</div>' + |
| 143 |
|
| 144 |
'<div class="bkbg-hg-rows" id="hg-rows">' + renderRowsHtml('', [], o) + '</div>' + |
| 145 |
|
| 146 |
(o.showCompare ? |
| 147 |
'<div class="bkbg-hg-compare" id="hg-compare" style="background:' + o.cardBg + '">' + |
| 148 |
'<div class="bkbg-hg-compare-label" style="color:' + o.labelColor + '">Verify / Compare Hash</div>' + |
| 149 |
'<div class="bkbg-hg-compare-row">' + |
| 150 |
'<input id="hg-compare-in" type="text" class="bkbg-hg-compare-input" style="background:' + o.inputBg + ';color:' + o.labelColor + '" placeholder="Paste expected hash to compare…">' + |
| 151 |
'<span id="hg-compare-badge" class="bkbg-hg-compare-badge" style="display:none"></span>' + |
| 152 |
'</div>' + |
| 153 |
'</div>' |
| 154 |
: '') + |
| 155 |
'</div>'; |
| 156 |
|
| 157 |
var textareaEl = root.querySelector('#hg-input'); |
| 158 |
var algosEl = root.querySelector('#hg-algos'); |
| 159 |
var rowsEl = root.querySelector('#hg-rows'); |
| 160 |
var compareInEl = root.querySelector('#hg-compare-in'); |
| 161 |
var compareBadge= root.querySelector('#hg-compare-badge'); |
| 162 |
|
| 163 |
// computed hash cache |
| 164 |
var hashCache = {}; |
| 165 |
|
| 166 |
function renderRowsHtml(text, computed, opt) { |
| 167 |
if (!text) { |
| 168 |
return enabledAlgos.map(function(a) { |
| 169 |
return '<div class="bkbg-hg-row" style="background:' + opt.hashBg + '">' + |
| 170 |
'<span class="bkbg-hg-row-algo">' + a + '</span>' + |
| 171 |
'<span class="bkbg-hg-row-value" style="color:' + opt.hashColor + ';opacity:0.4">— enter text to generate —</span>' + |
| 172 |
'</div>'; |
| 173 |
}).join(''); |
| 174 |
} |
| 175 |
return enabledAlgos.map(function(a) { |
| 176 |
var val = computed[a] || '…'; |
| 177 |
return '<div class="bkbg-hg-row" style="background:' + opt.hashBg + '" data-algo="' + a + '">' + |
| 178 |
'<span class="bkbg-hg-row-algo">' + a + '</span>' + |
| 179 |
'<span class="bkbg-hg-row-value" id="hg-val-' + a.replace(/[^a-z0-9]/gi,'-') + '" style="color:' + opt.hashColor + '">' + val + '</span>' + |
| 180 |
'<button class="bkbg-hg-row-copy" data-algo="' + a + '" title="Copy ' + a + ' hash">⧉</button>' + |
| 181 |
'<span class="bkbg-hg-copy-toast" id="hg-toast-' + a.replace(/[^a-z0-9]/gi,'-') + '">Copied!</span>' + |
| 182 |
'</div>'; |
| 183 |
}).join(''); |
| 184 |
} |
| 185 |
|
| 186 |
// ── Compute hashes ───────────────────────────────────────────── |
| 187 |
var hashTimer = null; |
| 188 |
function computeHashes(text) { |
| 189 |
if (!text) { |
| 190 |
hashCache = {}; |
| 191 |
rowsEl.innerHTML = renderRowsHtml('', {}, o); |
| 192 |
if (compareBadge) compareBadge.style.display = 'none'; |
| 193 |
return; |
| 194 |
} |
| 195 |
rowsEl.innerHTML = renderRowsHtml(text, {}, o); |
| 196 |
bindCopyBtns(); |
| 197 |
|
| 198 |
// MD5 — synchronous |
| 199 |
if (o.showMD5) { |
| 200 |
var m = md5(text); |
| 201 |
hashCache['MD5'] = m; |
| 202 |
var el = root.querySelector('#hg-val-MD5'); |
| 203 |
if (el) el.textContent = m; |
| 204 |
} |
| 205 |
|
| 206 |
// SHA — async (Web Crypto) |
| 207 |
function doSha(algo) { |
| 208 |
if (!window.crypto || !window.crypto.subtle) { |
| 209 |
var el2 = root.querySelector('#hg-val-' + algo.replace(/[^a-z0-9]/gi,'-')); |
| 210 |
if (el2) el2.textContent = '(Web Crypto not available)'; |
| 211 |
return; |
| 212 |
} |
| 213 |
shaHash(algo, text).then(function(hex) { |
| 214 |
hashCache[algo] = hex; |
| 215 |
var rowEl = root.querySelector('#hg-val-' + algo.replace(/[^a-z0-9]/gi,'-')); |
| 216 |
if (rowEl) rowEl.textContent = hex; |
| 217 |
updateCompare(); |
| 218 |
}).catch(function() {}); |
| 219 |
} |
| 220 |
if (o.showSHA1) doSha('SHA-1'); |
| 221 |
if (o.showSHA256) doSha('SHA-256'); |
| 222 |
if (o.showSHA512) doSha('SHA-512'); |
| 223 |
|
| 224 |
updateCompare(); |
| 225 |
bindCopyBtns(); |
| 226 |
} |
| 227 |
|
| 228 |
function updateCompare() { |
| 229 |
if (!compareInEl || !compareBadge) return; |
| 230 |
var expected = compareInEl.value.trim().toLowerCase(); |
| 231 |
if (!expected || !hashCache[activeAlgo]) { |
| 232 |
compareBadge.style.display = 'none'; |
| 233 |
return; |
| 234 |
} |
| 235 |
var actual = hashCache[activeAlgo].toLowerCase(); |
| 236 |
var match = (actual === expected); |
| 237 |
compareBadge.className = 'bkbg-hg-compare-badge ' + (match ? 'match' : 'nomatch'); |
| 238 |
compareBadge.textContent = match ? '✓ Match' : '✗ No match'; |
| 239 |
compareBadge.style.display = 'inline-block'; |
| 240 |
} |
| 241 |
|
| 242 |
function bindCopyBtns() { |
| 243 |
root.querySelectorAll('.bkbg-hg-row-copy').forEach(function(btn) { |
| 244 |
btn.addEventListener('click', function() { |
| 245 |
var algo = btn.getAttribute('data-algo'); |
| 246 |
var hash = hashCache[algo]; |
| 247 |
var toast = root.querySelector('#hg-toast-' + algo.replace(/[^a-z0-9]/gi,'-')); |
| 248 |
if (!hash) return; |
| 249 |
navigator.clipboard.writeText(hash).then(function() { |
| 250 |
if (toast) { toast.classList.add('show'); setTimeout(function(){ toast.classList.remove('show'); }, 1800); } |
| 251 |
}).catch(function(){}); |
| 252 |
}); |
| 253 |
}); |
| 254 |
} |
| 255 |
|
| 256 |
// ── Input debounce ───────────────────────────────────────────── |
| 257 |
textareaEl.addEventListener('input', function() { |
| 258 |
if (!o.autoHash) return; |
| 259 |
clearTimeout(hashTimer); |
| 260 |
hashTimer = setTimeout(function(){ computeHashes(textareaEl.value); }, 300); |
| 261 |
}); |
| 262 |
|
| 263 |
// ── Algo tab switching ───────────────────────────────────────── |
| 264 |
algosEl.querySelectorAll('.bkbg-hg-algo-btn').forEach(function(btn) { |
| 265 |
btn.addEventListener('click', function() { |
| 266 |
activeAlgo = btn.getAttribute('data-algo'); |
| 267 |
algosEl.querySelectorAll('.bkbg-hg-algo-btn').forEach(function(b) { |
| 268 |
var act = b.getAttribute('data-algo') === activeAlgo; |
| 269 |
b.classList.toggle('active', act); |
| 270 |
b.style.background = act ? o.accentColor : ''; |
| 271 |
b.style.borderColor = act ? o.accentColor : ''; |
| 272 |
b.style.color = act ? '#fff' : o.labelColor; |
| 273 |
}); |
| 274 |
updateCompare(); |
| 275 |
}); |
| 276 |
}); |
| 277 |
|
| 278 |
// ── Compare input ────────────────────────────────────────────── |
| 279 |
if (compareInEl) { |
| 280 |
compareInEl.addEventListener('input', updateCompare); |
| 281 |
} |
| 282 |
|
| 283 |
// ── Initial hash (if prefilled) ──────────────────────────────── |
| 284 |
if (o.defaultInput) { |
| 285 |
computeHashes(o.defaultInput); |
| 286 |
} |
| 287 |
|
| 288 |
// ── Manual hash button (if autoHash off) ────────────────────── |
| 289 |
if (!o.autoHash) { |
| 290 |
var btn = document.createElement('button'); |
| 291 |
btn.textContent = 'Generate Hashes'; |
| 292 |
btn.style.cssText = 'margin-top:10px;padding:10px 22px;border-radius:9px;border:none;background:' + o.accentColor + ';color:#fff;font-size:14px;font-weight:700;cursor:pointer;transition:opacity 0.2s;'; |
| 293 |
textareaEl.parentNode.insertBefore(btn, textareaEl.nextSibling); |
| 294 |
btn.addEventListener('click', function(){ computeHashes(textareaEl.value); }); |
| 295 |
} |
| 296 |
}); |
| 297 |
})(); |
| 298 |
|