v2.0: 新增 ChaosCryptChat 端到端加密聊天系统 + 更新文档
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
# 运行时数据(聊天记录/账户/服务器状态)
|
||||
data/
|
||||
server_data/
|
||||
ChaosCryptChat/data/
|
||||
ChaosCryptChat/server_data/
|
||||
|
||||
# 密钥文件(严禁上传!)
|
||||
*.key
|
||||
|
||||
# Python 缓存
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# 媒体缓存
|
||||
media/
|
||||
|
||||
# 系统文件
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
desktop.ini
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,418 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
☁️ 混沌加密聊天 - 中央服务器 (解决 NAT / 公网穿透)
|
||||
====================================================
|
||||
- 服务器只负责: 用户认证 + 群成员管理 + 消息中继
|
||||
- 所有消息内容由客户端加密, 服务器【不持有群密钥】
|
||||
- 密钥只在客户端本地, 永不上传到服务器
|
||||
- 部署: 放到有公网IP的机器, python server.py [端口]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
import struct
|
||||
import socket
|
||||
import hashlib
|
||||
import threading
|
||||
import sqlite3
|
||||
|
||||
SERVER_HOST = '0.0.0.0'
|
||||
SERVER_PORT = 8666
|
||||
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'server_data', 'server.db')
|
||||
|
||||
MAX_FRAME = 256 * 1024 * 1024
|
||||
|
||||
|
||||
# ==================== 帧协议 (与客户端一致) ====================
|
||||
def send_msg(sock, obj):
|
||||
payload = json.dumps(obj, ensure_ascii=False).encode('utf-8')
|
||||
sock.sendall(struct.pack('>I', len(payload)) + payload)
|
||||
|
||||
|
||||
def recv_exact(sock, n):
|
||||
data = b''
|
||||
while len(data) < n:
|
||||
try:
|
||||
chunk = sock.recv(n - len(data))
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception:
|
||||
return None
|
||||
if not chunk:
|
||||
return None
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
|
||||
def recv_msg(sock):
|
||||
header = recv_exact(sock, 4)
|
||||
if header is None:
|
||||
return None
|
||||
length = struct.unpack('>I', header)[0]
|
||||
if length <= 0 or length > MAX_FRAME:
|
||||
return None
|
||||
payload = recv_exact(sock, length)
|
||||
if payload is None:
|
||||
return None
|
||||
try:
|
||||
return json.loads(payload.decode('utf-8'))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# ==================== 服务器数据库 ====================
|
||||
class ServerDB:
|
||||
def __init__(self, db_path=DB_PATH):
|
||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||
self.conn = sqlite3.connect(db_path, check_same_thread=False)
|
||||
self.conn.row_factory = sqlite3.Row
|
||||
self.lock = threading.Lock()
|
||||
self._init()
|
||||
|
||||
def _init(self):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS users(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
salt TEXT NOT NULL,
|
||||
pass_hash TEXT NOT NULL,
|
||||
created_at REAL)''')
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS groups(
|
||||
id TEXT PRIMARY KEY,
|
||||
owner TEXT NOT NULL,
|
||||
name TEXT,
|
||||
announcement TEXT,
|
||||
created_at REAL)''')
|
||||
cur.execute('''CREATE TABLE IF NOT EXISTS group_members(
|
||||
group_id TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
joined_at REAL,
|
||||
PRIMARY KEY(group_id, username))''')
|
||||
self.conn.commit()
|
||||
|
||||
def register(self, username, password):
|
||||
salt = os.urandom(16).hex()
|
||||
ph = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'),
|
||||
bytes.fromhex(salt), 120000).hex()
|
||||
try:
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute('INSERT INTO users(username, salt, pass_hash, created_at) VALUES(?,?,?,?)',
|
||||
(username, salt, ph, time.time()))
|
||||
self.conn.commit()
|
||||
return True, 'ok'
|
||||
except sqlite3.IntegrityError:
|
||||
return False, '该用户名已被注册'
|
||||
|
||||
def login(self, username, password):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
row = cur.execute('SELECT * FROM users WHERE username=?', (username,)).fetchone()
|
||||
if row is None:
|
||||
return None, '用户不存在'
|
||||
ph = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'),
|
||||
bytes.fromhex(row['salt']), 120000).hex()
|
||||
if ph != row['pass_hash']:
|
||||
return None, '密码错误'
|
||||
return row, 'ok'
|
||||
|
||||
def create_group(self, group_id, owner, name):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute('INSERT INTO groups(id, owner, name, created_at) VALUES(?,?,?,?)',
|
||||
(group_id, owner, name, time.time()))
|
||||
cur.execute('INSERT OR IGNORE INTO group_members(group_id, username, joined_at) VALUES(?,?,?)',
|
||||
(group_id, owner, time.time()))
|
||||
self.conn.commit()
|
||||
|
||||
def add_member(self, group_id, username):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
row = cur.execute('SELECT 1 FROM groups WHERE id=?', (group_id,)).fetchone()
|
||||
if row is None:
|
||||
return False
|
||||
cur.execute('INSERT OR IGNORE INTO group_members(group_id, username, joined_at) VALUES(?,?,?)',
|
||||
(group_id, username, time.time()))
|
||||
self.conn.commit()
|
||||
return True
|
||||
|
||||
def remove_member(self, group_id, username):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute('DELETE FROM group_members WHERE group_id=? AND username=?',
|
||||
(group_id, username))
|
||||
self.conn.commit()
|
||||
|
||||
def get_group(self, group_id):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
return cur.execute('SELECT id, name, announcement, owner FROM groups WHERE id=?',
|
||||
(group_id,)).fetchone()
|
||||
|
||||
def list_groups(self, username):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute('''SELECT g.id, g.name, g.owner, g.announcement FROM groups g
|
||||
JOIN group_members m ON m.group_id = g.id
|
||||
WHERE m.username=?''', (username,)).fetchall()
|
||||
return [{'id': r['id'], 'name': r['name'], 'owner': r['owner'],
|
||||
'announcement': r['announcement'] or ''} for r in rows]
|
||||
|
||||
def is_owner(self, group_id, username):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
row = cur.execute('SELECT owner FROM groups WHERE id=?', (group_id,)).fetchone()
|
||||
return bool(row and row['owner'] == username)
|
||||
|
||||
def set_group_name(self, group_id, name):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute('UPDATE groups SET name=? WHERE id=?', (name, group_id))
|
||||
self.conn.commit()
|
||||
|
||||
def set_announcement(self, group_id, text):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
cur.execute('UPDATE groups SET announcement=? WHERE id=?', (text, group_id))
|
||||
self.conn.commit()
|
||||
|
||||
def members(self, group_id):
|
||||
with self.lock:
|
||||
cur = self.conn.cursor()
|
||||
rows = cur.execute('SELECT username FROM group_members WHERE group_id=?',
|
||||
(group_id,)).fetchall()
|
||||
return [r['username'] for r in rows]
|
||||
|
||||
|
||||
# ==================== 中央服务器 ====================
|
||||
class CentralServer:
|
||||
def __init__(self, host=SERVER_HOST, port=SERVER_PORT, db=None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.db = db or ServerDB()
|
||||
self.sock = None
|
||||
self.running = False
|
||||
self.clients = {} # cid -> {socket, username, token, addr, lock}
|
||||
self.next_id = 1
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def start(self):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.sock.bind((self.host, self.port))
|
||||
self.sock.listen(50)
|
||||
self.running = True
|
||||
print(f"☁️ 中央服务器启动: {self.host}:{self.port}")
|
||||
print(f" 数据库: {DB_PATH}")
|
||||
threading.Thread(target=self._accept_loop, daemon=True).start()
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
if self.sock:
|
||||
try:
|
||||
self.sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
self.sock = None
|
||||
with self.lock:
|
||||
socks = [c['socket'] for c in self.clients.values()]
|
||||
self.clients.clear()
|
||||
for s in socks:
|
||||
try:
|
||||
s.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _accept_loop(self):
|
||||
while self.running:
|
||||
try:
|
||||
client, addr = self.sock.accept()
|
||||
with self.lock:
|
||||
cid = self.next_id
|
||||
self.next_id += 1
|
||||
self.clients[cid] = {'socket': client, 'username': None,
|
||||
'token': None, 'addr': addr,
|
||||
'lock': threading.Lock()}
|
||||
print(f" 📶 新连接: {addr[0]}:{addr[1]}")
|
||||
threading.Thread(target=self._handler, args=(cid,), daemon=True).start()
|
||||
except Exception:
|
||||
break
|
||||
|
||||
def _send(self, cid, msg):
|
||||
with self.lock:
|
||||
c = self.clients.get(cid)
|
||||
if c:
|
||||
try:
|
||||
with c['lock']:
|
||||
send_msg(c['socket'], msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _handler(self, cid):
|
||||
client = self.clients[cid]['socket']
|
||||
# 第一步: 认证 (register 自动登录 或 login)
|
||||
try:
|
||||
msg = recv_msg(client)
|
||||
if msg and msg.get('type') == 'register':
|
||||
username = str(msg.get('username', ''))[:24]
|
||||
password = str(msg.get('password', ''))
|
||||
ok, err = self.db.register(username, password)
|
||||
if not ok:
|
||||
self._send(cid, {'type': 'error', 'msg': err})
|
||||
self._remove(cid)
|
||||
return
|
||||
row, _ = self.db.login(username, password)
|
||||
self._auth_ok(cid, row)
|
||||
elif msg and msg.get('type') == 'login':
|
||||
username = str(msg.get('username', ''))[:24]
|
||||
password = str(msg.get('password', ''))
|
||||
row, err = self.db.login(username, password)
|
||||
if row is None:
|
||||
self._send(cid, {'type': 'error', 'msg': err})
|
||||
self._remove(cid)
|
||||
return
|
||||
self._auth_ok(cid, row)
|
||||
else:
|
||||
self._remove(cid)
|
||||
return
|
||||
except Exception:
|
||||
self._remove(cid)
|
||||
return
|
||||
|
||||
# 第二步: 业务消息循环
|
||||
while self.running:
|
||||
if cid not in self.clients:
|
||||
break
|
||||
try:
|
||||
msg = recv_msg(client)
|
||||
if msg is None:
|
||||
break
|
||||
self._handle(cid, msg)
|
||||
except Exception:
|
||||
break
|
||||
self._remove(cid)
|
||||
|
||||
def _auth_ok(self, cid, row):
|
||||
username = row['username']
|
||||
token = os.urandom(16).hex()
|
||||
with self.lock:
|
||||
if cid in self.clients:
|
||||
self.clients[cid]['username'] = username
|
||||
self.clients[cid]['token'] = token
|
||||
print(f" 👤 {username} 已登录")
|
||||
self._send(cid, {'type': 'login_ok', 'token': token, 'username': username})
|
||||
|
||||
def _handle(self, cid, msg):
|
||||
with self.lock:
|
||||
if cid not in self.clients:
|
||||
return
|
||||
username = self.clients[cid]['username']
|
||||
mtype = msg.get('type')
|
||||
|
||||
if mtype == 'create_group':
|
||||
group_id = str(msg.get('group_id') or f"g{int(time.time()*1000)}-{os.urandom(3).hex()}")
|
||||
name = str(msg.get('name', ''))[:40]
|
||||
self.db.create_group(group_id, username, name or group_id)
|
||||
print(f" 📡 {username} 创建群 [{name}] ({group_id})")
|
||||
self._send(cid, {'type': 'group_created', 'group_id': group_id, 'name': name})
|
||||
|
||||
elif mtype == 'list_groups':
|
||||
groups = self.db.list_groups(username)
|
||||
self._send(cid, {'type': 'groups', 'groups': groups})
|
||||
|
||||
elif mtype == 'join_group':
|
||||
group_id = str(msg.get('group_id', ''))
|
||||
if self.db.add_member(group_id, username):
|
||||
self._send(cid, {'type': 'join_ok', 'group_id': group_id})
|
||||
# 推送当前群名/公告给新成员
|
||||
ginfo = self.db.get_group(group_id)
|
||||
self._send(cid, {'type': 'group_info', 'group_id': group_id,
|
||||
'name': ginfo['name'] if ginfo else '',
|
||||
'announcement': ginfo['announcement'] if ginfo else ''})
|
||||
self._broadcast_group(group_id,
|
||||
{'type': 'system', 'text': f"👋 {username} 加入了群",
|
||||
'group_id': group_id}, exclude=cid)
|
||||
else:
|
||||
self._send(cid, {'type': 'error', 'msg': '群不存在'})
|
||||
|
||||
elif mtype == 'set_group_name':
|
||||
group_id = str(msg.get('group_id', ''))
|
||||
name = str(msg.get('name', ''))[:40]
|
||||
if self.db.is_owner(group_id, username):
|
||||
self.db.set_group_name(group_id, name)
|
||||
self._broadcast_group(group_id, {'type': 'group_name', 'group_id': group_id,
|
||||
'name': name}, exclude=None)
|
||||
print(f" 🏷️ {username} 修改群名: {name}")
|
||||
else:
|
||||
self._send(cid, {'type': 'error', 'msg': '仅群主可修改群名'})
|
||||
|
||||
elif mtype == 'set_announcement':
|
||||
group_id = str(msg.get('group_id', ''))
|
||||
text = str(msg.get('text', ''))[:500]
|
||||
if self.db.is_owner(group_id, username):
|
||||
self.db.set_announcement(group_id, text)
|
||||
self._broadcast_group(group_id, {'type': 'announcement', 'group_id': group_id,
|
||||
'text': text}, exclude=None)
|
||||
print(f" 📢 {username} 设置公告: {text[:30]}")
|
||||
else:
|
||||
self._send(cid, {'type': 'error', 'msg': '仅群主可设置公告'})
|
||||
|
||||
elif mtype == 'leave_group':
|
||||
group_id = str(msg.get('group_id', ''))
|
||||
self.db.remove_member(group_id, username)
|
||||
self._send(cid, {'type': 'leave_ok', 'group_id': group_id})
|
||||
self._broadcast_group(group_id,
|
||||
{'type': 'system', 'text': f"👋 {username} 离开了群",
|
||||
'group_id': group_id}, exclude=cid)
|
||||
|
||||
elif mtype == 'group_members':
|
||||
group_id = str(msg.get('group_id', ''))
|
||||
self._send(cid, {'type': 'members', 'group_id': group_id,
|
||||
'members': self.db.members(group_id)})
|
||||
|
||||
elif mtype == 'group_message':
|
||||
group_id = str(msg.get('group_id', ''))
|
||||
payload = msg.get('payload')
|
||||
# 转发给同群其他成员 (服务器不解析加密内容)
|
||||
self._broadcast_group(group_id,
|
||||
{'type': 'group_message', 'group_id': group_id,
|
||||
'from': username, 'payload': payload}, exclude=cid)
|
||||
|
||||
def _broadcast_group(self, group_id, msg, exclude=None):
|
||||
members = set(self.db.members(group_id))
|
||||
with self.lock:
|
||||
targets = [(cid2, c['socket'], c['lock']) for cid2, c in self.clients.items()
|
||||
if c.get('username') in members and cid2 != exclude]
|
||||
for cid2, sock, lk in targets:
|
||||
try:
|
||||
with lk:
|
||||
send_msg(sock, msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _remove(self, cid):
|
||||
with self.lock:
|
||||
entry = self.clients.pop(cid, None)
|
||||
if entry:
|
||||
try:
|
||||
entry['socket'].close()
|
||||
except Exception:
|
||||
pass
|
||||
if entry['username']:
|
||||
print(f" 🔌 {entry['username']} 断开")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else SERVER_PORT
|
||||
srv = CentralServer(port=port)
|
||||
srv.start()
|
||||
print("按 Ctrl+C 停止服务器")
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
srv.stop()
|
||||
print("\n☁️ 服务器已停止")
|
||||
@@ -1,4 +1,4 @@
|
||||
\# 🔐 ChaosCrypt - 混沌加密系统
|
||||
\# 🔐 ChaosCrypt - 混沌加密系统(v2.0)
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
> \*不依赖 NIST、不依赖椭圆曲线、不依赖质数分解——只依赖随机性本身\*
|
||||
|
||||
> \*\*v2.0 新增 💬 ChaosCryptChat:基于混沌加密的端到端加密聊天系统(文本/图片/文件/语音)\*\*
|
||||
|
||||
|
||||
|
||||
\---
|
||||
@@ -36,9 +38,11 @@
|
||||
|
||||
10\. \[常见问题](#常见问题)
|
||||
|
||||
11\. \[项目结构](#项目结构)
|
||||
11\. \[💬 ChaosCryptChat 混沌加密聊天](#-chaoscryptchat-混沌加密聊天)
|
||||
|
||||
12\. \[免责声明](#免责声明)
|
||||
12\. \[项目结构](#项目结构)
|
||||
|
||||
13\. \[免责声明](#免责声明)
|
||||
|
||||
|
||||
|
||||
@@ -60,6 +64,8 @@
|
||||
|
||||
> 这不是对现有加密算法的改进,而是对"安全"这个概念的重新定义。
|
||||
|
||||
\*\*v2.0 新增\*\*:在混沌加密内核之上构建了 \*\*ChaosCryptChat\*\* 端到端加密聊天系统,让混沌加密真正走进实时通信场景。
|
||||
|
||||
|
||||
|
||||
\---
|
||||
@@ -128,6 +134,8 @@ ChaosCrypt 对此表示:
|
||||
|
||||
| 🐍 \*\*纯 Python\*\* | 仅需 Python 3.6+,零额外依赖 |
|
||||
|
||||
| 💬 \*\*加密聊天(v2.0)\*\* | ChaosCryptChat 端到端加密聊天:文本/图片/文件/语音,P2P + 中央服务器双模式 |
|
||||
|
||||
|
||||
|
||||
\---
|
||||
@@ -482,6 +490,83 @@ AES 的 10⁷⁷ 在它面前就像一粒沙子对比整个银河系。
|
||||
|
||||
|
||||
|
||||
\## 💬 ChaosCryptChat 混沌加密聊天(v2.0 新增)
|
||||
|
||||
\*\*ChaosCryptChat\*\* 是基于 ChaosCrypt 混沌加密内核构建的端到端加密聊天系统。所有聊天内容(文本、图片、文件、语音)在发送前均经混沌加密,服务器与中间节点无法读取任何消息。
|
||||
|
||||
\### 核心特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| 🔗 \*\*端到端加密\*\* | 所有消息客户端本地加密,服务器只做中继,不持有群密钥 |
|
||||
| ☁️ \*\*双群模式\*\* | 🔗 端到端 P2P 直连 + ☁️ 中央服务器(解决 NAT 穿透) |
|
||||
| 🎤 \*\*语音消息\*\* | 录音 → 混沌加密 → 发送 → 解密播放 |
|
||||
| 🖼️ \*\*图片/文件\*\* | 二进制数据混沌加密传输,支持任意文件类型 |
|
||||
| 👥 \*\*群管理\*\* | 创建群、加入群、群名、公告、踢人、群主审批 |
|
||||
| 🚫 \*\*消息撤回\*\* | 2 分钟内可撤回自己的消息 |
|
||||
| 🔑 \*\*每用户密钥\*\* | 每用户独立密钥认证,防止冒充他人 |
|
||||
| 📜 \*\*历史记录\*\* | SQLite 本地存储聊天历史(重启不丢) |
|
||||
| 🛡️ \*\*HMAC 校验\*\* | 每条消息带 HMAC 签名,防篡改、防伪造 |
|
||||
| 📱 \*\*密钥分享\*\* | 群密钥支持复制 / 二维码 / 文件三种方式分享 |
|
||||
|
||||
\### 两种群模式
|
||||
|
||||
\*\*① 端到端(P2P)群聊\*\*:群主即服务器,成员直连群主主机,适合局域网或公网 IP 场景。群密钥由群主生成并分享给成员。
|
||||
|
||||
\*\*② 中央服务器群聊\*\*:运行 `server.py`(公网 IP)解决 NAT 穿透。服务器只负责用户认证、群成员管理、消息中继——\*\*不持有群密钥,无法解密任何消息\*\*。
|
||||
|
||||
\### 快速启动
|
||||
|
||||
\#### 启动聊天客户端
|
||||
|
||||
```bash
|
||||
|
||||
cd ChaosCryptChat
|
||||
|
||||
python qtmain.py
|
||||
|
||||
```
|
||||
|
||||
\#### 启动中央服务器(可选,公网/NAT 场景)
|
||||
|
||||
```bash
|
||||
|
||||
cd ChaosCryptChat
|
||||
|
||||
python server.py 8666
|
||||
|
||||
```
|
||||
|
||||
\#### 安装依赖
|
||||
|
||||
```bash
|
||||
|
||||
pip install PyQt5 \# 必需,GUI 界面
|
||||
|
||||
pip install pygame \# 语音播放(可选)
|
||||
|
||||
pip install sounddevice numpy \# 录音(可选,或 pyaudio)
|
||||
|
||||
pip install Pillow \# 图片显示(可选)
|
||||
|
||||
pip install qrcode \# 群密钥二维码(可选)
|
||||
|
||||
```
|
||||
|
||||
> 除 PyQt5 外均为可选依赖,未安装时对应功能自动禁用。
|
||||
|
||||
\### 使用流程
|
||||
|
||||
1\. \*\*注册/登录\*\*:启动后创建本地账户(PBKDF2 密码哈希,支持自动登录)
|
||||
|
||||
2\. \*\*创建群\*\*:点击「📡 创建群」,将群密钥复制/生成二维码分享给好友
|
||||
|
||||
3\. \*\*加入群\*\*:点击「📥 加入群」,粘贴群密钥或扫码加入
|
||||
|
||||
4\. \*\*开始聊天\*\*:发送文本/图片/文件/语音;右键消息可撤回、分享、下载;群主可踢人、设公告、改群名
|
||||
|
||||
\---
|
||||
|
||||
\## 快速开始
|
||||
|
||||
|
||||
@@ -501,7 +586,7 @@ cd chaoscrypt
|
||||
|
||||
|
||||
|
||||
\# 无需安装依赖,Python 3.6+ 即可
|
||||
\# 核心模块零依赖,Python 3.6+ 即可;ChaosCryptChat 需 PyQt5
|
||||
|
||||
```
|
||||
|
||||
@@ -813,6 +898,18 @@ A:有。证明如下:
|
||||
|
||||
A:初级和安全没有半毛钱关系。你长得好看能挡子弹吗?能破就是能破,不能破就是不能破。\*\*你破一个试试?\*\*
|
||||
|
||||
\*\*Q:ChaosCryptChat 聊天安全吗?\*\*
|
||||
|
||||
A:端到端加密——消息在客户端用群密钥混沌加密,服务器只转发密文、不持有密钥。即使服务器被入侵也拿不到聊天内容。
|
||||
|
||||
\*\*Q:聊天密钥怎么分享?\*\*
|
||||
|
||||
A:群主创建群后生成群密钥,可通过复制文本、二维码、密钥文件三种方式分享。请走可信渠道传递,避免被中间人截获。
|
||||
|
||||
\*\*Q:ChaosCryptChat 需要公网 IP 吗?\*\*
|
||||
|
||||
A:不需要。局域网用 P2P 直连即可;跨网络用中央服务器(server.py)中继,解决 NAT 穿透。
|
||||
|
||||
|
||||
|
||||
\---
|
||||
@@ -831,13 +928,19 @@ chaoscrypt/
|
||||
|
||||
├── README.md # 本文档
|
||||
|
||||
└── SECURITY.md # 安全说明文档
|
||||
├── SECURITY.md # 安全说明文档
|
||||
|
||||
└── ChaosCryptChat/ # 💬 混沌加密聊天(v2.0 新增)
|
||||
|
||||
├── qtmain.py # PyQt5 聊天客户端(端到端加密)
|
||||
|
||||
└── server.py # ☁️ 中央服务器(NAT 穿透 / 消息中继)
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
就这么三个文件,不藏任何东西,不依赖任何库。
|
||||
核心就三个文件,零依赖;ChaosCryptChat 是 v2.0 新增的加密聊天模块。
|
||||
|
||||
|
||||
|
||||
@@ -855,6 +958,10 @@ chaoscrypt/
|
||||
|
||||
> 生产环境请确保 `.key` 文件和用户密码均安全存储,并考虑强制用户使用强密码策略。
|
||||
|
||||
> \*\*ChaosCryptChat\*\* 服务器不持有群密钥,但会记录用户登录信息与消息中继日志,请谨慎选择信任的服务器。
|
||||
|
||||
> 聊天密钥由群主分享,请通过可信渠道传递;即使服务器被攻陷也不会泄露历史聊天内容。
|
||||
|
||||
|
||||
|
||||
\---
|
||||
|
||||
+67
@@ -765,4 +765,71 @@ DeriveKey(K\_user, K\_short, n) 包含:
|
||||
================================================================================
|
||||
|
||||
|
||||
================================================================================
|
||||
十、ChaosCryptChat 聊天模块安全分析(v2.0)
|
||||
================================================================================
|
||||
|
||||
本部分分析 ChaosCryptChat(端到端加密聊天系统)的安全模型。
|
||||
|
||||
10.1 威胁模型
|
||||
|
||||
假设:
|
||||
- 中央服务器可被攻击者完全控制(最坏情况)
|
||||
- 网络传输可被监听、篡改、重放
|
||||
- 客户端本地文件可信(不讨论端侧木马)
|
||||
|
||||
10.2 端到端加密
|
||||
|
||||
消息路径:
|
||||
明文 → 混沌加密(群密钥 + nonce 派生消息密钥)→ 密文 → 网络 → 密文 → 混沌解密 → 明文
|
||||
|
||||
关键属性:
|
||||
- 群密钥只存在于客户端本地,永不上传服务器
|
||||
- 服务器仅中继密文,无法解密任何消息
|
||||
- 每条消息使用独立 nonce 派生消息密钥,避免重放
|
||||
- HMAC-SHA256 完整性校验:密文被篡改将导致解密失败
|
||||
|
||||
10.3 中央服务器模型
|
||||
|
||||
服务器职责:用户认证 + 群成员管理 + 消息中继
|
||||
|
||||
服务器不持有:
|
||||
- 群密钥(session key)
|
||||
- 消息明文
|
||||
- 每用户密钥(user key,经服务器持有密钥加密后下发)
|
||||
|
||||
服务器被攻陷的影响:
|
||||
- 无法解密历史或实时聊天内容
|
||||
- 可能泄露:用户名、登录时间、中继日志、群成员关系
|
||||
- 可实施:拒绝服务、成员关系观察(元数据泄露)
|
||||
|
||||
10.4 身份认证
|
||||
|
||||
用户密码:PBKDF2-HMAC-SHA256,120,000 次迭代 + 随机盐
|
||||
每用户密钥:加入群时签发,用于 HMAC 签名,防止成员冒充他人
|
||||
群密钥认证:加入群需提供群密钥派生的认证凭据,证明是群成员
|
||||
|
||||
10.5 消息完整性
|
||||
|
||||
文本/语音/文件消息均携带 HMAC:
|
||||
- 文本: HMAC(密文 + 群密钥)
|
||||
- 二进制: HMAC('BIN|' + 群密钥 + nonce + 密文)
|
||||
|
||||
篡改检测:接收方重算 HMAC 不匹配 → 拒绝消息
|
||||
|
||||
10.6 已知限制
|
||||
|
||||
1. 端到端(P2P)模式下,群主主机既是聊天者又是服务器,若群主被攻陷则群聊失守
|
||||
2. 服务器虽无法解密内容,但能观察到元数据(谁和谁在何时通信)
|
||||
3. 群密钥通过群主手动分享,存在被截获的风险(需可信渠道传递)
|
||||
4. 语音播放/录音依赖第三方库(pygame/sounddevice),其安全性不在本系统保证范围
|
||||
5. 消息撤回仅做本地标记 + 服务器广播,已离线成员仍可能看到撤回前的消息
|
||||
|
||||
10.7 结论
|
||||
|
||||
ChaosCryptChat 提供端到端加密 + 完整性校验 + 身份认证,服务器无法解密内容。
|
||||
其安全边界符合"服务器不可信"模型,适合对隐私有要求但不涉及合规认证的通信场景。
|
||||
元数据泄露与密钥分享渠道仍需用户自行权衡与管理。
|
||||
|
||||
================================================================================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user