虽然端到端加密是信息安全的黄金标准,但许多平台需要更灵活的方法。企业通常需要信息访问来解决争议、保证质量或遵守法律。本文展示了一种兼顾安全性和业务需求的实用解决方案。
对于刚刚开始实施安全聊天的人来说,与端到端加密相比,使用 CryptoJS 的方法提供了一个简单得多的切入点。基本实现简单明了,易于理解和维护,同时还能为大多数业务案例提供强大的安全性。它避免了端到端加密带来的密钥管理和多设备同步的复杂性。
本文将介绍:
- 使用 Socket.IO 设置服务器
- 消息加密
- 实时通信
- 基本 UI 实现
- 错误处理
服务器实现
// server/index.ts
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import cors from 'cors';
import { encryptMessage, decryptMessage } from './encryption';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: { origin: "http://localhost:3000" } //url
});
// 存储已连接的用户
const connectedUsers = new Map<string, string>();
app.use(cors());
app.use(express.json());
io.on('connection', (socket) => {
const userId = socket.handshake.query.userId as string;
connectedUsers.set(userId, socket.id);
socket.on('message:send', async (data) => {
try {
// 将加密信息保存到数据库,适应您的数据库
const message = await Message.create({
senderId: userId,
receiverId: data.receiver,
content: data.content, // Already encrypted from client
timestamp: new Date()
});
// 如果在线,转发给receiver
const receiverSocketId = connectedUsers.get(data.receiver);
if (receiverSocketId) {
io.to(receiverSocketId).emit('message:received', {
id: message._id,
content: data.content,
sender: userId,
timestamp: new Date()
});
}
} catch (error) {
socket.emit('message:error', 'Failed to send message');
}
});
socket.on('disconnect', () => {
connectedUsers.delete(userId);
});
});
httpServer.listen(3001, () => {
console.log('Server running on port 3001');
});
前端实现
// hooks/useChat.ts
import { useEffect, useRef, useState, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
import { encryptMessage, decryptMessage } from '../lib/encryption';
interface Message {
id: string;
content: string;
sender: string;
timestamp: Date;
}
export const useChat = (userId: string, receiverId: string) => {
const [messages, setMessages] = useState<Message[]>([]);
const [isConnected, setIsConnected] = useState(false);
const socket = useRef<Socket | null>(null);
useEffect(() => {
socket.current = io('http://localhost:3001', {
query: { userId }
});
socket.current.on('connect', () => {
setIsConnected(true);
});
socket.current.on('message:received', (message) => {
const decryptedContent = decryptMessage(message.content);
setMessages(prev => [...prev, {
...message,
content: decryptedContent
}]);
});
socket.current.on('message:error', (error) => {
console.error('Message error:', error);
});
return () => {
socket.current?.disconnect();
};
}, [userId]);
const sendMessage = useCallback((content: string) => {
if (!socket.current || !content.trim()) return;
const encryptedContent = encryptMessage(content);
socket.current.emit('message:send', {
receiver: receiverId,
content: encryptedContent
});
// 为 state 添加信息
setMessages(prev => [...prev, {
id: Date.now().toString(),
content,
sender: userId,
timestamp: new Date()
}]);
}, [userId, receiverId]);
return { messages, sendMessage, isConnected };
};
加密
// lib/encryption.ts
import CryptoJS from 'crypto-js';
//生成自己的密钥
const ENCRYPTION_KEY = process.env.NEXT_PUBLIC_ENCRYPTION_KEY!;
export const encryptMessage = (message: string): string => {
return CryptoJS.AES.encrypt(message, ENCRYPTION_KEY).toString();
};
export const decryptMessage = (encryptedMessage: string): string => {
const bytes = CryptoJS.AES.decrypt(encryptedMessage, ENCRYPTION_KEY);
return bytes.toString(CryptoJS.enc.Utf8);
};
聊天组件
import { useChat } from '../hooks/useChat';
export const Chat = ({ userId, receiverId }: {
userId: string;
receiverId: string;
}) => {
const { messages, sendMessage, isConnected } = useChat(userId, receiverId);
const [input, setInput] = useState('');
const handleSend = () => {
if (!input.trim()) return;
sendMessage(input);
setInput('');
};
return (
<div className="flex flex-col h-screen">
{!isConnected && (
<div className="bg-yellow-100 p-2">
Connecting...
</div>
)}
<div className="flex-1 overflow-y-auto p-4">
{messages.map((msg) => (
<div
key={msg.id}
className={`mb-2 ${
msg.sender === userId ? 'text-right' : 'text-left'
}`}
>
<div className={`inline-block p-2 rounded-lg ${
msg.sender === userId
? 'bg-blue-500 text-white'
: 'bg-gray-200'
}`}>
{msg.content}
</div>
</div>
))}
</div>
<div className="border-t p-4 flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
className="flex-1 p-2 border rounded"
placeholder="Type a message..."
/>
<button
onClick={handleSend}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Send
</button>
</div>
</div>
);
};
消息流程
- 用户输入信息并点击发送
- 消息在客户端加密
- 加密消息发送到服务器
- 服务器将加密消息保存到数据库中
- 服务器将加密消息转发给收件人
- 收件人在客户端解密消息
- 消息显示在聊天 UI 中
建立一个安全的聊天系统并不复杂。通过使用 Socket.IO 进行实时通信和 CryptoJS 进行基本加密,您可以创建一个强大的解决方案,在安全性和实际业务需求之间取得平衡。
作者:Svetlin Markov
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/54062.html