(() => { 'use strict'; const cryptoObj = window.crypto || window.msCrypto; const storage = window.localStorage; const storageName = 'hexo-blog-encrypt:#' + window.location.pathname; const keySalt = textToArray('hexo-blog-encrypt的作者们都是大帅比!'); const ivSalt = textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!'); // As we can't detect the wrong password with AES-CBC, // so adding an empty div and check it when decrption. const knownPrefix = ""; const mainElement = document.getElementById('hexo-blog-encrypt'); const wrongPassMessage = mainElement.dataset['wpm']; const wrongHashMessage = mainElement.dataset['whm']; const dataElement = mainElement.getElementsByTagName('script')['hbeData']; const encryptedData = dataElement.innerText; const HmacDigist = dataElement.dataset['hmacdigest']; function hexToArray(s) { return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => { return parseInt(h, 16); }))); } function textToArray(s) { var i = s.length; var n = 0; var ba = new Array() for (var j = 0; j < i;) { var c = s.codePointAt(j); if (c < 128) { ba[n++] = c; j++; } else if ((c > 127) && (c < 2048)) { ba[n++] = (c >> 6) | 192; ba[n++] = (c & 63) | 128; j++; } else if ((c > 2047) && (c < 65536)) { ba[n++] = (c >> 12) | 224; ba[n++] = ((c >> 6) & 63) | 128; ba[n++] = (c & 63) | 128; j++; } else { ba[n++] = (c >> 18) | 240; ba[n++] = ((c >> 12) & 63) | 128; ba[n++] = ((c >> 6) & 63) | 128; ba[n++] = (c & 63) | 128; j += 2; } } return new Uint8Array(ba); } function arrayBufferToHex(arrayBuffer) { if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') { throw new TypeError('Expected input to be an ArrayBuffer') } var view = new Uint8Array(arrayBuffer) var result = '' var value for (var i = 0; i < view.length; i++) { value = view[i].toString(16) result += (value.length === 1 ? '0' + value : value) } return result } async function getExecutableScript(oldElem) { let out = document.createElement('script'); const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy']; attList.forEach((att) => { if (oldElem[att]) out[att] = oldElem[att]; }) return out; } async function convertHTMLToElement(content) { let out = document.createElement('div'); out.innerHTML = content; out.querySelectorAll('script').forEach(async (elem) => { elem.replaceWith(await getExecutableScript(elem)); }); return out; } function getKeyMaterial(password) { let encoder = new TextEncoder(); return cryptoObj.subtle.importKey( 'raw', encoder.encode(password), { 'name': 'PBKDF2', }, false, [ 'deriveKey', 'deriveBits', ] ); } function getHmacKey(keyMaterial) { return cryptoObj.subtle.deriveKey({ 'name': 'PBKDF2', 'hash': 'SHA-256', 'salt': keySalt.buffer, 'iterations': 1024 }, keyMaterial, { 'name': 'HMAC', 'hash': 'SHA-256', 'length': 256, }, true, [ 'verify', ]); } function getDecryptKey(keyMaterial) { return cryptoObj.subtle.deriveKey({ 'name': 'PBKDF2', 'hash': 'SHA-256', 'salt': keySalt.buffer, 'iterations': 1024, }, keyMaterial, { 'name': 'AES-CBC', 'length': 256, }, true, [ 'decrypt', ]); } function getIv(keyMaterial) { return cryptoObj.subtle.deriveBits({ 'name': 'PBKDF2', 'hash': 'SHA-256', 'salt': ivSalt.buffer, 'iterations': 512, }, keyMaterial, 16 * 8); } async function verifyContent(key, content) { const encoder = new TextEncoder(); const encoded = encoder.encode(content); let signature = hexToArray(HmacDigist); const result = await cryptoObj.subtle.verify({ 'name': 'HMAC', 'hash': 'SHA-256', }, key, signature, encoded); console.log(`Verification result: ${result}`); if (!result) { alert(wrongHashMessage); console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`); } return result; } async function decrypt(decryptKey, iv, hmacKey) { let typedArray = hexToArray(encryptedData); const result = await cryptoObj.subtle.decrypt({ 'name': 'AES-CBC', 'iv': iv, }, decryptKey, typedArray.buffer).then(async (result) => { const decoder = new TextDecoder(); const decoded = decoder.decode(result); // check the prefix, if not then we can sure here is wrong password. if (!decoded.startsWith(knownPrefix)) { throw "Decode successfully but not start with KnownPrefix."; } const hideButton = document.createElement('button'); hideButton.textContent = 'Encrypt again'; hideButton.type = 'button'; hideButton.classList.add("hbe-button"); hideButton.addEventListener('click', () => { window.localStorage.removeItem(storageName); window.location.reload(); }); document.getElementById('hexo-blog-encrypt').style.display = 'inline'; document.getElementById('hexo-blog-encrypt').innerHTML = ''; document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded)); document.getElementById('hexo-blog-encrypt').appendChild(hideButton); // support html5 lazyload functionality. document.querySelectorAll('img').forEach((elem) => { if (elem.getAttribute("data-src") && !elem.src) { elem.src = elem.getAttribute('data-src'); } }); // support theme-next refresh window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh(); // TOC part var tocDiv = document.getElementById("toc-div"); if (tocDiv) { tocDiv.style.display = 'inline'; } var tocDivs = document.getElementsByClassName('toc-div-class'); if (tocDivs && tocDivs.length > 0) { for (var idx = 0; idx < tocDivs.length; idx++) { tocDivs[idx].style.display = 'inline'; } } // trigger event var event = new Event('hexo-blog-decrypt'); window.dispatchEvent(event); return await verifyContent(hmacKey, decoded); }).catch((e) => { alert(wrongPassMessage); console.log(e); return false; }); return result; } function hbeLoader() { const oldStorageData = JSON.parse(storage.getItem(storageName)); if (oldStorageData) { console.log(`Password got from localStorage(${storageName}): `, oldStorageData); const sIv = hexToArray(oldStorageData.iv).buffer; const sDk = oldStorageData.dk; const sHmk = oldStorageData.hmk; cryptoObj.subtle.importKey('jwk', sDk, { 'name': 'AES-CBC', 'length': 256, }, true, [ 'decrypt', ]).then((dkCK) => { cryptoObj.subtle.importKey('jwk', sHmk, { 'name': 'HMAC', 'hash': 'SHA-256', 'length': 256, }, true, [ 'verify', ]).then((hmkCK) => { decrypt(dkCK, sIv, hmkCK).then((result) => { if (!result) { storage.removeItem(storageName); } }); }); }); } mainElement.addEventListener('keydown', async (event) => { if (event.isComposing || event.keyCode === 13) { const password = document.getElementById('hbePass').value; const keyMaterial = await getKeyMaterial(password); const hmacKey = await getHmacKey(keyMaterial); const decryptKey = await getDecryptKey(keyMaterial); const iv = await getIv(keyMaterial); decrypt(decryptKey, iv, hmacKey).then((result) => { console.log(`Decrypt result: ${result}`); if (result) { cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => { cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => { const newStorageData = { 'dk': dk, 'iv': arrayBufferToHex(iv), 'hmk': hmk, }; storage.setItem(storageName, JSON.stringify(newStorageData)); }); }); } }); } }); } hbeLoader(); })();