進階
NIP-04 加密私訊
深入了解 Nostr 的加密直接訊息規範,ECDH 密鑰交換和 AES-256-CBC 加密。
15 分鐘
NIP-04 概述
NIP-04 定義了 Nostr 中的加密直接訊息(Encrypted Direct Messages)格式。 它使用 ECDH(橢圓曲線 Diffie-Hellman)密鑰交換生成共享密鑰, 然後用 AES-256-CBC 加密訊息內容。
安全警告: NIP-04 存在一些已知的安全限制,包括缺乏前向保密和元數據洩露。 NIP-44 是更安全的替代方案,但 NIP-04 仍被廣泛使用。
事件結構
{
"id": "...",
"pubkey": "<發送者公鑰>",
"created_at": 1234567890,
"kind": 4, // 加密 DM 類型
"tags": [
["p", "<接收者公鑰>"] // 必須標記接收者
],
"content": "<加密內容>?iv=<初始向量>",
"sig": "..."
} 加密流程
1
ECDH 密鑰交換
使用發送者私鑰和接收者公鑰計算共享密鑰:shared = ECDH(私鑰_A, 公鑰_B)
2
生成 AES 密鑰
取共享點的 x 座標作為 AES-256 密鑰
3
生成隨機 IV
生成 16 bytes 的隨機初始向量
4
AES-CBC 加密
使用 AES-256-CBC 模式加密訊息內容
5
組合輸出
將加密內容和 IV 用 Base64 編碼,格式:<encrypted>?iv=<iv>
程式碼範例
加密訊息
import * as secp256k1 from '@noble/secp256k1'
import { randomBytes, createCipheriv } from 'crypto'
function encrypt(privateKey, recipientPubkey, message) {
// 1. ECDH 計算共享密鑰
const sharedPoint = secp256k1.getSharedSecret(
privateKey,
'02' + recipientPubkey // 壓縮公鑰格式
)
const sharedKey = sharedPoint.slice(1, 33) // 取 x 座標
// 2. 生成隨機 IV
const iv = randomBytes(16)
// 3. AES-256-CBC 加密
const cipher = createCipheriv('aes-256-cbc', sharedKey, iv)
let encrypted = cipher.update(message, 'utf8', 'base64')
encrypted += cipher.final('base64')
// 4. 組合輸出
return encrypted + '?iv=' + iv.toString('base64')
} 解密訊息
import { createDecipheriv } from 'crypto'
function decrypt(privateKey, senderPubkey, content) {
// 1. 解析加密內容和 IV
const [encrypted, ivParam] = content.split('?iv=')
const iv = Buffer.from(ivParam, 'base64')
// 2. ECDH 計算共享密鑰(與加密相同)
const sharedPoint = secp256k1.getSharedSecret(
privateKey,
'02' + senderPubkey
)
const sharedKey = sharedPoint.slice(1, 33)
// 3. AES-256-CBC 解密
const decipher = createDecipheriv('aes-256-cbc', sharedKey, iv)
let decrypted = decipher.update(encrypted, 'base64', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
} 安全特性
優點
- • 端到端加密
- • 無需密鑰交換儀式
- • 中繼器無法讀取內容
- • 實現簡單
限制
- • 無前向保密(PFS)
- • 元數據可見(誰和誰通訊)
- • 無訊息認證(MAC)
- • 重放攻擊風險
ECDH 原理
ECDH 允許兩方在不交換私鑰的情況下建立共享密鑰:
# Alice 和 Bob 各有密鑰對
Alice: 私鑰 a, 公鑰 A = a * G
Bob: 私鑰 b, 公鑰 B = b * G
# 計算共享密鑰
Alice: shared = a * B = a * (b * G) = ab * G
Bob: shared = b * A = b * (a * G) = ab * G
# 相同的共享點! NIP-44:更安全的替代
NIP-44 解決了 NIP-04 的多個安全問題:
| 特性 | NIP-04 | NIP-44 |
|---|---|---|
| 加密算法 | AES-256-CBC | XChaCha20-Poly1305 |
| 訊息認證 | 無 | 有(AEAD) |
| 填充 | PKCS7 | 固定長度 |
| 版本控制 | 無 | 有 |
查詢私訊
# 查詢發送給我的私訊
["REQ", "my-dms", {
"kinds": [4],
"#p": ["<my_pubkey>"]
}]
# 查詢我發送的私訊
["REQ", "sent-dms", {
"kinds": [4],
"authors": ["<my_pubkey>"]
}]
# 查詢與特定用戶的對話
["REQ", "convo", {
"kinds": [4],
"authors": ["<my_pubkey>", "<their_pubkey>"],
"#p": ["<my_pubkey>", "<their_pubkey>"]
}] 最佳實踐: 對於敏感通訊,考慮使用支持 NIP-44 的客戶端。 同時,定期更換密鑰可以降低長期密鑰洩露的風險。
已複製連結