Maple楓
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Nuxt3 前端加密 如何在 Nuxt3 中實作 RSA 加密功能,保護敏感資料傳輸安全。 --- ## 常見加密方法 ### 1. MD5 加密 - **特性**: 單向雜湊函數,無法解密 - **用途**: 密碼雜湊、資料完整性驗證 - **安全性**: ⚠️ 不建議用於密碼儲存 (易被暴力破解) ### 2. Base64 編碼 - **特性**: 編碼而非加密,可輕易解碼 - **用途**: 資料傳輸、二進制資料轉文字 - **安全性**: ❌ 無安全性,僅用於編碼 ### 3. RSA 加密 (推薦) - **特性**: 非對稱加密,使用公鑰/私鑰對 - **用途**: 敏感資料加密傳輸 - **安全性**: ✅ 高安全性 --- ## RSA 加密原理 ### 單向加密 ``` 前端: 公鑰加密 → 傳送到後端 → 後端: 私鑰解密 ``` - 公鑰可以公開 - 私鑰必須保密 - 只能用私鑰解密 ### 雙向加密 (更安全) ``` 需要 2 組密鑰對 前端: - 第一組公鑰加密 (發送資料) - 第二組私鑰解密 (接收資料) 後端: - 第二組公鑰加密 (發送資料) - 第一組私鑰解密 (接收資料) ``` **優勢**: 即使前端私鑰洩漏,攻擊者也無法解密後端傳來的資料 --- ## 實作方式比較 | 方式 | 優點 | 缺點 | 建議使用場景 | |------|------|------|-------------| | **CDN** | 不需安裝套件,減少打包大小 | 依賴外部 CDN,可能有載入延遲 | 專案打包時遇到問題 | | **NPM** | 本地引入,不依賴外部資源 | 需要額外配置 transpile | 正式專案,穩定性要求高 | --- ## 方法一: 使用 CDN (簡單快速) ### 檔案結構 ``` project/ ├── composables/ │ └── useJsencrypt.js # 加密解密邏輯 ├── pages/ │ └── encryption.vue # 示範頁面 ├── .env # 環境變數 (公私鑰) └── nuxt.config.ts # Nuxt 配置 ``` --- ### 1. 安裝與配置 **nuxt.config.ts** ```typescript export default defineNuxtConfig({ app: { head: { script: [ { src: 'https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.3.2/jsencrypt.min.js', defer: true } ] } }, runtimeConfig: { public: { // 公鑰 (用於加密) jsencryptPublicKey: process.env.NUXT_PUBLIC_JSENCRYPT_PUBLIC_KEY || '', // 私鑰 (用於解密) jsencryptPrivateKey: process.env.NUXT_PUBLIC_JSENCRYPT_PRIVATE_KEY || '' } } }) ``` --- ### 2. 環境變數設定 **.env** ```bash # 公鑰 (Public Key) - 用於加密 NUXT_PUBLIC_JSENCRYPT_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKXtONNNt3fYEFhpA7231EN4Ww UNdXWBigUYCqoxCyfP/zfH+8GhnUFknmiiXTkQqGhIVXdan+cNk5Gq/ji7IAYWH BquHUgf5N7N2Hi7fYH7z+tAiRPMaIQxsH1Bo6zHW4+a+yB4pioFiKmdsKdzaOZi BhcWZeO+uAeLJQgfpRpwIDAQAB -----END PUBLIC KEY-----" # 私鑰 (Private Key) - 用於解密 NUXT_PUBLIC_JSENCRYPT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCKXtONNNt3fYEFhpA7231EN4WwUNdXWBigUYCqoxCyfP/zfH+8 GhnUFknmiiXTkQqGhIVXdan+cNk5Gq/ji7IAYWHBquHUgf5N7N2Hi7fYH7z+tAiR PMaIQxsH1Bo6zHW4+a+yB4pioFiKmdsKdzaOZiBhcWZeO+uAeLJQgfpRpwIDAQAB AoGAIVgioMeZD5115w/7WAFXmYXLuKZyjkDThma9m+E519lZkKJy4bBkgwBwBJdL 8ETmhW4P9/iJ45/sKN+ufSYf2a6qygrjjk27087ViEL9TWDtReN1i1izvPuT12x1 sR50YE0Zokl+ZOSHl3lOZuAWgmBgYnoj2XfYlDXf63tPVGECQQDKZP8s/JYbSrii DZJypEZ7L6lrNZsOaz2ljWOUqfwXc1ucazOAGV24xUpQsZJj1oNRRWIoKRp8Xdly abvyXZy3AkEArwTJypcT0EXyttxbNe/SDnSuYJznyGmPpcIcx0H9P/eSr8KEvD3f CjlFXG/2TxoKzsjksZNxG6PTj71huT3ikQJAQmTtXNmjeGKDvQ2MvDTtth2Fi1jz e8BsYbHHOA6nVx4NsHtuUph/qUx3O491AXFudKu5LomFWcUDv0e2UySocwJAOClL L41HOGAZwH/5bNdmCml6w1nWLsNg6wnc/ju4rlwdX/UFbvpTpg42qbDr0vpCWZSx fiIX+4yYiNl2kGDBYQJAYr+d8az1RxinmFEzMuPvkfcXrUyC/3Hn2/eJZQAgBjYe uBXDhMlQUfCEfyQ/tLfDtSOsCwTjn0C/JnQsLxjtZg== -----END RSA PRIVATE KEY-----" ``` **⚠️ 重要提醒:** - 請替換為你自己產生的密鑰對 - 私鑰絕對不要提交到版本控制系統 - 將 `.env` 加入 `.gitignore` --- ### 3. Composable 實作 **composables/useJsencrypt.js** ```javascript export const useJsencrypt = () => { // 取得配置 const config = useRuntimeConfig() /** * 加密資料 * @param {string} data - 要加密的資料 * @returns {string|false} 加密後的字串,失敗返回 false */ const encryptData = (data) => { if (!process.client) return null if (!data) return null try { // 創建 JSEncrypt 實例 const encrypt = new JSEncrypt() // 設置公鑰 const publicKey = config.public.jsencryptPublicKey if (!publicKey) { console.error('未設定公鑰') return null } encrypt.setPublicKey(publicKey) // 使用公鑰加密 const encryptedData = encrypt.encrypt(data) if (!encryptedData) { console.error('加密失敗') return null } return encryptedData } catch (error) { console.error('加密過程發生錯誤:', error) return null } } /** * 解密資料 * @param {string} data - 要解密的資料 * @returns {string|false} 解密後的字串,失敗返回 false */ const decryptData = (data) => { if (!process.client) return null if (!data) return null try { // 創建 JSEncrypt 實例 const decrypt = new JSEncrypt() // 設置私鑰 const privateKey = config.public.jsencryptPrivateKey if (!privateKey) { console.error('未設定私鑰') return null } decrypt.setPrivateKey(privateKey) // 使用私鑰解密 const decryptedData = decrypt.decrypt(data) if (!decryptedData) { console.error('解密失敗') return null } return decryptedData } catch (error) { console.error('解密過程發生錯誤:', error) return null } } return { encryptData, decryptData } } ``` --- ### 4. 頁面使用範例 **pages/encryption.vue** ```vue <template> <div class="encryption-demo"> <h2>RSA 加密示範</h2> <!-- 顯示區域 --> <div class="result-box"> <h3>{{ isEncrypted ? '加密結果' : '原始內容' }}</h3> <div class="result-content"> {{ displayText }} </div> </div> <!-- 控制按鈕 --> <div class="button-group"> <button @click="handleEncrypt" :disabled="isEncrypted" class="btn btn-primary" > 🔒 加密 </button> <button @click="handleDecrypt" :disabled="!isEncrypted" class="btn btn-secondary" > 🔓 解密 </button> <button @click="handleReset" class="btn btn-outline" > 🔄 重置 </button> </div> <!-- 狀態提示 --> <div v-if="statusMessage" class="status-message" :class="statusType"> {{ statusMessage }} </div> </div> </template> <script setup> // 引入加密功能 const { encryptData, decryptData } = useJsencrypt() // 原始資料 const originalText = ref('This is a secret message! 這是一段機密訊息!') // 顯示的文字 const displayText = ref(originalText.value) // 是否已加密 const isEncrypted = ref(false) // 狀態訊息 const statusMessage = ref('') const statusType = ref('') // 'success' | 'error' /** * 顯示狀態訊息 */ const showStatus = (message, type = 'success') => { statusMessage.value = message statusType.value = type // 3 秒後清除訊息 setTimeout(() => { statusMessage.value = '' statusType.value = '' }, 3000) } /** * 加密處理 */ const handleEncrypt = () => { const encrypted = encryptData(originalText.value) if (encrypted) { displayText.value = encrypted isEncrypted.value = true showStatus('✅ 加密成功', 'success') } else { showStatus('❌ 加密失敗,請檢查公鑰設定', 'error') } } /** * 解密處理 */ const handleDecrypt = () => { const decrypted = decryptData(displayText.value) if (decrypted) { displayText.value = decrypted isEncrypted.value = false showStatus('✅ 解密成功', 'success') } else { showStatus('❌ 解密失敗,請檢查私鑰設定', 'error') } } /** * 重置 */ const handleReset = () => { displayText.value = originalText.value isEncrypted.value = false showStatus('🔄 已重置', 'success') } </script> <style scoped> .encryption-demo { max-width: 800px; margin: 2rem auto; padding: 2rem; } h2 { text-align: center; margin-bottom: 2rem; color: #2c3e50; } .result-box { background: #f8f9fa; border: 2px solid #dee2e6; border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; } .result-box h3 { margin-top: 0; margin-bottom: 1rem; color: #495057; font-size: 1rem; } .result-content { min-height: 150px; padding: 1rem; background: white; border-radius: 4px; word-break: break-all; font-family: 'Courier New', monospace; font-size: 0.9rem; line-height: 1.6; color: #212529; } .button-group { display: flex; gap: 1rem; justify-content: center; margin-bottom: 1rem; } .btn { padding: 0.75rem 1.5rem; border: none; border-radius: 6px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; } .btn:disabled { opacity: 0.5; cursor: not-allowed; } .btn-primary { background: #007bff; color: white; } .btn-primary:hover:not(:disabled) { background: #0056b3; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3); } .btn-secondary { background: #6c757d; color: white; } .btn-secondary:hover:not(:disabled) { background: #545b62; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3); } .btn-outline { background: white; color: #6c757d; border: 2px solid #6c757d; } .btn-outline:hover { background: #6c757d; color: white; transform: translateY(-2px); } .status-message { text-align: center; padding: 1rem; border-radius: 6px; font-weight: 500; } .status-message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .status-message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } </style> ``` --- ## 方法二: 使用 NPM 安裝 (推薦正式專案) ### 1. 安裝套件 ```bash npm install jsencrypt ``` --- ### 2. 檔案結構 ``` project/ ├── plugins/ │ └── jsencrypt.client.js # Plugin 封裝 ├── pages/ │ └── encryption.vue # 示範頁面 ├── .env # 環境變數 └── nuxt.config.ts # Nuxt 配置 ``` --- ### 3. 配置 Nuxt **nuxt.config.ts** ```typescript export default defineNuxtConfig({ build: { // 重要: 需要 transpile jsencrypt transpile: ['jsencrypt'] }, runtimeConfig: { public: { jsencryptPublicKey: process.env.NUXT_PUBLIC_JSENCRYPT_PUBLIC_KEY || '', jsencryptPrivateKey: process.env.NUXT_PUBLIC_JSENCRYPT_PRIVATE_KEY || '' } } }) ``` --- ### 4. Plugin 封裝 **plugins/jsencrypt.client.js** ```javascript import JSEncrypt from 'jsencrypt' export default defineNuxtPlugin(() => { const config = useRuntimeConfig() /** * 加密資料 */ const encryptData = (data) => { if (!data) return null try { const encrypt = new JSEncrypt() const publicKey = config.public.jsencryptPublicKey if (!publicKey) { console.error('未設定公鑰') return null } encrypt.setPublicKey(publicKey) const encryptedData = encrypt.encrypt(data) if (!encryptedData) { console.error('加密失敗') return null } return encryptedData } catch (error) { console.error('加密錯誤:', error) return null } } /** * 解密資料 */ const decryptData = (data) => { if (!data) return null try { const decrypt = new JSEncrypt() const privateKey = config.public.jsencryptPrivateKey if (!privateKey) { console.error('未設定私鑰') return null } decrypt.setPrivateKey(privateKey) const decryptedData = decrypt.decrypt(data) if (!decryptedData) { console.error('解密失敗') return null } return decryptedData } catch (error) { console.error('解密錯誤:', error) return null } } // 提供給全域使用 return { provide: { encryptData, decryptData } } }) ``` --- ### 5. 頁面使用 **pages/encryption.vue** ```vue <script setup> // 從 plugin 取得加密方法 const { $encryptData, $decryptData } = useNuxtApp() const originalText = ref('This is a secret message!') const displayText = ref(originalText.value) const isEncrypted = ref(false) const handleEncrypt = () => { const encrypted = $encryptData(originalText.value) if (encrypted) { displayText.value = encrypted isEncrypted.value = true } } const handleDecrypt = () => { const decrypted = $decryptData(displayText.value) if (decrypted) { displayText.value = decrypted isEncrypted.value = false } } </script> <template> <!-- 與方法一相同的 template --> </template> ``` --- ## 產生 RSA 密鑰對 ### 線上工具 (快速) - [travistidwell.com/jsencrypt/demo](http://travistidwell.com/jsencrypt/demo/) - [cryptotools.net/rsagen](https://cryptotools.net/rsagen) ### 使用 OpenSSL (推薦) ```bash # 1. 產生私鑰 openssl genrsa -out private_key.pem 1024 # 2. 從私鑰產生公鑰 openssl rsa -in private_key.pem -pubout -out public_key.pem # 3. 查看私鑰內容 cat private_key.pem # 4. 查看公鑰內容 cat public_key.pem ``` **安全建議:** - 生產環境使用 2048 位元以上: `openssl genrsa -out private_key.pem 2048` - 私鑰務必妥善保管,不要提交到版本控制 - 定期更換密鑰對 --- ## 實際應用場景 ### 1. 登入密碼加密 ```vue <script setup> const { encryptData } = useJsencrypt() const login = async (username, password) => { // 加密密碼 const encryptedPassword = encryptData(password) // 發送到後端 await $fetch('/api/login', { method: 'POST', body: { username, password: encryptedPassword } }) } </script> ``` --- ### 2. 敏感資料傳輸 ```vue <script setup> const { encryptData } = useJsencrypt() const submitCreditCard = async (cardNumber) => { // 加密信用卡號 const encryptedCard = encryptData(cardNumber) await $fetch('/api/payment', { method: 'POST', body: { card: encryptedCard } }) } </script> ``` --- ### 3. 雙向加密通訊 ```javascript // composables/useSecureApi.js export const useSecureApi = () => { const { encryptData, decryptData } = useJsencrypt() const securePost = async (url, data) => { // 加密請求資料 const encryptedData = encryptData(JSON.stringify(data)) // 發送請求 const response = await $fetch(url, { method: 'POST', body: { data: encryptedData } }) // 解密回應資料 const decryptedResponse = decryptData(response.data) return JSON.parse(decryptedResponse) } return { securePost } } ``` --- ## 常見問題 ### Q: Build 時出現 `Cannot find module 'jsencrypt'` 錯誤? A: 需要在 `nuxt.config.ts` 中設定 transpile: ```typescript export default defineNuxtConfig({ build: { transpile: ['jsencrypt'] } }) ``` --- ### Q: 為什麼私鑰會暴露在前端? A: - **單向加密**: 前端只需要公鑰加密,私鑰在後端解密 - **雙向加密**: 前端確實需要私鑰,但這是用來解密後端傳來的資料,無法解密前端發出的資料 (使用不同密鑰對) **正確做法:** ``` 前端發送: 後端公鑰加密 → 後端私鑰解密 後端發送: 前端公鑰加密 → 前端私鑰解密 ``` --- ### Q: RSA 加密的資料長度有限制嗎? A: 有的!RSA 只能加密較短的資料。 **限制:** - 1024 位元密鑰: 最多 117 bytes - 2048 位元密鑰: 最多 245 bytes **解決方案:** 1. 分段加密長資料 2. 使用 RSA 加密對稱密鑰 (如 AES),再用對稱密鑰加密資料 (混合加密) --- ### Q: CDN 載入失敗怎麼辦? A: 可以下載 jsencrypt.min.js 放在 `public/js/` 目錄: ```typescript // nuxt.config.ts export default defineNuxtConfig({ app: { head: { script: [ { src: '/js/jsencrypt.min.js', defer: true } ] } } }) ``` --- ### Q: 如何驗證加密解密是否正常? A: 可以在瀏覽器 Console 測試: ```javascript // 1. 加密 const encrypted = $encryptData('test message') console.log('加密結果:', encrypted) // 2. 解密 const decrypted = $decryptData(encrypted) console.log('解密結果:', decrypted) // 應該是 'test message' ``` --- ## 安全建議 ### ✅ 應該做的 1. **使用 HTTPS**: RSA 加密只是多一層保護,仍需 HTTPS 2. **密鑰管理**: 私鑰不要硬編碼,使用環境變數 3. **定期更換**: 定期更換密鑰對 4. **長度足夠**: 生產環境使用 2048 位元以上 5. **錯誤處理**: 加密失敗時要有適當處理 ### ❌ 不應該做的 1. **不要單獨使用**: 不能取代 HTTPS 2. **不要儲存私鑰**: Git 中不要提交私鑰 3. **不要過度依賴**: 前端加密容易被繞過,後端仍需驗證 4. **不要加密大檔案**: RSA 不適合大量資料加密 5. **不要重複使用**: 不同專案使用不同密鑰對 --- ## 效能優化 ### 1. 延遲載入 ```vue <script setup> const { encryptData, decryptData } = useJsencrypt() const isLoaded = ref(false) onMounted(() => { // 確認 JSEncrypt 已載入 if (process.client && window.JSEncrypt) { isLoaded.value = true } }) </script> ``` --- ### 2. 快取加密實例 ```javascript // composables/useJsencrypt.js let encryptInstance = null let decryptInstance = null export const useJsencrypt = () => { const config = useRuntimeConfig() const getEncryptInstance = () => { if (!encryptInstance) { encryptInstance = new JSEncrypt() encryptInstance.setPublicKey(config.public.jsencryptPublicKey) } return encryptInstance } const getDecryptInstance = () => { if (!decryptInstance) { decryptInstance = new JSEncrypt() decryptInstance.setPrivateKey(config.public.jsencryptPrivateKey) } return decryptInstance } // ... } ``` --- ## 參考資源 - [JSEncrypt 官方文檔](https://travistidwell.com/jsencrypt/) - [RSA 加密原理](https://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95) - [OpenSSL 工具](https://www.openssl.org/) - [MDN - Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) <!-- --- ## 進階主題 ### 使用 Web Crypto API (原生) 現代瀏覽器支援原生加密 API,無需額外套件: ```javascript // 產生密鑰對 const generateKeyPair = async () => { return await window.crypto.subtle.generateKey( { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, true, ['encrypt', 'decrypt'] ) } // 加密 const encrypt = async (publicKey, data) => { const encoded = new TextEncoder().encode(data) return await window.crypto.subtle.encrypt( { name: 'RSA-OAEP' }, publicKey, encoded ) } ``` **優勢:** 原生支援,效能更好,無需載入額外套件 **劣勢:** API 較複雜,瀏覽器相容性需注意 -->

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully