GOLB:全新 AES 加密
睡觉做着美梦半途吵醒,精神状态感人
于是起来开发了几个小时的 AES 前端加密 Hugo 页面
故而分享简略教程新建 python 文件
首先 pip install pycryptodome
文件内容:
import os
import re
import base64
import json
import shutil
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
from Crypto.Hash import HMAC, SHA256
# --- 配置 ---
TARGET_DIR = "./public/"
FILE_EXTENSIONS = ('.html', '.htm')
# --- 加密并附带 HMAC 的函数 ---
def encrypt_and_sign_aes(plaintext: str, key: str) -> str:
"""
使用AES加密文本,并使用HMAC签名。
密钥会被分成两部分:一部分用于AES,另一部分用于HMAC。
返回Base64编码的 {iv, ciphertext, hmac} JSON对象。
"""
# 1. 处理主密钥:使用SHA-256派生出两个不同的子密钥
master_key = key.encode('utf-8')
key_hash = SHA256.new(master_key).digest() # 32 bytes
aes_key = key_hash[:16] # AES-128 key
hmac_key = key_hash[16:] # HMAC key
# 2. AES 加密
cipher = AES.new(aes_key, AES.MODE_CBC)
padded_text = pad(plaintext.encode('utf-8'), AES.block_size)
ciphertext = cipher.encrypt(padded_text)
# 3. 准备要签名的数据 (IV + ciphertext)
data_to_sign = cipher.iv + ciphertext
# 4. 生成 HMAC-SHA256 签名
h = HMAC.new(hmac_key, digestmod=SHA256)
h.update(data_to_sign)
signature = h.hexdigest()
# 5. 将 IV, ciphertext, signature 打包成 JSON 并 Base64 编码
package = {
"iv": base64.b64encode(cipher.iv).decode('utf-8'),
"ciphertext": base64.b64encode(ciphertext).decode('utf-8'),
"hmac": signature
}
return base64.b64encode(json.dumps(package).encode('utf-8')).decode('utf-8')
# --- 主处理逻辑 ---
def process_file(file_path: str):
print(f"Processing file: {file_path}")
# backup_path = file_path + ".bak"
# if not os.path.exists(backup_path):
# shutil.copy2(file_path, backup_path)
# print(f" Backup created: {backup_path}")
# else:
# print(f" Backup already exists: {backup_path}")
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
print(f" Warning: Could not read {file_path} as UTF-8. Skipping.")
return
pattern = re.compile(
r'<PYTHONENCRYPT\s+key="([^"]*?)">\s*(.*?)\s*</PYTHONENCRYPT>',
re.DOTALL | re.IGNORECASE
)
def replace_match(match):
key = match.group(1)
html_content = match.group(2)
if not key:
print(f" Warning: Found block without key in {file_path}. Skipping block.")
return match.group(0)
try:
# 使用新的带签名的加密函数
encrypted_package_b64 = encrypt_and_sign_aes(html_content, key)
return f'<script>let aesed="{encrypted_package_b64}"</script>'
except Exception as e:
print(f" Error encrypting block in {file_path}: {e}")
return match.group(0)
new_content = pattern.sub(replace_match, content)
if new_content != content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f" File updated: {file_path}")
else:
print(f" No matching blocks found or no changes made.")
if __name__ == "__main__":
original_cwd = os.getcwd()
target_path = os.path.join(original_cwd, TARGET_DIR)
if not os.path.isdir(target_path):
print(f"Error: Target directory '{target_path}' does not exist.")
exit(1)
os.chdir(target_path)
print(f"Changed working directory to: {os.getcwd()}")
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith(FILE_EXTENSIONS):
full_path = os.path.join(root, file)
display_path = full_path[2:] if full_path.startswith('./') else full_path
process_file(display_path)
print("\nAll files processed with HMAC.")
修改 baseof.html 模板
找到 {{ block "main" . }} 的位置:
(根据里面的注释仔细修改):
{{/* 加密判断 */}}
{{ if {{/* 判断需要加密的页面 */}}
}}
{{ $key := .Site.Params.ssap }}
<PYTHONENCRYPT key="{{ $key }}">
{{ block "main" . }} {{ end }}
</PYTHONENCRYPT>
<div id="protect-box" class="post">
<noscript>
<p class="error">请启用JavaScript以查看内容。</p>
</noscript>
<style>
.key-form {
padding-top: 2rem;
margin-top: 2em;
position: relative;
overflow: hidden;
}
.key-form input {
height: 2rem;
width: calc(100% - 4rem);
border: none;
border-bottom: 0.1rem solid var(--color-secondary);
outline: none;
vertical-align: middle;
font-size: 1rem;
background-color: unset !important;
color: var(--color);
transition: all 0.3s ease;
}
.key-form label {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
width: calc(100% - 4rem);
}
.key-form label span {
position: absolute;
bottom: -4.2rem;
left: 0;
transition: all 0.3s ease;
}
.key-form input:focus+label span,
.key-form input:valid+label span {
transform: translateY(-100%);
font-size: 1.5rem;
color: #5984d9;
}
.key-form input:valid,
.key-form input:focus {
border-color: #5984d9;
}
</style>
<form class="key-form">
<input id="key-query" required />
<label for="key-query">
<span>Words from the bottom of your heart.</span>
</label>
<button style="padding: 0.7rem; display: inline-flex; font-size: 1rem; font-family: unset;color: var(--color);" id="submit-button">🧨</button>
</form>
<p id="res-box"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
<script>
async function decryptAndVerifyAES(encryptedPackageB64, password) {
try {
// 1. 解析 Base64 -> JSON -> 各个组件
const jsonStr = atob(encryptedPackageB64);
const packageObj = JSON.parse(jsonStr);
const ivWA = CryptoJS.enc.Base64.parse(packageObj.iv);
const ciphertextWA = CryptoJS.enc.Base64.parse(packageObj.ciphertext);
const receivedHmacHex = packageObj.hmac;
// 2. 处理密钥:派生 AES key 和 HMAC key
const encoder = new TextEncoder();
const masterKeyU8 = encoder.encode(password);
const masterKeyWA = CryptoJS.lib.WordArray.create(masterKeyU8);
const keyHashWA = CryptoJS.SHA256(masterKeyWA);
const keyHashHex = keyHashWA.toString(CryptoJS.enc.Hex);
const aesKeyHex = keyHashHex.substring(0, 32); // 16 bytes -> 32 hex chars
const hmacKeyHex = keyHashHex.substring(32); // 16 bytes -> 32 hex chars
const aesKeyWA = CryptoJS.enc.Hex.parse(aesKeyHex);
const hmacKeyWA = CryptoJS.enc.Hex.parse(hmacKeyHex);
// 3. 验证 HMAC
const dataToSignWA = CryptoJS.lib.WordArray.create(
ivWA.words.concat(ciphertextWA.words),
ivWA.sigBytes + ciphertextWA.sigBytes
);
const computedHmac = CryptoJS.HmacSHA256(dataToSignWA, hmacKeyWA);
const computedHmacHex = computedHmac.toString(CryptoJS.enc.Hex);
if (computedHmacHex !== receivedHmacHex) {
throw new Error("HMAC verification failed! Data may be tampered or key is wrong.");
}
// 4. HMAC 验证通过,开始解密
const decryptor = CryptoJS.AES.decrypt(
{ ciphertext: ciphertextWA },
aesKeyWA,
{
iv: ivWA,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
const decryptedText = decryptor.toString(CryptoJS.enc.Utf8);
// 5. 额外检查:解密后的内容是否是有效的 UTF-8 (可选)
if (!decryptedText || decryptedText.includes('\uFFFD')) {
throw new Error("Decryption produced invalid UTF-8. Key might be wrong.");
}
return decryptedText;
} catch (error) {
console.error("Decryption/Verification failed:", error);
return `Decryption failed: ${error.message}`;
}
}
const btne = document.getElementById('submit-button');
const resb = document.getElementById('res-box');
let decryptedHtml = '';
async function renderThis(secretKey) {
// 1. 获取加密后的数据
const encryptedData = aesed; // 这是从你的 <script> 标签中读取的变量
decryptedHtml = await decryptAndVerifyAES(encryptedData, secretKey);
if (decryptedHtml.startsWith("Decryption failed:")) {
resb.innerText = decryptedHtml;
} else {
localStorage.setItem("keytime", Date.now());
document.querySelector('.content').innerHTML = decryptedHtml;
{{/* Array.from(document.querySelector('.content').querySelectorAll('script')).forEach(oldScriptEl => {
// 创建一个新的<script>节点:
const newScriptEl = document.createElement('script');
// 复制attributes:
Array.from(oldScriptEl.attributes).forEach(attr => {
newScriptEl.setAttribute(attr.name, attr.value);
});
// 复制text:
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
// 在原始位置替换原始<script>节点:
newScriptEl.appendChild(scriptText);
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
}); */}}
const scripts = Array.from(document.querySelector('.content').querySelectorAll('script'));
// 排序:带 src 的放前面
scripts.sort((a, b) => {
const aHasSrc = a.hasAttribute('src');
const bHasSrc = b.hasAttribute('src');
// 如果 a 有 src 而 b 没有,则 a 应该排在前面
if (aHasSrc && !bHasSrc) return -1;
// 如果 b 有 src 而 a 没有,则 b 应该排在前面
if (!aHasSrc && bHasSrc) return 1;
// 如果两者都有或都没有 src,则保持原有顺序(稳定排序)
return 0;
});
function loadScript(index) {
if (index >= scripts.length) return; // 所有脚本已处理完毕
const oldScriptEl = scripts[index];
const newScriptEl = document.createElement('script');
// 复制 attributes
Array.from(oldScriptEl.attributes).forEach(attr => {
newScriptEl.setAttribute(attr.name, attr.value);
});
if (oldScriptEl.src) {
// 对于外部脚本,等待加载完成后再处理下一个
newScriptEl.onload = newScriptEl.onerror = () => {
loadScript(index + 1); // 加载下一个脚本
};
} else {
// 对于内联脚本,直接设置内容并立即处理下一个
newScriptEl.text = oldScriptEl.textContent;
}
// 替换旧的 script 标签
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
// 如果是内联脚本,手动触发下一个加载
if (!oldScriptEl.src) {
loadScript(index + 1);
}
}
// 开始加载第一个脚本
loadScript(0);
}
}
if (localStorage.getItem("keytime")) {
if (Date.now() - localStorage.getItem("keytime") >= 604800000) { //过期
localStorage.removeItem("pagekey");
localStorage.removeItem("keytime");
} else { //没过期
var keykey=localStorage.getItem("pagekey");
renderThis(keykey);
}
} else {
localStorage.removeItem("pagekey");
btne.addEventListener('click', function(e) {
localStorage.setItem("pagekey", document.getElementById('key-query').value);
var keykey=localStorage.getItem("pagekey");
renderThis(keykey);
e.preventDefault();
});
}
</script>
</div>
{{ else }}
{{ block "main" . }} {{ end }}
{{ end }}
测试
生成后运行 python 该 python 文件名.py 测试
完成!
- Total words: 1573 -
Read other posts