Thomas 的 15 個網站從「一看源代碼就破」升級到「SHA-256 Hash + 強密碼」。完整安全等級對比 + 小白實操指南。
Thomas 有 15 個 Cloudflare Pages 網站,部署在不同的 .pages.dev 域名下。其中大約 5 個包含私人數據:
問題很簡單:這些網址只要被人知道,任何人打開就能看到全部內容。沒有任何登入、密碼、或驗證機制。
想像你的日記本放在圖書館的開放書架上。書架沒上鎖,也沒寫「私人勿碰」。只要有人路過隨手翻開,你的秘密就全曝光了。網站不加保護就是這個狀態。
不同的保護方式差距巨大。以下從低到高排列,分數越高越安全:
if(sha256(input) === '7e3f630...')。源代碼只看到一串亂碼,看不到密碼。防住了 90% 的普通人。懂技術的人可以用 DevTools 繞過顯示邏輯,但暴力破解 13 位英數密碼要幾百年。if(input === '19890117')。右鍵 → 查看源代碼 → 搜索 password → 看到密碼。破解時間:10 秒。在升級之前,Thomas 的私人網站是 Level 1(明文密碼)。密碼直接寫在 JavaScript 裡,任何會按右鍵的人 10 秒就能破解。
現在是 Level 2(SHA-256 Hash)。源代碼裡只看到一串 64 字元的亂碼,看不到密碼。配合 13 位英數密碼,暴力破解需要幾百年。
| 英文 | 中文 | 白話解釋 |
|---|---|---|
Password |
密碼 | 你設的通關暗號 |
Plaintext |
明文 | 密碼直接寫在代碼裡,任何人都看得到 |
Hash |
雜湊 / 哈希 | 把密碼變成一串亂碼,不可逆(不能從亂碼算回密碼) |
SHA-256 |
安全雜湊演算法 | 一種 Hash 算法,輸出 64 個字元的十六進制字串 |
Brute Force |
暴力破解 | 一個一個試所有可能的密碼組合 |
Frontend |
前端 | 瀏覽器裡跑的代碼(用戶看得到全部) |
Backend |
後端 | 伺服器上跑的代碼(用戶看不到) |
DevTools |
開發者工具 | 瀏覽器按 F12 打開的調試面板 |
OTP |
一次性密碼 | One-Time Password,用一次就作廢的驗證碼 |
OAuth |
開放授權 | 用 Google / Facebook 帳號登入第三方網站的協議 |
localStorage |
本地儲存 | 瀏覽器存數據的地方(記住你 30 天不用重新輸密碼) |
Cloudflare Access |
零信任門禁 | Cloudflare 的後端保護服務,攔在網站前面 |
Token |
令牌 | 驗證通過後拿到的「通行證」 |
Refresh Token |
續期令牌 | 通行證過期時自動續期用的 |
Hash 就像把一顆雞蛋打進碗裡攪成蛋液。你看著蛋液不可能還原成完整的雞蛋,但每次用同一顆雞蛋打出來的蛋液「模樣」是一樣的。密碼的 Hash 就是這個原理。
"thomas2026hub""7e3f630357ccb84ce792687f853d028d08ef280b2958ae6ee8e4a7054eb0b5e8"這是理解網站安全最重要的一個概念。前端方案(Level 1-2)和後端方案(Level 3-4)的區別不是「好不好」,而是「內容在哪裡」。
因為用戶已經拿到了所有東西。密碼框只是一個「遮罩」,內容全在瀏覽器裡。就像把金庫大門敞開,只在門口拉了一條警戒線 -- 守規矩的人不會跨過去,但想進的人抬腳就過了。
因為 Level 3-4 的設定複雜度高、依賴外部服務(Cloudflare Access / Google OAuth),每次打開都要驗證很煩。而 Level 2 對 Thomas 的場景已經夠用:私人數據不是國家機密,只需要擋住隨便點進來的陌生人。安全要匹配場景,不需要用大砲打蚊子。
| 密碼 | 類型 | 可能組合數 | 暴力破解時間 | 等級 |
|---|---|---|---|---|
19890117 |
8 位純數字 | 1 億 | 幾秒 ~ 幾分鐘 | 極弱 |
password |
8 位英文小寫 | 2,000 億 | 幾小時 | 弱 |
Thomas88 |
8 位英數混合 | 2 兆 | 幾天 | 中等 |
thomas2026hub |
13 位英數混合 | 1023 | 幾百年 | 強 |
T#hom@s_2026! |
13 位含特殊字元 | 1026 | 幾萬年 | 很強 |
Thomas 選了 thomas2026hub(13 位英數混合),暴力破解需要幾百年。對個人網站來說綽綽有餘。密碼好記(名字 + 年份 + 用途),不需要特殊字元也足夠強。
13 位以上,英文加數字混合。不需要特殊字元,但不要用純數字或常見詞。
例如:thomas2026hub、mysite2026pass、cloudpage2026x
方法 A:用 Python(推薦)
python -c "import hashlib; print(hashlib.sha256('你的密碼'.encode()).hexdigest())"
方法 B:用瀏覽器 DevTools Console
按 F12 打開 DevTools → 切到 Console 頁籤 → 貼上:
crypto.subtle.digest('SHA-256', new TextEncoder().encode('你的密碼'))
.then(h => console.log(
Array.from(new Uint8Array(h))
.map(b => b.toString(16).padStart(2,'0'))
.join('')
))
把輸出的那串 64 字元十六進制字串複製下來。
把以下代碼加入你的 HTML 文件。PW_HASH 換成你在 Step 2 得到的 Hash 值。
<!-- 密碼畫面 -->
<div id="pw-screen">
<input type="password" id="pw-input"
placeholder="請輸入密碼" />
<button onclick="checkPw()">進入</button>
<div id="pw-error" style="display:none">
密碼錯誤
</div>
</div>
<!-- 主要內容(預設隱藏) -->
<div id="main-content" style="display:none">
...你的網頁內容...
</div>
<script>
const PW_HASH = '你的hash值';
const PW_KEY = location.hostname + '_pw';
const PW_DAYS = 30;
async function sha256(s) {
const d = new TextEncoder().encode(s);
const h = await crypto.subtle.digest('SHA-256', d);
return Array.from(new Uint8Array(h))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
function isPwOk() {
try {
const d = JSON.parse(
localStorage.getItem(PW_KEY) || '{}'
);
return d.ok && Date.now() < d.exp;
} catch (e) { return false; }
}
async function checkPw() {
const hash = await sha256(
document.getElementById('pw-input').value
);
if (hash === PW_HASH) {
localStorage.setItem(PW_KEY, JSON.stringify({
ok: 1,
exp: Date.now() + PW_DAYS * 864e5
}));
document.getElementById('pw-screen')
.style.display = 'none';
document.getElementById('main-content')
.style.display = 'block';
} else {
document.getElementById('pw-error')
.style.display = 'block';
document.getElementById('pw-input').value = '';
}
}
// 自動檢查:30天內免重輸
if (isPwOk()) {
document.getElementById('pw-screen')
.style.display = 'none';
document.getElementById('main-content')
.style.display = 'block';
}
// Enter 鍵觸發
document.getElementById('pw-input')
.addEventListener('keydown', function(e) {
if (e.key === 'Enter') checkPw();
});
</script>
cd 你的網站文件夾
npx wrangler pages deploy . --project-name=你的項目名 --branch=main
部署完成後,打開網站確認密碼框正常顯示、輸入密碼後能正常進入。
echo -n "你的密碼" | gcloud secrets create 網站名-password \
--project=你的專案 --data-file=-
這樣即使忘記密碼,也能用 gcloud secrets versions access latest --secret=網站名-password 查回來。
| 網站 | 保護方式 | 密碼 Hash | 安全等級 |
|---|---|---|---|
thomas-hub.pages.dev |
SHA-256 Hash | 7e3f63... |
Level 2(60 分) |
thomas-ai-notes.pages.dev |
SHA-256 Hash | 7e3f63... |
Level 2(60 分) |
bch-fy2025.pages.dev |
SHA-256 Hash | 7e3f63... |
Level 2(60 分) |
triathlon-2026.pages.dev |
SHA-256 Hash | 7e3f63... |
Level 2(60 分) |
triathlon-2026-v2.pages.dev |
SHA-256 Hash | 7e3f63... |
Level 2(60 分) |
passion-worship-2026.pages.dev |
無 | — | Level 0(公開) |
nvda-dashboard.pages.dev |
無 | — | Level 0(公開) |
密碼存放位置:GCP Secret Manager cloudflare-pages-password-thomastangnz(project: nvda-strategy)
所有受保護網站使用同一組密碼,方便記憶。Hash 值統一為 7e3f630357ccb84ce...
理論上不行。SHA-256 是單向函數,從 Hash 值無法算回原始密碼。但如果密碼太簡單(如 1234),攻擊者可以用「彩虹表」(預先算好的常見密碼 Hash 對照表)直接查到。所以密碼長度和複雜度很重要。
如果真的擔心,升級到 Level 3(Cloudflare Access Email OTP)。對 Thomas 的場景來說,能繞過前端密碼的人本來就不是他的目標防護對象。Level 2 的目的是擋住 90% 隨便點進來的人。
三個原因:(1) 設定複雜,特別是 Google OAuth 模式需要 GCP 配置,壞了就進不去;(2) 每次打開都要驗證(Email OTP 要去信箱找驗證碼);(3) 靜態網站不值得這麼折騰,SHA-256 Hash 夠用了。
重新輸入密碼即可。localStorage 只是「記住登入狀態 30 天」的便利功能,不影響安全性。清除瀏覽器數據、換瀏覽器、換電腦,都需要重新輸入密碼。
對 Thomas 的場景足夠安全。這些都是個人靜態網站,不涉及金融交易或帳號系統。用同一個密碼的好處是只需要記一組,存一份到 GCP Secret Manager 就不怕忘記。
不難。在 Cloudflare Zero Trust Dashboard 建立 Application,設定 Email OTP Policy,指定允許的 email 即可。不需要改 HTML 代碼。可以參考 GCP OAuth + 網站認證踩坑指南 裡的相關說明。