正在研究js-md5
发表于 : 2025年 6月 28日 17:12
https://github.com/blueimp/JavaScript-MD5
該項目已經存檔,不再更新。
該項目已經存檔,不再更新。
代码: 全选
當然可以!以下是如何在本地瀏覽器中使用提供的 `md5.js` 代碼的繁體中文說明:
### 前提條件
1. **CryptoJS 庫**:`md5.js` 依賴於 CryptoJS 的核心模組(`core.js`),提供 `WordArray` 和 `Hasher` 等基礎類。你需要核心模組和 MD5 模組。
2. **HTML 文件**:創建一個 HTML 文件來載入腳本並執行 MD5 雜湊計算。
3. **本地文件**:確保 `core.js` 和 `md5.js` 在你的項目目錄中。
### 使用步驟
1. **獲取 CryptoJS 核心模組**:
- `md5.js` 需要 `core.js`。你可以從 CryptoJS 官方網站或 CDN 下載完整庫。
- 為方便起見,建議使用 CDN(如 jsDelivr)載入核心和 MD5 模組。
2. **設置項目目錄**:
- 將 `core.js` 和 `md5.js` 放入項目資料夾(例如 `./js/crypto-js/`),或使用 CDN。
- 在同一目錄中創建一個 HTML 文件(例如 `index.html`)。
3. **創建 HTML 文件**:
以下是載入 CryptoJS(核心和 MD5)並展示 MD5 雜湊計算的 HTML 示例:
```html
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MD5 雜湊範例</title>
</head>
<body>
<h1>MD5 雜湊範例</h1>
<p>輸入:<input type="text" id="inputText" value="Hello, World!"></p>
<button onclick="computeMD5()">計算 MD5</button>
<p>MD5 雜湊:<span id="output"></span></p>
<!-- 從 CDN 載入 CryptoJS 核心和 MD5 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/md5.min.js"></script>
<!-- 或者使用本地文件 -->
<!-- <script src="js/crypto-js/core.js"></script> -->
<!-- <script src="js/crypto-js/md5.js"></script> -->
<script>
function computeMD5() {
// 獲取輸入文字
const inputText = document.getElementById('inputText').value;
// 計算 MD5 雜湊
const hash = CryptoJS.MD5(inputText);
// 顯示雜湊值(轉為十六進位字串)
document.getElementById('output').textContent = hash.toString();
}
</script>
</body>
</html>
```
4. **代碼說明**:
- **載入腳本**:
- 使用 `<script>` 標籤從 CDN 載入 `core.min.js` 和 `md5.min.js`。若使用本地文件,則替換為本地路徑。
- 確保 `core.js` 在 `md5.js` 之前載入,因後者依賴前者。
- **計算 MD5**:
- `computeMD5` 函數從 HTML 輸入欄位獲取文字。
- `CryptoJS.MD5(inputText)` 計算輸入文字的 MD5 雜湊。
- 使用 `toString()` 將雜湊轉為十六進位字串並顯示在 `<span>` 中。
5. **運行 HTML 文件**:
- **本地伺服器**:由於瀏覽器安全限制(CORS),不能直接用 `file://` 打開 HTML 文件。需使用本地網頁伺服器:
- **Node.js**:安裝 `http-server`(`npm install -g http-server`),在項目目錄運行 `http-server`,然後訪問 `http://localhost:8080`。
- **Python**:在項目目錄運行 `python -m http.server 8000`(Python 3),訪問 `http://localhost:8000`。
- **其他選項**:使用 VS Code 的 Live Server 擴展或其他本地伺服器工具。
- 在瀏覽器中打開伺服器 URL(例如 `http://localhost:8080`)。
6. **測試 MD5 雜湊**:
- 在瀏覽器中打開 HTML 頁面。
- 在輸入欄位輸入文字(例如 "Hello, World!")。
- 點擊「計算 MD5」按鈕,查看 MD5 雜湊值(例如 `65a8e27d8879283831b664bd8b7f0ad4`)。
7. **使用本地 `md5.js`(可選)**:
- 若要直接使用提供的 `md5.js` 代碼,確保項目資料夾中有兼容的 `core.js`。
- 將提供的 `md5.js` 代碼保存為 `md5.js`(例如 `./js/crypto-js/md5.js`)。
- 在 HTML 文件中引用,如上述註解中的 `<script>` 標籤所示。
- 確認 `core.js` 與 `md5.js` 版本兼容,提供的代碼需 `CryptoJS` 物件包含 `lib.WordArray` 和 `сию
8. **其他注意事項**:
- **HMAC-MD5**:代碼也支持 HMAC-MD5(例如 `CryptoJS.HmacMD5(message, key)`)。可修改腳本以計算 HMAC-MD5:
```javascript
const hmac = CryptoJS.HmacMD5("message", "secretkey").toString();
```
- **錯誤處理**:若雜湊計算失敗,檢查瀏覽器控制台(F12 或 Ctrl+Shift+J)是否有錯誤,例如缺少 `CryptoJS` 或文件路徑錯誤。
- **安全提醒**:MD5 已被認為在密碼學上不安全,不應用於安全關鍵任務(如密碼雜湊),僅用於非安全用途(如校驗和)。
### 示例輸出
輸入 "Hello, World!",MD5 雜湊應為:
```
65a8e27d8879283831b664bd8b7f0ad4
```
### 故障排除
- **腳本無效**:確保 `core.js` 在 `md5.js` 之前載入,檢查控制台錯誤。
- **CORS 問題**:使用本地網頁伺服器而非直接打開 HTML 文件。
- **缺少依賴**:若使用本地文件,確認 `core.js` 版本與 `md5.js` 兼容。
按照以上步驟,你即可在本地瀏覽器中使用 `md5.js` 計算 MD5 雜湊。如需具體部分協助,請告訴我!
代码: 全选
好的,我們來詳細介紹一下 SJCL (Stanford Javascript Crypto Library) 可用的加密模式,以及在使用 PBKDF2 時,加密結果中除了密文、鹽和初始向量 (IV) 之外,還需要哪些參數。
### SJCL 簡介
SJCL 是一個由斯坦福大學計算機安全實驗室設計和開發的 JavaScript 加密庫,旨在提供安全、快速且易於使用的加密工具。它特別注重默認設置的安全性,以幫助開發者避免常見的密碼學錯誤。
### PBKDF2 的角色
首先,需要釐清一個重要的概念:**PBKDF2 (Password-Based Key Derivation Function 2) 並不是一種加密模式**,而是一個密鑰派生函數。它的作用是將一個(通常是較弱的)用戶密碼轉換成一個強度足夠的、固定長度的密鑰,以供後續的加密算法使用。
這個過程通常如下:
`密碼 + 鹽 (Salt) -> PBKDF2 (反覆運算) -> 加密密鑰`
因此,當你在 SJCL 中使用密碼進行加密時,PBKDF2 會被自動調用來生成密鑰。這也是為什麼加密結果中會包含 `salt`(鹽)和 `iter`(反覆運算次數)的原因。
-----
### SJCL 可用的主要加密模式
SJCL 主要支持的是**經過身份驗證的加密模式 (Authenticated Encryption with Associated Data, AEAD)**。這類模式的優點是在加密數據的同時,還能生成一個驗證標籤 (Authentication Tag),用於確保數據在傳輸或存儲過程中沒有被篡改。這比傳統的非驗證模式(如 CBC)要安全得多。
SJCL 的默認和推薦模式是 **CCM**。
1. **CCM (Counter with CBC-MAC)**
* **簡介**: 這是一種為塊加密算法(如 AES)設計的 AEAD 模式,結合了 CTR (Counter) 模式的加密和 CBC-MAC 的認證功能。它是 SJCL 的**默認模式**。
* **優點**: 安全性高,能同時保證機密性 (Confidentiality) 和完整性 (Integrity)。
2. **GCM (Galois/Counter Mode)**
* **簡介**: 另一種流行的 AEAD 模式,同樣結合了 CTR 模式和一種基於伽羅瓦域 (Galois Field) 乘法的認證機制。
* **優點**: 通常比 CCM 有更高的性能(尤其是在有硬件加速支持的環境下),同樣提供高強度的機密性和完整性驗證。
3. **OCB 2.0 (Offset Codebook Mode)**
* **簡介**: 這是一種非常高效的 AEAD 模式,計算成本很低。
* **注意**: OCB 模式受到專利保護,雖然在某些情況下可以免費使用,但在商業項目中使用前需要注意其許可證問題。因此,CCM 或 GCM 是更普遍的選擇。
4. **CBC (Cipher Block Chaining)**
* **簡介**: 一種較傳統的加密模式。SJCL 也支持此模式,但不推薦直接使用,因為它**本身不提供數據完整性驗證**。如果必須使用,開發者需要自己實現一個獨立的 MAC(如 HMAC)來防止篡改攻擊。SJCL 的設計哲學就是盡量避免讓開發者陷入這種困境。
-----
### 加密結果中還需要什麼?
當你使用 `sjcl.encrypt()` 函數並傳入密碼時,SJCL 會自動處理 PBKDF2 和加密,並生成一個 JSON 字符串。這個字符串包含了**解密所需的所有參數**。
我們來分解一下這個 JSON 對象的典型結構:
```json
{
"iv": "...", // 初始向量 (Initialization Vector)
"v": 1, // SJCL 版本號
"iter": 10000, // PBKDF2 的反覆運算次數
"ks": 128, // 密鑰長度 (Key Size),單位是位
"ts": 64, // 驗證標籤長度 (Tag Size),單位是位
"mode": "ccm", // 使用的加密模式
"adata": "", // 附加驗證數據 (Additional Authenticated Data)
"cipher": "aes", // 使用的加密算法
"salt": "...", // 用於 PBKDF2 的鹽
"ct": "..." // 密文 (Ciphertext),其中包含了加密後的數據和驗證標籤
}
```
除了你提到的**密文 (`ct`)**、**鹽 (`salt`)** 和 **IV (`iv`)** 之外,這些不同的模式還需要以下關鍵信息才能成功解密:
| 參數 | 解釋 | 為何需要? |
| :--- | :--- | :--- |
| **`mode`** | **加密模式** | 這是解密的核心參數之一。解密時必須使用與加密時完全相同的模式(如 `ccm` 或 `gcm`),否則無法正確還原數據。 |
| **`iter`** | **PBKDF2 迭代次數** | 解密時,需要使用用戶密碼、`salt` 和完全相同的 `iter` 次數,才能派生出與加密時一致的密鑰。 |
| **`ks`** | **密鑰長度** | 派生密鑰時需要知道目標密鑰的長度(如 128 位、192 位或 256 位)。 |
| **`ts`** | **驗證標籤長度** | **對於 AEAD 模式(如 CCM, GCM)至關重要**。它告訴解密程序,在密文 (`ct`) 的末尾有多少位是驗證標籤。解密程序會重新計算標籤並與此部分進行比較,以驗證數據的完整性。 |
| **`adata`** | **附加驗證數據** | 這是一個可選參數。`adata` 是指那些**不需要加密但需要保護其完整性**的數據(例如,元數據、非機密的頭部信息等)。如果在加密時提供了 `adata`,那麼在解密時**必須提供完全相同的 `adata`**,否則驗證會失敗。 |
| `cipher` | 加密算法 | 標明了使用的對稱加密算法,通常是 `aes`。 |
| `v` | 版本號 | 確保庫版本的兼容性。 |
### 總結
總結來說,當你使用 SJCL 配合 PBKDF2 進行加密時,除了**密文、鹽、IV** 之外,解密過程還依賴於一個完整的參數集合,其中:
* **對於所有模式**:都需要 `iter` 和 `ks` 來重新生成正確的加密密鑰。
* **對於 AEAD 模式 (CCM, GCM)**:`mode`、`ts` 和 `adata` 是解密和驗證數據完整性的關鍵。解密函數會從密文 `ct` 中提取驗證標籤,並使用這些參數來驗證數據是否被篡改。
SJCL 的優勢在於它將所有這些必需的參數都整齊地打包在一個 JSON 對象中,大大簡化了開發者的工作,並降低了因遺漏參數而導致解密失敗或安全漏洞的風險。
代码: 全选
{
"ct": "...", // Ciphertext (Base64)
"salt": "...", // Salt (Hex)
"iv": "...", // IV (Hex)
"mac": "...", // HMAC Tag (Hex)
"config": {
"mode": "CBC",
"padding": "Pkcs7",
"iterations": 1000,
"keySize": 256
}
}
代码: 全选
用crypto.js以及SJCL做一個html+js+css程式,適配手機端。
要求如下:
一,html的元素有:
h2{加密}
textarea#ejc_text{明文}
input#ejc_encryptpw{密碼}
button[type=button]#ejc_encrypt{加密}
rypto.js的aes加密結果:
pre#ejc_aes_ecb
pre#ejc_aes_cbc
pre#ejc_aes_cfb
pre#ejc_aes_ctr
pre#ejc_aes_ofb
SJCL的aes加密結果:
pre#sjcl_mode_cbc
pre#sjcl_mode_ccm
pre#sjcl_mode_ctr
pre#sjcl_mode_gcm
h2{解密}
textarea#ejc_encrypttext
input#ejc_decryptpw
button[type=button]#ejc_decrypt{解密}
textarea#ejc_decryptresult
二,在必要的地方添加h4標題。
三,當點擊ejc_encrypt時,crypto.js以及SJCL將產生加密結果。
四,pre需強制換行。其內容將為json格式。
如果是SJCL的加密結果,其格式為:
{"iv":"Phr6GAlAjJiotvc+mj8sZQ==",
"v":1,
"iter":1000,
"ks":128,
"ts":64,
"mode":"ccm",
"adata":"",
"cipher":"aes",
"salt":"ZnJ87+1uVg0=",
"ct":"pf7M+U5nenIAXHcFJhVo1w/oBjQ="}
如果是crypto.js的加密結果,其格式為:
{
"ct": "...", // Ciphertext (Base64)
"salt": "...", // Salt (Base64)
"iv": "...", // IV (Base64)
"hmac-sha256": "...", // HMAC Tag (Base64)
"config": {
"mode": "CBC",
"padding": "Pkcs7",
"iterations": 1000,
"keySize": 256
}
}
五,當產生結果後,點擊其中一個pre,它的內容將輸入至ejc_encrypttext中。
六,當擊點ejc_decrypt時,js將識別它是crypto.js還是SJCL的加密結果,然後用ejc_decryptpw解密。
代码: 全选
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加密解密工具</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="https://bitwiseshiftleft.github.io/sjcl/sjcl.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
max-width: 100%;
box-sizing: border-box;
}
h2, h4 {
color: #333;
}
textarea, input, button {
width: 100%;
margin-bottom: 10px;
padding: 10px;
box-sizing: border-box;
font-size: 16px;
}
textarea {
height: 100px;
resize: vertical;
}
input[type="password"] {
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
pre {
background-color: #fff;
border: 1px solid #ccc;
padding: 10px;
white-space: pre-wrap;
word-wrap: break-word;
cursor: pointer;
margin-bottom: 10px;
font-size: 14px;
}
pre:hover {
background-color: #f0f0f0;
}
@media (max-width: 600px) {
body {
padding: 10px;
}
textarea, input, button {
font-size: 14px;
}
pre {
font-size: 12px;
}
}
</style>
</head>
<body>
<h2>加密</h2>
<h4>明文</h4>
<textarea id="ejc_text" placeholder="輸入明文"></textarea>
<h4>密碼</h4>
<input type="password" id="ejc_encryptpw" placeholder="輸入加密密碼">
<button type="button" id="ejc_encrypt">加密</button>
<h4>Crypto.js AES 加密結果</h4>
<h5>ECB Mode</h5>
<pre id="ejc_aes_ecb"></pre>
<h5>CBC Mode</h5>
<pre id="ejc_aes_cbc"></pre>
<h5>CFB Mode</h5>
<pre id="ejc_aes_cfb"></pre>
<h5>CTR Mode</h5>
<pre id="ejc_aes_ctr"></pre>
<h5>OFB Mode</h5>
<pre id="ejc_aes_ofb"></pre>
<h4>SJCL AES 加密結果</h4>
<h5>CBC Mode</h5>
<pre id="sjcl_mode_cbc"></pre>
<h5>CCM Mode</h5>
<pre id="sjcl_mode_ccm"></pre>
<h5>CTR Mode</h5>
<pre id="sjcl_mode_ctr"></pre>
<h5>GCM Mode</h5>
<pre id="sjcl_mode_gcm"></pre>
<h2>解密</h2>
<h4>密文</h4>
<textarea id="ejc_encrypttext" placeholder="輸入密文"></textarea>
<h4>密碼</h4>
<input type="password" id="ejc_decryptpw" placeholder="輸入解密密碼">
<button type="button" id="ejc_decrypt">解密</button>
<h4>解密結果</h4>
<textarea id="ejc_decryptresult" readonly></textarea>
<script>
// 加密函數
document.getElementById('ejc_encrypt').addEventListener('click', () => {
const plaintext = document.getElementById('ejc_text').value;
const password = document.getElementById('ejc_encryptpw').value;
if (!plaintext || !password) {
alert('請輸入明文和密碼');
return;
}
// CryptoJS 加密
const modes = [
{ id: 'ejc_aes_ecb', mode: CryptoJS.mode.ECB },
{ id: 'ejc_aes_cbc', mode: CryptoJS.mode.CBC },
{ id: 'ejc_aes_cfb', mode: CryptoJS.mode.CFB },
{ id: 'ejc_aes_ctr', mode: CryptoJS.mode.CTR },
{ id: 'ejc_aes_ofb', mode: CryptoJS.mode.OFB }
];
modes.forEach(({ id, mode }) => {
const salt = CryptoJS.lib.WordArray.random(16);
const iv = mode === CryptoJS.mode.ECB ? '' : CryptoJS.lib.WordArray.random(16);
const key = CryptoJS.PBKDF2(password, salt, { keySize: 256/32, iterations: 1000 });
const encrypted = CryptoJS.AES.encrypt(plaintext, key, {
iv: iv,
mode: mode,
padding: CryptoJS.pad.Pkcs7
});
const hmac = CryptoJS.HmacSHA256(encrypted.ciphertext, key).toString(CryptoJS.enc.Base64);
const result = {
ct: encrypted.ciphertext.toString(CryptoJS.enc.Base64),
salt: salt.toString(CryptoJS.enc.Base64),
iv: iv ? iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': hmac,
config: {
mode: mode === CryptoJS.mode.ECB ? 'ECB' :
mode === CryptoJS.mode.CBC ? 'CBC' :
mode === CryptoJS.mode.CFB ? 'CFB' :
mode === CryptoJS.mode.CTR ? 'CTR' : 'OFB',
padding: 'Pkcs7',
iterations: 1000,
keySize: 256
}
};
document.getElementById(id).textContent = JSON.stringify(result, null, 2);
});
// SJCL 加密
const sjclModes = [
{ id: 'sjcl_mode_cbc', mode: 'cbc' },
{ id: 'sjcl_mode_ccm', mode: 'ccm' },
{ id: 'sjcl_mode_ctr', mode: 'ctr' },
{ id: 'sjcl_mode_gcm', mode: 'gcm' }
];
sjclModes.forEach(({ id, mode }) => {
const encrypted = sjcl.encrypt(password, plaintext, {
mode: mode,
iter: 1000,
ks: 128,
ts: 64
});
document.getElementById(id).textContent = JSON.stringify(JSON.parse(encrypted), null, 2);
});
});
// 點擊 pre 複製到密文輸入框
const pres = document.querySelectorAll('pre');
pres.forEach(pre => {
pre.addEventListener('click', () => {
document.getElementById('ejc_encrypttext').value = pre.textContent;
});
});
// 解密函數
document.getElementById('ejc_decrypt').addEventListener('click', () => {
const ciphertext = document.getElementById('ejc_encrypttext').value;
const password = document.getElementById('ejc_decryptpw').value;
const resultField = document.getElementById('ejc_decryptresult');
if (!ciphertext || !password) {
alert('請輸入密文和密碼');
resultField.value = '';
return;
}
try {
const json = JSON.parse(ciphertext);
if (json.ct && json.salt && json.config) {
// CryptoJS 解密
const { ct, salt, iv, config } = json;
const mode = config.mode === 'ECB' ? CryptoJS.mode.ECB :
config.mode === 'CBC' ? CryptoJS.mode.CBC :
config.mode === 'CFB' ? CryptoJS.mode.CFB :
config.mode === 'CTR' ? CryptoJS.mode.CTR :
CryptoJS.mode.OFB;
const key = CryptoJS.PBKDF2(password, CryptoJS.enc.Base64.parse(salt), {
keySize: 256/32,
iterations: config.iterations
});
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(ct) },
key,
{
iv: iv ? CryptoJS.enc.Base64.parse(iv) : undefined,
mode: mode,
padding: CryptoJS.pad.Pkcs7
}
);
const hmac = CryptoJS.HmacSHA256(CryptoJS.enc.Base64.parse(ct), key).toString(CryptoJS.enc.Base64);
if (hmac !== json['hmac-sha256']) {
throw new Error('HMAC 驗證失敗');
}
resultField.value = decrypted.toString(CryptoJS.enc.Utf8);
} else if (json.ct && json.iv && json.salt) {
// SJCL 解密
const decrypted = sjcl.decrypt(password, JSON.stringify(json));
resultField.value = decrypted;
} else {
throw new Error('無效的密文格式');
}
} catch (e) {
resultField.value = '解密失敗: ' + e.message;
}
});
</script>
</body>
</html>
代码: 全选
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加密解密工具</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>加密解密工具</h1>
<div class="container">
<h2>加密</h2>
<h4>明文</h4>
<textarea id="ejc_text" placeholder="輸入明文"></textarea>
<h4>密碼</h4>
<input type="password" id="ejc_encryptpw" placeholder="輸入密碼">
<button type="button" id="ejc_encrypt">加密</button>
<hr>
<h3>Crypto.js AES 加密結果:</h3>
<h4>ECB 模式</h4>
<pre id="ejc_aes_ecb" class="breakable"></pre>
<h4>CBC 模式</h4>
<pre id="ejc_aes_cbc" class="breakable"></pre>
<h4>CFB 模式</h4>
<pre id="ejc_aes_cfb" class="breakable"></pre>
<h4>CTR 模式</h4>
<pre id="ejc_aes_ctr" class="breakable"></pre>
<h4>OFB 模式</h4>
<pre id="ejc_aes_ofb" class="breakable"></pre>
<hr>
<h3>SJCL AES 加密結果:</h3>
<h4>CBC 模式</h4>
<pre id="sjcl_mode_cbc" class="breakable"></pre>
<h4>CCM 模式</h4>
<pre id="sjcl_mode_ccm" class="breakable"></pre>
<h4>CTR 模式</h4>
<pre id="sjcl_mode_ctr" class="breakable"></pre>
<h4>GCM 模式</h4>
<pre id="sjcl_mode_gcm" class="breakable"></pre>
</div>
<div class="container">
<h2>解密</h2>
<h4>加密結果</h4>
<textarea id="ejc_encrypttext" placeholder="貼上加密結果"></textarea>
<h4>密碼</h4>
<input type="password" id="ejc_decryptpw" placeholder="輸入密碼">
<button type="button" id="ejc_decrypt">解密</button>
<h4>解密結果</h4>
<textarea id="ejc_decryptresult" readonly placeholder="解密結果將顯示在這裡"></textarea>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sjcl/1.0.8/sjcl.min.js"></script>
<script src="script.js"></script>
</body>
</html>
代码: 全选
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
color: #333;
}
h1 {
text-align: center;
color: #0056b3;
}
h2 {
color: #0056b3;
border-bottom: 2px solid #0056b3;
padding-bottom: 5px;
margin-top: 20px;
}
h3 {
color: #0056b3;
margin-top: 15px;
}
h4 {
color: #555;
margin-top: 10px;
}
.container {
background-color: #fff;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
textarea, input[type="password"] {
width: calc(100% - 20px);
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 16px;
}
textarea {
min-height: 100px;
resize: vertical;
}
button {
background-color: #007bff;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
width: 100%;
box-sizing: border-box;
margin-top: 10px;
}
button:hover {
background-color: #0056b3;
}
pre {
background-color: #e9e9e9;
padding: 10px;
border-radius: 4px;
overflow-x: auto; /* Allow horizontal scrolling for long lines */
white-space: pre-wrap; /* Force wrapping for pre content */
word-wrap: break-word; /* Break long words */
margin-bottom: 10px;
cursor: pointer;
font-size: 14px;
}
pre:hover {
background-color: #dcdcdc;
}
hr {
border: 0;
border-top: 1px dashed #ccc;
margin: 20px 0;
}
/* Mobile responsiveness */
@media (max-width: 600px) {
body {
padding: 10px;
}
.container {
padding: 15px;
}
textarea, input[type="password"], button {
font-size: 14px;
}
}
代码: 全选
document.addEventListener('DOMContentLoaded', () => {
const ejcText = document.getElementById('ejc_text');
const ejcEncryptPw = document.getElementById('ejc_encryptpw');
const ejcEncryptBtn = document.getElementById('ejc_encrypt');
const ejcAesEcb = document.getElementById('ejc_aes_ecb');
const ejcAesCbc = document.getElementById('ejc_aes_cbc');
const ejcAesCfb = document.getElementById('ejc_aes_cfb');
const ejcAesCtr = document.getElementById('ejc_aes_ctr');
const ejcAesOfb = document.getElementById('ejc_aes_ofb');
const sjclModeCbc = document.getElementById('sjcl_mode_cbc');
const sjclModeCcm = document.getElementById('sjcl_mode_ccm');
const sjclModeCtr = document.getElementById('sjcl_mode_ctr');
const sjclModeGcm = document.getElementById('sjcl_mode_gcm');
const ejcEncryptText = document.getElementById('ejc_encrypttext');
const ejcDecryptPw = document.getElementById('ejc_decryptpw');
const ejcDecryptBtn = document.getElementById('ejc_decrypt');
const ejcDecryptResult = document.getElementById('ejc_decryptresult');
// --- 加密功能 ---
ejcEncryptBtn.addEventListener('click', () => {
const plaintext = ejcText.value;
const password = ejcEncryptPw.value;
if (!plaintext || !password) {
alert('請輸入明文和密碼!');
return;
}
// Crypto.js 加密
try {
const cryptoAesEcb = CryptoJS.AES.encrypt(plaintext, password, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 });
ejcAesEcb.textContent = JSON.stringify({
ct: cryptoAesEcb.ciphertext.toString(CryptoJS.enc.Base64),
salt: cryptoAesEcb.salt ? cryptoAesEcb.salt.toString(CryptoJS.enc.Base64) : '',
iv: cryptoAesEcb.iv ? cryptoAesEcb.iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': '', // ECB typically doesn't use HMAC for direct encryption
config: { mode: 'ECB', padding: 'Pkcs7', iterations: 1 }
}, null, 2);
const cryptoAesCbc = CryptoJS.AES.encrypt(plaintext, password, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
ejcAesCbc.textContent = JSON.stringify({
ct: cryptoAesCbc.ciphertext.toString(CryptoJS.enc.Base64),
salt: cryptoAesCbc.salt ? cryptoAesCbc.salt.toString(CryptoJS.enc.Base64) : '',
iv: cryptoAesCbc.iv ? cryptoAesCbc.iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': '',
config: { mode: 'CBC', padding: 'Pkcs7', iterations: 1 }
}, null, 2);
const cryptoAesCfb = CryptoJS.AES.encrypt(plaintext, password, { mode: CryptoJS.mode.CFB, padding: CryptoJS.pad.NoPadding }); // CFB often uses NoPadding
ejcAesCfb.textContent = JSON.stringify({
ct: cryptoAesCfb.ciphertext.toString(CryptoJS.enc.Base64),
salt: cryptoAesCfb.salt ? cryptoAesCfb.salt.toString(CryptoJS.enc.Base64) : '',
iv: cryptoAesCfb.iv ? cryptoAesCfb.iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': '',
config: { mode: 'CFB', padding: 'NoPadding', iterations: 1 }
}, null, 2);
const cryptoAesCtr = CryptoJS.AES.encrypt(plaintext, password, { mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding }); // CTR often uses NoPadding
ejcAesCtr.textContent = JSON.stringify({
ct: cryptoAesCtr.ciphertext.toString(CryptoJS.enc.Base64),
salt: cryptoAesCtr.salt ? cryptoAesCtr.salt.toString(CryptoJS.enc.Base64) : '',
iv: cryptoAesCtr.iv ? cryptoAesCtr.iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': '',
config: { mode: 'CTR', padding: 'NoPadding', iterations: 1 }
}, null, 2);
const cryptoAesOfb = CryptoJS.AES.encrypt(plaintext, password, { mode: CryptoJS.mode.OFB, padding: CryptoJS.pad.NoPadding }); // OFB often uses NoPadding
ejcAesOfb.textContent = JSON.stringify({
ct: cryptoAesOfb.ciphertext.toString(CryptoJS.enc.Base64),
salt: cryptoAesOfb.salt ? cryptoAesOfb.salt.toString(CryptoJS.enc.Base64) : '',
iv: cryptoAesOfb.iv ? cryptoAesOfb.iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': '',
config: { mode: 'OFB', padding: 'NoPadding', iterations: 1 }
}, null, 2);
} catch (e) {
console.error('Crypto.js 加密失敗:', e);
alert('Crypto.js 加密失敗,請檢查輸入或控制台。');
}
// SJCL 加密
try {
// SJCL 預設為 GCM 模式,且其加密結果包含所有必要資訊
const sjclOptions = {
v: 1, // Version
iter: 1000, // Iterations for PBKDF2
ks: 128, // Key size in bits
ts: 64, // Tag size in bits
adata: "", // Authenticated data
cipher: "aes" // Cipher name
};
const sjclCbcResult = sjcl.encrypt(password, plaintext, { ...sjclOptions, mode: 'cbc' });
sjclModeCbc.textContent = sjclCbcResult;
const sjclCcmResult = sjcl.encrypt(password, plaintext, { ...sjclOptions, mode: 'ccm' });
sjclModeCcm.textContent = sjclCcmResult;
const sjclCtrResult = sjcl.encrypt(password, plaintext, { ...sjclOptions, mode: 'ctr' });
sjclModeCtr.textContent = sjclCtrResult;
const sjclGcmResult = sjcl.encrypt(password, plaintext, { ...sjclOptions, mode: 'gcm' });
sjclModeGcm.textContent = sjclGcmResult;
} catch (e) {
console.error('SJCL 加密失敗:', e);
alert('SJCL 加密失敗,請檢查輸入或控制台。');
}
});
// --- 點擊 pre 將內容複製到解密區 ---
const preElements = document.querySelectorAll('pre.breakable');
preElements.forEach(pre => {
pre.addEventListener('click', () => {
ejcEncryptText.value = pre.textContent;
ejcEncryptText.focus(); // 讓使用者知道內容已被貼上
});
});
// --- 解密功能 ---
ejcDecryptBtn.addEventListener('click', () => {
const encryptedText = ejcEncryptText.value;
const password = ejcDecryptPw.value;
if (!encryptedText || !password) {
alert('請輸入加密結果和密碼!');
return;
}
let decryptedResult = '';
try {
const parsedData = JSON.parse(encryptedText);
// 判斷是 Crypto.js 還是 SJCL 的加密結果
if (parsedData.config && parsedData.config.mode) {
// Crypto.js 解密
const decrypted = CryptoJS.AES.decrypt(encryptedText, password);
decryptedResult = decrypted.toString(CryptoJS.enc.Utf8);
} else if (parsedData.iv && parsedData.salt && parsedData.ct && parsedData.cipher === 'aes') {
// SJCL 解密
decryptedResult = sjcl.decrypt(password, encryptedText);
} else {
alert('無法識別加密結果的格式,請確認!');
return;
}
ejcDecryptResult.value = decryptedResult;
} catch (e) {
console.error('解密失敗:', e);
ejcDecryptResult.value = '解密失敗!請檢查密碼或加密結果的格式。';
}
});
});
代码: 全选
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加密與解密</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<script src="https://bitwiseshiftleft.github.io/sjcl/sjcl.js"></script>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 600px;
margin: 0 auto;
background-color: #f4f4f4;
}
h2, h4 {
color: #333;
}
textarea, input, button {
display: block;
width: 100%;
margin-bottom: 10px;
padding: 10px;
font-size: 16px;
}
textarea {
height: 100px;
resize: vertical;
}
input[type="password"] {
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
pre {
background-color: #fff;
border: 1px solid #ddd;
padding: 10px;
white-space: pre-wrap;
word-wrap: break-word;
cursor: pointer;
margin-bottom: 10px;
border-radius: 4px;
}
pre:hover {
background-color: #f0f0f0;
}
@media (max-width: 600px) {
body {
padding: 10px;
}
textarea, input, button {
font-size: 14px;
}
}
</style>
</head>
<body>
<h2>加密</h2>
<h4>輸入明文與密碼</h4>
<textarea id="ejc_text" placeholder="輸入明文"></textarea>
<input id="ejc_encryptpw" type="password" placeholder="輸入加密密碼">
<button type="button" id="ejc_encrypt">加密</button>
<h4>CryptoJS AES 加密結果</h4>
<pre id="ejc_aes_ecb">ECB 模式結果</pre>
<pre id="ejc_aes_cbc">CBC 模式結果</pre>
<pre id="ejc_aes_cfb">CFB 模式結果</pre>
<pre id="ejc_aes_ctr">CTR 模式結果</pre>
<pre id="ejc_aes_ofb">OFB 模式結果</pre>
<h4>SJCL AES 加密結果</h4>
<pre id="sjcl_mode_cbc">CBC 模式結果</pre>
<pre id="sjcl_mode_ccm">CCM 模式結果</pre>
<pre id="sjcl_mode_ctr">CTR 模式結果</pre>
<pre id="sjcl_mode_gcm">GCM 模式結果</pre>
<h2>解密</h2>
<textarea id="ejc_encrypttext" placeholder="輸入加密結果(JSON格式)"></textarea>
<input id="ejc_decryptpw" type="password" placeholder="輸入解密密碼">
<button type="button" id="ejc_decrypt">解密</button>
<textarea id="ejc_decryptresult" placeholder="解密結果" readonly></textarea>
<script>
// CryptoJS 模式映射
const cryptoModes = {
ecb: CryptoJS.mode.ECB,
cbc: CryptoJS.mode.CBC,
cfb: CryptoJS.mode.CFB,
ctr: CryptoJS.mode.CTR,
ofb: CryptoJS.mode.OFB
};
// 加密按鈕事件
document.getElementById('ejc_encrypt').addEventListener('click', () => {
const text = document.getElementById('ejc_text').value;
const password = document.getElementById('ejc_encryptpw').value;
if (!text || !password) {
alert('請輸入明文和密碼');
return;
}
// CryptoJS 加密
for (const [modeName, mode] of Object.entries(cryptoModes)) {
const salt = CryptoJS.lib.WordArray.random(16);
const iv = modeName !== 'ecb' ? CryptoJS.lib.WordArray.random(16) : '';
const key = CryptoJS.PBKDF2(password, salt, { keySize: 256/32, iterations: 1000 });
const encrypted = CryptoJS.AES.encrypt(text, key, {
iv: iv,
mode: mode,
padding: CryptoJS.pad.Pkcs7
});
const hmac = CryptoJS.HmacSHA256(encrypted.ciphertext, key).toString(CryptoJS.enc.Base64);
const result = {
ct: encrypted.ciphertext.toString(CryptoJS.enc.Base64),
salt: salt.toString(CryptoJS.enc.Base64),
iv: iv ? iv.toString(CryptoJS.enc.Base64) : '',
'hmac-sha256': hmac,
config: {
mode: modeName.toUpperCase(),
padding: 'Pkcs7',
iterations: 1000,
keySize: 256
}
};
document.getElementById(`ejc_aes_${modeName}`).textContent = JSON.stringify(result, null, 2);
}
// SJCL 加密
const sjclModes = ['cbc', 'ccm', 'ctr', 'gcm'];
for (const mode of sjclModes) {
const encrypted = sjcl.encrypt(password, text, { mode: mode, iter: 1000, ks: 128 });
document.getElementById(`sjcl_mode_${mode}`).textContent = JSON.stringify(JSON.parse(encrypted), null, 2);
}
});
// 點擊 pre 填充加密結果
document.querySelectorAll('pre').forEach(pre => {
pre.addEventListener('click', () => {
document.getElementById('ejc_encrypttext').value = pre.textContent;
});
});
// 解密按鈕事件
document.getElementById('ejc_decrypt').addEventListener('click', () => {
const encryptedText = document.getElementById('ejc_encrypttext').value;
const password = document.getElementById('ejc_decryptpw').value;
const resultArea = document.getElementById('ejc_decryptresult');
if (!encryptedText || !password) {
resultArea.value = '請輸入加密結果和密碼';
return;
}
try {
const data = JSON.parse(encryptedText);
// 檢查是否為 SJCL 格式
if ('v' in data && 'cipher' in data && data.cipher === 'aes') {
try {
const decrypted = sjcl.decrypt(password, JSON.stringify(data));
resultArea.value = decrypted;
} catch (e) {
resultArea.value = 'SJCL 解密失敗:' + e.message;
}
return;
}
// 假設為 CryptoJS 格式
const { ct, salt, iv, config } = data;
const key = CryptoJS.PBKDF2(password, CryptoJS.enc.Base64.parse(salt), {
keySize: config.keySize/32,
iterations: config.iterations
});
const decrypted = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(ct) },
key,
{
iv: iv ? CryptoJS.enc.Base64.parse(iv) : undefined,
mode: cryptoModes[config.mode.toLowerCase()],
padding: CryptoJS.pad.Pkcs7
}
);
const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
if (!decryptedText) {
resultArea.value = 'CryptoJS 解密失敗:密碼錯誤或數據損壞';
} else {
resultArea.value = decryptedText;
}
} catch (e) {
resultArea.value = '解密失敗:無效的 JSON 格式或數據';
}
});
</script>
</body>
</html>