跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScriptNode.js大前端java算法

Web 版 IM 聊天消息加密的三种算法实现方案

Web 版即时通讯应用面临隐私泄露风险,端到端加密(E2EE)是核心解决方案。对比三种加密方案:静态非对称加密、非对称加密加数字签名、混合加密系统。前两者存在性能差或缺乏前向保密性等问题,仅适合学习或低频场景。推荐采用混合加密系统,结合对称与非对称加密优势,实现高性能与前向保密,是现代生产环境的标准做法。

HadoopMan发布于 2026/3/29更新于 2026/9/1064 浏览
Web 版 IM 聊天消息加密的三种算法实现方案

在这里插入图片描述

第一部分:引言与核心密码学概念

1.1 为什么 IM 需要端到端加密(E2EE)?

即时通讯内容通常包含个人隐私、商业机密等敏感信息。传统的安全措施(如 HTTPS)只能保证信息在传输过程中的安全,无法防止消息在服务器上被窃取或窥探(例如,数据库被攻破、服务器管理员作恶)。端到端加密(End-to-End Encryption, E2EE)旨在解决这一问题。

  • 核心思想:消息在发送方客户端就被加密,直到到达接收方客户端才被解密。在整个传输和存储过程中,消息始终以密文形式存在。即使是 IM 服务提供商,也无法获取消息的明文内容。
  • 安全目标:
    • 保密性 (Confidentiality):防止未授权方读取信息内容。
    • 完整性 (Integrity):确保信息在传输过程中未被篡改。
    • 身份验证 (Authentication):确保信息确实来自声称的发送者。
    • 不可否认性 (Non-repudiation):发送者事后无法否认自己发送过的信息。
1.2 核心密码学概念与工具

在深入方案之前,必须理解以下概念:

  1. 对称加密 (Symmetric Encryption)
    • 描述:加密和解密使用同一个密钥。
    • 算法:AES (Advanced Encryption Standard) 是当前的标准,常用密钥长度 128, 192, 256 位。
    • 优点:速度快,适合加密大量数据。
    • 缺点:密钥分发困难。如何安全地将密钥分享给通信双方是一个经典难题。
  2. 非对称加密 (Asymmetric Encryption)
    • 描述:使用一对密钥:公钥 (Public Key) 和 私钥 (Private Key)。公钥可以公开,用于加密;私钥必须严格保密,用于解密。用公钥加密的数据,只有对应的私钥能解密。
    • 算法:RSA (Rivest–Shamir–Adleman), ECC (Elliptic Curve Cryptography)。ECC 在相同安全强度下比 RSA 密钥更短、计算更快。
    • 优点:解决了密钥分发问题。你可以随意发布你的公钥,任何人都可以用它加密信息,但只有你能用私钥解密。
    • 缺点:速度非常慢(比对称加密慢几个数量级),不适合加密大量数据。
  3. 混合加密系统 (Hybrid Cryptosystem)
    • 描述:结合对称加密和非对称加密的优点。
    • 流程:
      1. 发送方随机生成一个对称密钥(称为会话密钥)。
      2. 发送方使用接收方的公钥加密这个对称密钥。
      3. 发送方使用对称密钥加密实际要发送的消息。
      4. 发送方将加密后的对称密钥和加密后的消息一起发送给接收方。
      5. 接收方使用自己的私钥解密出对称密钥。
      6. 接收方使用对称密钥解密出原始消息。
    • 优点:既获得了非对称加密的安全密钥分发,又获得了对称加密的高效数据加密。这是现代安全通信(如 TLS/SSL)的基础。
  4. 数字签名 (Digital Signature)
  • 描述:用于验证消息的来源和完整性。发送方使用自己的私钥对消息的哈希值进行加密,得到签名。接收方使用发送方的公钥对签名进行解密,并将结果与自己对消息计算的哈希值对比。如果匹配,则证明消息确实来自该发送者且未被篡改。
  • 作用:提供身份验证和不可否认性。
  • 密钥派生函数 (KDF - Key Derivation Function)
    • 描述:从一个主密钥或密码派生出一個或多个加密密钥。例如,PBKDF2, scrypt, bcrypt, HKDF。
    • 作用:增强弱密码的安全性,实现'密钥拉伸',并从单个输入密钥材料生成多个密钥。
  • 前端密码学库选择
    • Web Crypto API:现代浏览器原生支持的 API,性能最好,但 API 较底层,某些高级功能(如 OAEP)支持可能因浏览器而异。
    • crypto-js:流行易用的库,但纯 JavaScript 实现,性能不如原生 API,且可能更容易受到侧信道攻击。
    • node-forge:功能非常强大的库,在 Node.js 和浏览器中都能工作,提供了比 Web Crypto API 更友好的抽象。
    • libsodium.js:是著名的 libsodium 库的 JavaScript 版本,提供了经过高度优化的、难以误用的高级 API,非常推荐用于生产环境。
  • 后端密码学库选择
    • Java 标准库 (javax.crypto, java.security) 已经非常强大,足以实现所有需求。

  • 第二部分:方案一:静态非对称加密(基础方案)

    2.1 方案概述与流程

    这是最直观的 E2EE 方案。每个用户拥有一对固定的长期密钥。发送者使用接收者的公钥直接加密每一条消息。

    流程:

    1. 密钥生成与上传:用户注册/登录时,前端生成 RSA 密钥对。私钥本地保存,公钥上传至服务器。
    2. 获取公钥:A 要给 B 发消息时,A 的前端从服务器获取 B 的公钥。
    3. 加密与发送:A 使用 B 的公钥加密消息,将密文发送给服务器。
    4. 中继与接收:服务器将密文转发给 B。
    5. 解密与展示:B 使用自己的私钥解密消息。

    序列图:

    +---------+ +-------------+ +---------+ +----------+
    | 用户 A | | 前端 A | | 后端服务器 | | 前端 B |
    +---------+ +-------------+ +---------+ +----------+
    | | | | | | | |
    | 1. 生成密钥对 | | | |------------------>| | |
    | 2. 上传公钥 | | | |------------------>| | |
    | 3. 存储公钥 | | | |<------------------| | ...
    | | | | | | | |
    | 4. 输入消息 | | | |------------------>| | |
    | 5. 获取 B 的公钥请求 | | | |------------------>| | |
    | 6. 返回 B 的公钥 | | | |<------------------| | |
    | 7. 用 B 的公钥加密消息 | | | |---(CPU Intensive)--| | |
    | 8. 发送密文给 B | | | |------------------>| | |
    | 9. 转发密文给 B | | | |------------------>| | |
    | 10. 用 B 的私钥解密 | | | |---(CPU Intensive)-- | | |
    | 11. 显示明文 | | | | | |
    
    2.2 前端 Vue 实现(使用 node-forge)
    1. 安装依赖
    npm install node-forge
    
    2. 核心工具类 crypto.js
    // utils/crypto.js
    import forge from 'node-forge';
    
    // 生成 RSA 密钥对
    export function generateRSAKeyPair() {
      return new Promise((resolve, reject) => {
        forge.pki.rsa.generateKeyPair({
          bits: 2048,
          workers: 2
        }, (err, keypair) => {
          if (err) {
            reject(err);
            return;
          }
          const publicKey = forge.pki.publicKeyToPem(keypair.publicKey);
          const privateKey = forge.pki.privateKeyToPem(keypair.privateKey);
          resolve({ publicKey, privateKey });
        });
      });
    }
    
    // 使用公钥加密消息 (RSA-OAEP padding,比 PKCS#1 更安全)
    export function encryptMessageWithPublicKey(publicKeyPem, message) {
      try {
        const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
        // 将字符串转换为字节缓冲区
        const encodedMessage = forge.util.encodeUtf8(message);
        // 使用 OAEP 填充进行加密,SHA-256 作为摘要算法
        const encrypted = publicKey.encrypt(encodedMessage, 'RSA-OAEP', {
          md: forge.md.sha256.create()
        });
        // 将二进制数据转换为 Base64 字符串以便网络传输
        return forge.util.encode64(encrypted);
      } catch (error) {
        console.error('加密失败:', error);
        throw new Error('消息加密失败');
      }
    }
    
    // 使用私钥解密消息
    export function decryptMessageWithPrivateKey(privateKeyPem, encryptedMessageBase64) {
      try {
        const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
        // 将 Base64 字符串解码为二进制数据
        const encryptedData = forge.util.decode64(encryptedMessageBase64);
        // 使用 OAEP 填充进行解密
        const decrypted = privateKey.decrypt(encryptedData, 'RSA-OAEP', {
          md: forge.md.sha256.create()
        });
        // 将字节缓冲区转换回字符串
        return forge.util.decodeUtf8(decrypted);
      } catch (error) {
        console.error('解密失败:', error);
        throw new Error('消息解密失败,可能是密钥不匹配或消息已损坏');
      }
    }
    
    // 安全地存储私钥到本地存储 (提示:这并不绝对安全,可考虑使用浏览器安全 API 或用户密码二次加密)
    export function savePrivateKeySecurely(userId, privateKey) {
      // 示例:简单存储。生产环境应使用更安全的方式,例如用用户密码派生出的密钥进行加密后再存储。
      localStorage.setItem(`im_private_key_${userId}`, privateKey);
    }
    
    // 从本地存储获取私钥
    export function getPrivateKey(userId) {
      return localStorage.getItem(`im_private_key_${userId}`);
    }
    
    3. Vue 组件中使用
    <!-- components/Chat.vue -->
    <template>
      <div>
        <div v-for="msg in messages" :key="msg.id" :class="['message', msg.sender === currentUser.id ? 'sent' : 'received']">
          <p><strong>{{ msg.senderName }}:</strong> {{ msg.decryptedContent || '**加密消息**' }}</p>
          <small>{{ msg.timestamp }}</small>
          <button v-if="!msg.decryptedContent && msg.sender !== currentUser.id" @click="decryptMessage(msg)">解密</button>
        </div>
        <div>
          <textarea v-model="newMessage" @keyup.enter="sendMessage" placeholder="输入消息..."></textarea>
          <button @click="sendMessage" :disabled="!newMessage.trim()">发送</button>
        </div>
      </div>
    </template>
    <script>
    import { encryptMessageWithPublicKey, decryptMessageWithPrivateKey, getPrivateKey } from '@/utils/crypto';
    import { apiGetUserPublicKey, apiSendMessage } from '@/api/chat';
    
    export default {
      name: 'Chat',
      props: ['currentUser', 'targetUser'],
      data() {
        return {
          newMessage: '',
          messages: [], // 消息格式:{ id, sender, senderName, encryptedContent, decryptedContent, timestamp }
          websocket: null,
        };
      },
      async mounted() {
        this.connectWebSocket();
        // ... 加载历史消息 (历史消息也是密文,需要手动解密)
      },
      methods: {
        async sendMessage() {
          if (!this.newMessage.trim()) return;
          try {
            // 1. 从服务器获取目标用户的公钥
            const publicKeyResponse = await apiGetUserPublicKey(this.targetUser.id);
            const receiverPublicKey = publicKeyResponse.data;
            // 2. 加密消息
            const encryptedContent = encryptMessageWithPublicKey(receiverPublicKey, this.newMessage.trim());
            // 3. 构建消息对象并通过 WebSocket 或 API 发送
            const messagePayload = {
              receiverId: this.targetUser.id,
              type: 'text',
              content: encryptedContent, // 发送的是密文
              isEncrypted: true, // 标记此消息已加密
              timestamp: new Date().toISOString(),
            };
            // 4. 通过 WebSocket 发送
            this.websocket.send(JSON.stringify(messagePayload));
            // 5. 乐观更新 UI (显示为'已发送,加密中')
            this.messages.push({
              id: Date.now(), // 临时 ID
              sender: this.currentUser.id,
              senderName: '我',
              encryptedContent: '**消息已加密发送**',
              decryptedContent: null,
              timestamp: '刚刚',
            });
            this.newMessage = '';
          } catch (error) {
            console.error('发送消息失败:', error);
            this.$notify({ type: 'error', title: '发送失败', text: '加密或发送消息时出错' });
          }
        },
        async decryptMessage(message) {
          if (message.decryptedContent) return; // 已解密
          try {
            // 获取自己的私钥
            const privateKey = getPrivateKey(this.currentUser.id);
            if (!privateKey) {
              throw new Error('未找到解密密钥,请重新登录');
            }
            // 解密消息
            const decryptedContent = decryptMessageWithPrivateKey(privateKey, message.encryptedContent);
            // 更新消息状态
            message.decryptedContent = decryptedContent;
            // 可选:通知后端该消息已成功解密阅读(已读回执)
          } catch (error) {
            console.error('解密消息失败:', error);
            this.$notify({ type: 'error', title: '解密失败', text: '无法解密此消息' });
          }
        },
        connectWebSocket() {
          // ... WebSocket 连接逻辑,用于实时接收消息
          this.websocket.onmessage = (event) => {
            const messageData = JSON.parse(event.data);
            this.handleIncomingMessage(messageData);
          };
        },
        handleIncomingMessage(messageData) {
          // 处理接收到的消息
          const newMsg = {
            id: messageData.id,
            sender: messageData.senderId,
            senderName: messageData.senderName,
            encryptedContent: messageData.content, // 收到的是密文
            decryptedContent: null, // 初始化为 null,等待用户点击解密
            timestamp: new Date(messageData.timestamp).toLocaleTimeString(),
          };
          this.messages.push(newMsg);
          // 可选:如果是当前会话的消息,可以提示用户有新消息
          if (this.targetUser.id === messageData.senderId) {
            this.$notify({ type: 'info', title: '新消息', text: `来自 ${messageData.senderName} 的加密消息` });
          }
        },
      },
      beforeUnmount() {
        if (this.websocket) {
          this.websocket.close();
        }
      },
    };
    </script>
    <style scoped>
    /* ... 聊天样式 ... */
    .message.received button {
      margin-left: 10px;
      font-size: 0.8em;
    }
    </style>
    
    2.3 后端 Java 实现(Spring Boot)

    后端在此方案中角色较轻,主要负责公钥的存储查询和消息的转发。

    1. 实体类
    // User 实体,增加公钥字段
    @Entity
    @Table(name = "im_users")
    @Data
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String username;
        private String nickname;
        
        @Column(columnDefinition = "TEXT") // 公钥是长文本
        private String rsaPublicKey; // 用户的 RSA 公钥 (PEM 格式)
        
        // ... 其他字段如密码、创建时间等
    }
    
    // 消息实体
    @Entity
    @Table(name = "im_messages")
    @Data
    public class ChatMessage {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private Long senderId;
        private Long receiverId;
        
        @Column(columnDefinition = "TEXT") // 消息内容可能是长文本密文
        private String content;
        private Boolean isEncrypted; // 标记消息是否加密
        private String messageType; // "text", "image", "file" etc.
        private Instant timestamp;
        
        // ... 其他字段如状态(已发送、已送达、已读)
    }
    
    2. Controller 层
    @RestController
    @RequestMapping("/api/chat")
    public class ChatController {
        @Autowired
        private UserRepository userRepository;
        
        @Autowired
        private SimpMessagingTemplate messagingTemplate; // Spring WebSocket 消息发送模板
        
        @Autowired
        private ChatMessageRepository messageRepository;
    
        // 获取指定用户的公钥
        @GetMapping("/user/{userId}/public-key")
        public ResponseEntity<?> getUserPublicKey(@PathVariable Long userId) {
            User user = userRepository.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User not found"));
            if (user.getRsaPublicKey() == null) {
                return ResponseEntity.badRequest().body("User does not have a public key");
            }
            // 返回公钥字符串
            return ResponseEntity.ok().body(Collections.singletonMap("publicKey", user.getRsaPublicKey()));
        }
    
        // 接收并转发加密消息 (通过 HTTP API,也可通过 WebSocket 接收)
        @PostMapping("/message")
        public ResponseEntity<Void> sendEncryptedMessage(@RequestBody EncryptedMessageRequest request) {
            // 1. 验证发送者身份 (可以从 JWT token 中获取当前用户 ID,并与 request.getSenderId() 对比)
            // 2. 将消息保存到数据库(存密文)
            ChatMessage message = new ChatMessage();
            message.setSenderId(request.getSenderId());
            message.setReceiverId(request.getReceiverId());
            message.setContent(request.getContent()); // 这里已经是加密后的密文
            message.setIsEncrypted(true);
            message.setMessageType(request.getType());
            message.setTimestamp(Instant.now());
            messageRepository.save(message);
    
            // 3. 通过 WebSocket 实时转发给接收者
            // 目的地格式: /user/{userId}/queue/messages
            String destination = "/queue/messages";
            MessageDeliveryDto deliveryDto = new MessageDeliveryDto();
            deliveryDto.setId(message.getId());
            deliveryDto.setSenderId(request.getSenderId());
            deliveryDto.setSenderName(""); // 需要查询发送者姓名
            deliveryDto.setContent(request.getContent());
            deliveryDto.setEncrypted(true);
            deliveryDto.setType(request.getType());
            deliveryDto.setTimestamp(message.getTimestamp());
            messagingTemplate.convertAndSendToUser(
                request.getReceiverId().toString(),
                destination,
                deliveryDto
            );
    
            // 4. 可选:发送送达回执给发送者
            return ResponseEntity.ok().build();
        }
    
        // 请求体定义
        public static class EncryptedMessageRequest {
            private Long senderId;
            private Long receiverId;
            private String content;
            private String type;
            // getters and setters
        }
    
        // WebSocket 转发消息 DTO
        public static class MessageDeliveryDto {
            private Long id;
            private Long senderId;
            private String senderName;
            private String content;
            private Boolean isEncrypted;
            private String type;
            private Instant timestamp;
            // getters and setters
        }
    }
    
    3. WebSocket 配置
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
        }
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            registry.setApplicationDestinationPrefixes("/app");
            registry.enableSimpleBroker("/topic", "/queue");
            registry.setUserDestinationPrefix("/user");
        }
    
        // 可选:配置身份验证拦截器
        @Override
        public void configureClientInboundChannel(ChannelRegistration registration) {
            registration.interceptors(new AuthChannelInterceptor());
        }
    }
    
    2.4 密钥管理、注册与登录集成
    1. 用户注册/登录时生成密钥
    // Vuex Store (store/auth.js) 或登录组件中
    import { generateRSAKeyPair, savePrivateKeySecurely } from '@/utils/crypto';
    import { apiRegister, apiLogin, apiUpdatePublicKey } from '@/api/auth';
    
    const actions = {
      async register({ commit }, userData) {
        try {
          // 1. 生成密钥对
          const { publicKey, privateKey } = await generateRSAKeyPair();
          // 2. 将公钥包含在注册数据中
          const registrationData = {
            ...userData,
            publicKey: publicKey
          };
          // 3. 调用注册 API
          const response = await apiRegister(registrationData);
          // 4. 注册成功,在本地安全存储私钥
          if (response.data.userId) {
            savePrivateKeySecurely(response.data.userId, privateKey);
            commit('SET_USER', response.data);
            commit('SET_PRIVATE_KEY', privateKey); // 也可存入 Vuex state
          }
          return response;
        } catch (error) {
          console.error('注册失败:', error);
          throw error;
        }
      },
      async login({ commit }, credentials) {
        try {
          const response = await apiLogin(credentials);
          const user = response.data;
          // 检查本地是否已有该用户的私钥
          let privateKey = getPrivateKey(user.id);
          // 如果没有,说明可能是新设备登录,需要引导用户重新生成密钥对并上传公钥
          if (!privateKey) {
            this.$router.push('/key-setup'); // 跳转到密钥设置页面
            return;
          }
          commit('SET_USER', user);
          commit('SET_PRIVATE_KEY', privateKey);
          return response;
        } catch (error) {
          console.error('登录失败:', error);
          throw error;
        }
      }
    };
    
    2. 密钥设置页面
    <!-- views/KeySetup.vue -->
    <template>
      <div>
        <h2>安全密钥设置</h2>
        <p>检测到您在新设备登录,需要为您生成新的加密密钥。</p>
        <p>生成后,您将无法在其他设备上解密之前的消息。</p>
        <!-- 这是该方案的一个缺点 -->
        <button @click="generateNewKeys">生成新密钥</button>
      </div>
    </template>
    <script>
    import { generateRSAKeyPair, savePrivateKeySecurely } from '@/utils/crypto';
    import { apiUpdatePublicKey } from '@/api/auth';
    
    export default {
      methods: {
        async generateNewKeys() {
          try {
            const { publicKey, privateKey } = await generateRSAKeyPair();
            const userId = this.$store.state.auth.user.id;
            // 上传新公钥到服务器
            await apiUpdatePublicKey(userId, publicKey);
            // 保存新私钥到本地
            savePrivateKeySecurely(userId, privateKey);
            this.$store.commit('auth/SET_PRIVATE_KEY', privateKey);
            this.$router.go(-1); // 返回上一页
          } catch (error) {
            console.error('密钥生成失败:', error);
          }
        }
      }
    };
    </script>
    
    2.5 方案一优缺点总结
    • 优点:
      • 概念简单:易于理解和实现。
      • 符合 E2EE:服务器从未接触明文。
      • 无需状态管理:服务器无需管理会话密钥。
    • 缺点:
      • 性能极差:RSA 加密非常慢,尤其是对长消息。频繁发送消息会导致前端界面卡顿。
      • 无前向保密性 (Forward Secrecy):如果用户的长期私钥将来被泄露,攻击者可以解密该用户所有过去和未来的通信记录。这是致命的缺点。
      • 密钥管理复杂:用户更换设备后,旧设备上的私钥无法同步,导致无法解密新消息,且旧消息也无法在新设备上解密。解决方案(如用主密码加密私钥然后同步)会引入新的复杂性。
      • 无法认证发送者:接收者知道消息是用自己的公钥加密的,但无法密码学上证实发送者是谁。(方案二通过数字签名解决此问题)。

    结论:方案一适用于学习原理或对安全要求不高、消息频率极低的场景,不推荐用于生产环境。


    第三部分:方案二:非对称加密 + 数字签名(增强身份验证)

    3.1 方案概述与流程

    此方案在方案一的基础上增加了数字签名,解决了发送者身份验证和消息完整性的问题。

    流程(A 发消息给 B):

    1. A 生成消息明文 M。
    2. A 使用 B 的公钥加密 M,得到密文 C。
    3. A 使用自己的私钥对密文 C(或明文 M 的哈希)进行签名,得到签名 S。
      • 签名明文哈希更常见,因为性能更好且符合标准。
    4. A 将 { cipherText: C, signature: S, senderId: A } 发送给服务器。
    5. 服务器转发给 B。
    6. B 收到后,使用 A 的公钥验证签名 S。如果验证失败,则丢弃消息。
    7. 验证通过后,B 使用自己的私钥解密 C,得到明文 M。

    序列图:

    +---------+ +-------------+ +----------+ +----------+
    | 用户 A | | 前端 A | | 后端服务器 | | 前端 B |
    +---------+ +-------------+ +----------+ +----------+
    | | | | | | | |
    | 1. 输入消息 | | | |------------------>| | |
    | 2. 获取 B 的公钥 | | | |------------------>| | |
    | 3. 返回 B 的公钥 | | | |<------------------| | |
    | 4. 用 B 的公钥加密消息 | | | |---(CPU Intensive)--| | |
    | 5. 用 A 的私钥签名 | | | |---(CPU Intensive)--| | |
    | 6. 发送 (密文 + 签名) | | | |------------------>| | |
    | 7. 转发给 B | | | |------------------>| | |
    | 8. 获取 A 的公钥 | | | |<------------------? | | |
    | 9. 验证签名 | | | |---(CPU Intensive)-- | | |
    | 10. 验证成功? | | | |---Yes------------| | |
    | | | | | | | | |
    | 11. 用 B 的私钥解密 | | | |---(CPU Intensive)-- | | |
    | 12. 显示明文 | | | |---No-------------| | |
    | 13. 丢弃消息,报错 | | | | | |
    
    3.2 前端 Vue 实现增强

    在原有 crypto.js 工具类中添加签名和验证功能。

    // utils/crypto.js (新增函数)
    
    // 使用私钥对消息进行签名 (通常是对消息的哈希值进行签名)
    export function signMessageWithPrivateKey(privateKeyPem, message) {
      try {
        const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
        const md = forge.md.sha256.create(); // 创建 SHA-256 哈希上下文
        md.update(message, 'utf8'); // 更新哈希内容
        const signature = privateKey.sign(md); // 使用私钥对哈希值进行签名
        return forge.util.encode64(signature); // 返回 Base64 编码的签名
      } catch (error) {
        console.error('签名失败:', error);
        throw new Error('消息签名失败');
      }
    }
    
    // 使用公钥验证签名
    export function verifySignatureWithPublicKey(publicKeyPem, message, signatureBase64) {
      try {
        const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
        const md = forge.md.sha256.create();
        md.update(message, 'utf8');
        const signature = forge.util.decode64(signatureBase64);
        const isVerified = publicKey.verify(md.digest().getBytes(), signature);
        return isVerified;
      } catch (error) {
        console.error('验证签名失败:', error);
        return false; // 验证过程中出现异常,视为验证失败
      }
    }
    
    // 注意:以上是对明文签名。更常见的做法是对加密后的密文进行签名,以避免接收方先解密再验证的逻辑循环。
    // 另一种标准做法是:签名明文,然后将(密文 + 签名)一起发送。
    // 以下是签名明文的版本(更常见):
    export function signPlainTextWithPrivateKey(privateKeyPem, plainText) {
      // ... 实现同上,参数 plainText 代替 message
    }
    
    export function verifyPlainTextSignature(publicKeyPem, plainText, signatureBase64) {
      // ... 实现同上
    }
    

    修改发送和接收消息的逻辑:

    发送消息(增加签名)
    // components/Chat.vue - sendMessage 方法修改
    async sendMessage() {
      // ... 获取接收者公钥、加密消息 (得到 encryptedContent) ...
      // 使用发送者 (A) 的私钥对【明文】进行签名
      const privateKey = getPrivateKey(this.currentUser.id);
      const signature = signPlainTextWithPrivateKey(privateKey, this.newMessage.trim());
      const messagePayload = {
        receiverId: this.targetUser.id,
        type: 'text',
        content: encryptedContent,
        signature: signature, // 新增签名字段
        isEncrypted: true,
        timestamp: new Date().toISOString(),
      };
      // ... 发送 messagePayload ...
    }
    
    接收消息(增加验证)
    // components/Chat.vue - handleIncomingMessage 或 decryptMessage 方法修改
    async decryptMessage(message) {
      // ... 获取自己的私钥解密 (得到 decryptedContent) ...
      // 获取发送者的公钥来验证签名
      try {
        const senderPublicKeyResponse = await apiGetUserPublicKey(message.sender);
        const senderPublicKey = senderPublicKeyResponse.data.publicKey;
        // 验证签名:用发送者公钥验证解密出的明文 against 消息中的签名
        const isSignatureValid = verifyPlainTextSignature(senderPublicKey, decryptedContent, message.signature);
        if (isSignatureValid) {
          message.decryptedContent = decryptedContent;
          message.signatureStatus = 'verified';
          this.$notify({ type: 'success', title: '解密成功', text: '消息签名验证通过' });
        } else {
          message.decryptedContent = decryptedContent + " [警告:签名验证失败,消息可能被篡改或来源不可信]";
          message.signatureStatus = 'invalid';
          this.$notify({ type: 'warning', title: '安全警告', text: '消息解密成功,但签名验证失败' });
        }
      } catch (error) {
        console.error('验证签名时出错:', error);
        message.decryptedContent = decryptedContent + " [错误:签名验证过程出错]";
        message.signatureStatus = 'error';
      }
    }
    
    3.3 后端 Java 实现增强

    后端需要存储和转发签名。

    1. 修改消息实体和 DTO
    // ChatMessage 实体增加字段
    public class ChatMessage {
        // ... 其他字段 ...
        @Column(columnDefinition = "TEXT")
        private String signature; // 存储消息的数字签名 (Base64 编码)
    }
    
    // Controller 中的 DTO 也要增加 signature 字段
    public static class EncryptedMessageRequest {
        private Long senderId;
        private Long receiverId;
        private String content;
        private String signature; // 新增
        private String type;
        // getters and setters
    }
    
    public static class MessageDeliveryDto {
        // ... 其他字段 ...
        private String signature; // 新增
        // getters and setters
    }
    
    2. 后端签名验证(可选)

    后端原则上不处理明文,但可以选择性地验证签名以增加一层安全防护(防止恶意客户端发送伪造签名的消息)。

    // 在 ChatController 中,接收消息时可选验证签名
    @PostMapping("/message")
    public ResponseEntity<Void> sendEncryptedMessage(@RequestBody EncryptedMessageRequest request) {
        // 1. (可选) 验证发送者身份后,验证消息签名
        User sender = userRepository.findById(request.getSenderId()).orElseThrow(...);
        // 假设签名是对明文哈希的签名,但后端没有明文,无法验证。
        // 如果签名是对密文的签名,后端可以验证。
        // 通常后端不验证,因为它是密文。验证工作主要在客户端进行。
        // 2. 存储和转发消息(包括签名)
        // ... 原有逻辑 ...
    }
    
    3.4 方案二优缺点总结
    • 优点:
      • 提供身份验证和不可否认性:接收者可以确信消息来自特定的发送者。
      • 提供完整性保护:签名验证失败意味着消息或签名在传输过程中被篡改。
      • 继承方案一的 E2EE 优点。
    • 缺点:
      • 性能进一步下降:每次发送消息都需要进行两次昂贵的非对称加密操作(加密和签名)。
      • 依然没有前向保密性:长期私钥泄露的风险依然存在。
      • 密钥管理问题依旧。

    结论:方案二解决了身份验证问题,但加剧了性能问题,且仍未解决前向保密这一核心安全缺陷。仍不推荐用于高频生产环境。


    第四部分:方案三:混合加密系统(推荐生产方案)

    4.1 方案概述与流程

    这是现代安全通信的标准模型,完美结合了非对称加密和对称加密的优点。核心思想是:使用非对称加密安全地交换一个临时的对称密钥,然后使用这个对称密钥来加密实际的消息。

    核心概念:前向保密 (Forward Secrecy)

    • 每次会话或定期更换对称密钥(称为会话密钥)。
    • 即使攻击者破解了用户的长期私钥,也无法解密过去的通信记录,因为过去的会话密钥早已丢弃,且是用当时的临时密钥加密的。
    • 这是生产级 E2EE 系统的必备特性。

    流程(A 发起与 B 的会话):

    1. 会话初始化:
      • A 生成一个随机的对称会话密钥 SK 和初始化向量 IV。
      • A 获取 B 的长期公钥。
      • A 使用 B 的公钥加密 (SK, IV),得到密钥信封 Envelope。
      • A 可选地用自已的私钥对 Envelope 签名。
      • A 将 Envelope 和签名发送给 B。这个过程称为密钥交换。
    2. 发送消息:
      • A 使用会话密钥 SK 和 IV,通过 AES 算法对消息明文进行对称加密,得到密文 C。
      • A 将密文 C 发送给 B。
    3. 接收消息:
      • B 收到 Envelope 后,用自己的长期私钥解密,得到 SK 和 IV。
      • B 收到密文 C 后,使用 SK 和 IV 进行对称解密,得到明文。

    序列图:

    +---------+ +-------------+ +----------+ +----------+
    | 用户 A | | 前端 A | | 后端服务器 | | 前端 B |
    +---------+ +-------------+ +----------+ +----------+
    | | | | | | | |
    | 1. 开始会话 | | | |------------------>| | |
    | 2. 生成会话密钥 SK | | | |---(Generate SK)---| | |
    | 3. 获取 B 的公钥 | | | |------------------>| | |
    | 4. 返回 B 的公钥 | | | |<------------------| | |
    | 5. 用 B 的公钥加密 SK | | | |---(CPU Intensive)--| | |
    | 6. 发送密钥信封 | | | |------------------>| | |
    | 7. 转发给 B | | | |------------------>| | |
    | 8. 用 B 的私钥解密得 SK | | | |---(CPU Intensive)-- | | |
    | 9. 存储 SK | | | |---(Now ready)----| | |
    | ... | | | | | | | |
    | | | | | | | | |
    | 10. 输入消息 | | | | |------------------>| | |
    | | | | | | | | |
    | 11. 用 SK 加密消息 | | | | |---(Very Fast)-----| | |
    | | | | | | | | |
    | 12. 发送消息密文 | | | | |------------------>| | |
    | | | | | | | | |
    | 13. 转发给 B | | | | |------------------>| | |
    | | | | | | | | |
    | 14. 用 SK 解密消息 | | | |---(Very Fast)----| | |
    | | | | | | | | |
    | 15. 显示明文 | | | | | | | |
    

    *注:密钥交换通常只在会话开始时进行一次,后续所有消息都使用高效的对称加密。

    4.2 前端 Vue 实现(重大修改)

    我们需要一个全面的会话密钥管理机制。

    1. 扩展加密工具类 crypto.js
    // utils/crypto.js (新增混合加密函数)
    
    // 生成随机的对称密钥和 IV (用于 AES-CBC 模式)
    export function generateSymmetricKey() {
      const key = forge.random.getBytesSync(32); // AES-256 需要 32 字节的密钥
      const iv = forge.random.getBytesSync(16); // AES-CBC 需要 16 字节的 IV
      return {
        key: forge.util.encode64(key), // 编码为 Base64 便于存储
        iv: forge.util.encode64(iv)
      };
    }
    
    // 使用对称密钥加密消息 (AES-CBC)
    export function encryptWithSymmetricKey(keyBase64, ivBase64, message) {
      try {
        const key = forge.util.decode64(keyBase64);
        const iv = forge.util.decode64(ivBase64);
        const cipher = forge.cipher.createCipher('AES-CBC', key);
        cipher.start({ iv: iv });
        cipher.update(forge.util.createBuffer(message, 'utf8'));
        cipher.finish();
        const encrypted = cipher.output;
        return forge.util.encode64(encrypted.data);
      } catch (error) {
        console.error('对称加密失败:', error);
        throw new Error('消息加密失败');
      }
    }
    
    // 使用对称密钥解密消息 (AES-CBC)
    export function decryptWithSymmetricKey(keyBase64, ivBase64, encryptedMessageBase64) {
      try {
        const key = forge.util.decode64(keyBase64);
        const iv = forge.util.decode64(ivBase64);
        const encryptedData = forge.util.decode64(encryptedMessageBase64);
        const decipher = forge.cipher.createDecipher('AES-CBC', key);
        decipher.start({ iv: iv });
        decipher.update(forge.util.createBuffer(encryptedData));
        const result = decipher.finish();
        if (result) {
          return decipher.output.toString('utf8');
        } else {
          throw new Error('解密失败:可能密钥或 IV 不正确');
        }
      } catch (error) {
        console.error('对称解密失败:', error);
        throw new Error('消息解密失败');
      }
    }
    
    // 使用公钥加密对称密钥(封装信封)
    export function encryptSymmetricKeyWithPublicKey(publicKeyPem, symmetricKeyObj) {
      // 将对称密钥对象转换为字符串以便加密
      const keyDataStr = JSON.stringify(symmetricKeyObj);
      return encryptMessageWithPublicKey(publicKeyPem, keyDataStr); // 复用之前的 RSA 加密函数
    }
    
    // 使用私钥解密对称密钥(解封装信封)
    export function decryptSymmetricKeyWithPrivateKey(privateKeyPem, encryptedEnvelope) {
      const decryptedKeyStr = decryptMessageWithPrivateKey(privateKeyPem, encryptedEnvelope);
      return JSON.parse(decryptedKeyStr);
    }
    
    2. 会话密钥管理 (Vuex Store)

    我们需要一个地方来存储和管理与不同用户的会话密钥。

    // store/modules/chatSession.js
    const state = {
      // 会话密钥库:{ [targetUserId]: { key: '...', iv: '...', timestamp: ... } }
      sessionKeys: {},
    };
    
    const mutations = {
      SET_SESSION_KEY(state, { userId, keyData }) {
        state.sessionKeys[userId] = {
          ...keyData,
          timestamp: Date.now(), // 记录密钥生成时间,可用于过期策略
        };
      },
      CLEAR_SESSION_KEY(state, userId) {
        delete state.sessionKeys[userId];
      },
      CLEAR_ALL_SESSIONS(state) {
        state.sessionKeys = {};
      },
    };
    
    const actions = {
      // 为与特定用户的会话生成并交换密钥
      async establishSession({ commit, rootState }, targetUserId) {
        try {
          // 1. 生成对称密钥
          const symmetricKey = generateSymmetricKey();
          // 2. 获取目标用户的公钥
          const publicKeyResponse = await apiGetUserPublicKey(targetUserId);
          const receiverPublicKey = publicKeyResponse.data.publicKey;
          // 3. 用对方的公钥加密我们的对称密钥(创建信封)
          const encryptedEnvelope = encryptSymmetricKeyWithPublicKey(receiverPublicKey, symmetricKey);
          // 4. 发送密钥交换消息
          await apiSendKeyExchange({
            receiverId: targetUserId,
            encryptedEnvelope: encryptedEnvelope, // 可以在这里添加签名
          });
          // 5. 将会话密钥保存在本地 Store 中
          commit('SET_SESSION_KEY', { userId: targetUserId, keyData: symmetricKey });
          console.log(`会话密钥已建立并发送给用户 ${targetUserId}`);
          return symmetricKey;
        } catch (error) {
          console.error('建立会话失败:', error);
          throw new Error('无法建立安全会话');
        }
      },
      // 获取与某个用户的会话密钥,如果没有则建立
      async getOrCreateSessionKey({ state, dispatch }, targetUserId) {
        let keyData = state.sessionKeys[targetUserId];
        // 简单检查密钥是否存在以及是否过期(例如,设置 1 小时过期)
        const isExpired = keyData && (Date.now() - keyData.timestamp > 3600000); // 1 小时
        if (!keyData || isExpired) {
          keyData = await dispatch('establishSession', targetUserId);
        }
        return keyData;
      },
    };
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions,
    };
    
    3. 修改聊天组件
    <!-- components/Chat.vue (重大修改) -->
    <script>
    import { encryptWithSymmetricKey, decryptWithSymmetricKey } from '@/utils/crypto';
    import { mapActions } from 'vuex';
    
    export default {
      // ... methods: {
      async sendMessage() {
        if (!this.newMessage.trim()) return;
        try {
          // 1. 获取或创建与目标用户的会话密钥
          const sessionKeyData = await this.$store.dispatch('chatSession/getOrCreateSessionKey', this.targetUser.id);
          // 2. 使用对称密钥加密消息 (非常快)
          const encryptedContent = encryptWithSymmetricKey(
            sessionKeyData.key,
            sessionKeyData.iv,
            this.newMessage.trim()
          );
          // 3. 发送消息
          const messagePayload = {
            receiverId: this.targetUser.id,
            type: 'text',
            content: encryptedContent,
            isEncrypted: true,
            isSymmetric: true, // 新增字段,表明是对称加密
            timestamp: new Date().toISOString(),
          };
          this.websocket.send(JSON.stringify(messagePayload));
          // 4. 乐观更新 UI
          this.messages.push({
            id: Date.now(),
            sender: this.currentUser.id,
            senderName: '我',
            encryptedContent: '**消息已加密发送**',
            decryptedContent: this.newMessage.trim(), // 乐观显示明文
            timestamp: '刚刚',
          });
          this.newMessage = '';
        } catch (error) {
          console.error('发送消息失败:', error);
          this.$notify({ type: 'error', title: '发送失败', text: '加密或发送消息时出错' });
        }
      },
      async handleIncomingMessage(messageData) {
        // 处理接收到的消息
        const newMsg = {
          id: messageData.id,
          sender: messageData.senderId,
          senderName: messageData.senderName,
          encryptedContent: messageData.content,
          decryptedContent: null,
          isSymmetric: messageData.isSymmetric, // 检查是否是对称加密
          timestamp: new Date(messageData.timestamp).toLocaleTimeString(),
        };
        // 如果是对称加密消息,并且我们有会话密钥,尝试自动解密
        if (newMsg.isSymmetric) {
          const sessionKey = this.$store.state.chatSession.sessionKeys[newMsg.sender];
          if (sessionKey) {
            try {
              newMsg.decryptedContent = decryptWithSymmetricKey(
                sessionKey.key,
                sessionKey.iv,
                newMsg.encryptedContent
              );
            } catch (decryptError) {
              console.error('自动解密失败:', decryptError);
              newMsg.decryptedContent = null; // 解密失败,等待手动重试
            }
          }
        }
        this.messages.push(newMsg);
        // ... 其他逻辑 ...
      },
      // 处理接收到的密钥交换消息(来自 B 的回复或主动发起)
      async handleKeyExchangeMessage(messageData) {
        if (messageData.type === 'key_exchange') {
          try {
            const privateKey = getPrivateKey(this.currentUser.id);
            const decryptedKeyData = decryptSymmetricKeyWithPrivateKey(privateKey, messageData.encryptedEnvelope);
            // 存储发送方(B)的会话密钥,用于解密他发来的消息
            this.$store.commit('chatSession/SET_SESSION_KEY', {
              userId: messageData.senderId,
              keyData: decryptedKeyData,
            });
            console.log(`收到来自 ${messageData.senderId} 的会话密钥并已保存`);
            // 可以发送一个确认消息
          } catch (error) {
            console.error('处理密钥交换消息失败:', error);
          }
        }
      },
      // ... };
    };
    </script>
    
    4.3 后端 Java 实现

    后端需要处理两种类型的消息:key_exchange 和普通的 text 消息。

    1. 修改 Controller
    @PostMapping("/message")
    public ResponseEntity<Void> sendMessage(@RequestBody MessageRequest request) {
        // ... 身份验证 ...
        ChatMessage message = new ChatMessage();
        message.setSenderId(request.getSenderId());
        message.setReceiverId(request.getReceiverId());
        message.setContent(request.getContent());
        message.setSignature(request.getSignature());
        message.setIsEncrypted(request.getIsEncrypted());
        message.setMessageType(request.getType()); // "text" or "key_exchange"
        message.setTimestamp(Instant.now());
        messageRepository.save(message);
    
        MessageDeliveryDto deliveryDto = new MessageDeliveryDto();
        // ... 填充字段 ...
        deliveryDto.setType(request.getType()); // 设置消息类型
        messagingTemplate.convertAndSendToUser(
            request.getReceiverId().toString(),
            "/queue/messages",
            deliveryDto
        );
        return ResponseEntity.ok().build();
    }
    
    // 统一的请求体,支持多种消息类型
    public static class MessageRequest {
        private Long senderId;
        private Long receiverId;
        private String content;
        private String signature;
        private Boolean isEncrypted;
        private String type; // "text", "key_exchange", "image", etc.
        // getters and setters
    }
    
    4.4 高级特性:双工密钥协商与 Perfect Forward Secrecy

    上面的实现是 A 生成密钥给 B,是单向的。更安全的做法是双方各自生成一个密钥种子,通过 Diffie-Hellman 密钥交换协议协商出一个共享的会话密钥。这可以实现完全的前向保密,即使双方的长期私钥都泄露,过去的会话也无法解密。

    使用 ECDH(椭圆曲线 Diffie-Hellman)
    1. A 和 B 各自生成临时的 ECC 密钥对。
    2. A 将自己的临时公钥发送给 B。
    3. B 将自己的临时公钥发送给 A。
    4. A 用自己的临时私钥和 B 的临时公钥计算共享密钥。
    5. B 用自己的临时私钥和 A 的临时公钥计算共享密钥。(根据 ECC 数学原理,两者计算出的共享密钥相同)
    6. 双方用这个共享密钥派生出的对称密钥进行通信。
    7. 会话结束后,双方立即销毁临时的 ECC 密钥对和会话密钥。

    这样,每次会话的密钥都是独立的。实现此机制复杂度较高,通常借助 libsodium.js 等库。以下是概念性代码:

    // 概念性代码,使用 libsodium.js
    import sodium from 'libsodium-wrappers';
    await sodium.ready;
    
    // A 和 B 各自生成临时密钥对
    let keyPairA = sodium.crypto_kx_keypair();
    let keyPairB = sodium.crypto_kx_keypair();
    
    // A 计算共享密钥
    let sharedKeyA = sodium.crypto_kx_client_session_keys(keyPairA.publicKey, keyPairA.privateKey, keyPairB.publicKey);
    
    // B 计算共享密钥
    let sharedKeyB = sodium.crypto_kx_server_session_keys(keyPairB.publicKey, keyPairB.privateKey, keyPairA.publicKey);
    
    // sharedKeyA.rx === sharedKeyB.tx
    // sharedKeyA.tx === sharedKeyB.rx
    
    // 双方现在有两个密钥:一个用于发送,一个用于接收
    
    4.5 方案三优缺点总结
    • 优点:
      • 高性能:消息通信使用高效的对称加密,用户体验流畅。
      • 前向保密性:会话密钥是临时的,定期更换可使过去的通信在长期私钥泄露后依然安全。
      • 高安全性:结合了非对称加密的安全密钥分发和对称加密的效率。
    • 缺点:
      • 实现复杂度最高:需要管理会话状态、密钥的生命周期、密钥交换协议等。
      • 状态管理:需要在前端维护会话密钥的状态,页面刷新可能导致密钥丢失(需要重新协商)。可以考虑将会话密钥安全地存储在 IndexedDB 中。

    结论:方案三是唯一推荐用于生产环境的方案。它提供了最佳的性能和安全特性平衡,是现代 E2EE 应用的标准做法。


    第五部分:部署、测试与安全最佳实践

    5.1 密钥安全存储指南
    • 长期私钥:
      • 不要明文存储在 localStorage 中。容易被 XSS 攻击窃取。
      • 推荐方案:使用用户密码通过 PBKDF2 等 KDF 派生出一个密钥,用这个密钥对长期私钥进行加密后再存储。解密时要求用户输入密码。
      • 替代方案:使用浏览器的 window.crypto.subtle API 生成非提取式密钥,并存储在 IndexedDB 中。
    • 会话密钥:
      • 可以存储在 Vuex 内存中,页面刷新即丢失(需要重新协商,增强了前向保密性)。
      • 如果希望刷新后保持会话,可以加密后存入 sessionStorage 或 IndexedDB。
    5.2 传输安全
    • 必须使用 HTTPS:所有 API 和 WebSocket 连接都必须通过 TLS 加密,防止中间人攻击窃取公钥或密文。
    • 安全的 WebSocket:使用 wss:// 协议。
    5.3 后端安全考虑
    • 身份验证:所有 API 调用都必须有严格的身份验证(如 JWT),确保用户不能冒充他人发送消息或获取他人的公钥。
    • 权限检查:在转发消息前,验证发送者 senderId 确实属于当前登录用户。
    • 速率限制:对密钥交换和消息发送接口实施速率限制,防止滥用。
    5.4 测试策略
    1. 单元测试:测试每个加密/解密函数,确保其正确性。
    2. 集成测试:
      • 测试两个客户端能否成功完成密钥交换。
      • 测试加密消息能否被正确解密。
      • 测试签名验证能否正确识别有效和无效签名。
    3. 负载测试:模拟大量用户同时发送加密消息,测试系统的性能表现。
    5.5 处理常见问题
    • '无法解密'错误:引导用户检查密码(如果用了加密存储)或尝试重新建立会话。
    • '签名无效'警告:明确告知用户消息可能不可信,并建议通过其他渠道验证消息内容。
    • 新设备登录:设计清晰的流程引导用户生成新密钥对,并告知其对旧消息的影响。

    第六部分:总结与方案选择

    特性方案一:静态非对称方案二:静态 + 签名方案三:混合加密
    安全性低中高
    性能极差非常差优秀
    前向保密无无有
    身份验证/完整性无有有
    实现复杂度低中高
    密钥管理复杂复杂中等
    推荐场景学习原型需要身份验证的低频场景生产环境、所有 IM 应用

    最终强烈推荐:

    对于任何严肃的、面向用户的 Web 版 IM 应用,请务必选择方案三(混合加密系统)。它虽然是实现起来最复杂的方案,但它是唯一能同时满足安全性(E2EE、前向保密)、性能和用户体验要求的方案。方案一和方案二仅适用于理解概念或极其特殊的低频场景。

    在这里插入图片描述

    目录

    1. 第一部分:引言与核心密码学概念
    2. 1.1 为什么 IM 需要端到端加密(E2EE)?
    3. 1.2 核心密码学概念与工具
    4. 第二部分:方案一:静态非对称加密(基础方案)
    5. 2.1 方案概述与流程
    6. 2.2 前端 Vue 实现(使用 node-forge)
    7. 1. 安装依赖
    8. 2. 核心工具类 crypto.js
    9. 3. Vue 组件中使用
    10. 2.3 后端 Java 实现(Spring Boot)
    11. 1. 实体类
    12. 2. Controller 层
    13. 3. WebSocket 配置
    14. 2.4 密钥管理、注册与登录集成
    15. 1. 用户注册/登录时生成密钥
    16. 2. 密钥设置页面
    17. 2.5 方案一优缺点总结
    18. 第三部分:方案二:非对称加密 + 数字签名(增强身份验证)
    19. 3.1 方案概述与流程
    20. 3.2 前端 Vue 实现增强
    21. 发送消息(增加签名)
    22. 接收消息(增加验证)
    23. 3.3 后端 Java 实现增强
    24. 1. 修改消息实体和 DTO
    25. 2. 后端签名验证(可选)
    26. 3.4 方案二优缺点总结
    27. 第四部分:方案三:混合加密系统(推荐生产方案)
    28. 4.1 方案概述与流程
    29. 4.2 前端 Vue 实现(重大修改)
    30. 1. 扩展加密工具类 crypto.js
    31. 2. 会话密钥管理 (Vuex Store)
    32. 3. 修改聊天组件
    33. 4.3 后端 Java 实现
    34. 1. 修改 Controller
    35. 4.4 高级特性:双工密钥协商与 Perfect Forward Secrecy
    36. 使用 ECDH(椭圆曲线 Diffie-Hellman)
    37. 4.5 方案三优缺点总结
    38. 第五部分:部署、测试与安全最佳实践
    39. 5.1 密钥安全存储指南
    40. 5.2 传输安全
    41. 5.3 后端安全考虑
    42. 5.4 测试策略
    43. 5.5 处理常见问题
    44. 第六部分:总结与方案选择

    更多推荐文章

    查看全部
    • ComfyUI Photoshop插件完整教程:5步实现AI绘画工作流
    • 小米智能家居 Miloco 分离式部署指南
    • 绿联云 NAS 配置 WebDAV 实现公网同步
    • 基于 AI 智能体的 C 语言与前端实训项目快速实现指南
    • 基于 FastGPT 与 MCP 协议构建工具增强型智能体
    • 垂直行业大模型的训练策略与实施挑战分析
    • AI 不是机器人:它到底是什么?
    • AI 调参技巧:贝叶斯优化 Optuna
    • 国产数据库新机遇:电科金仓融合技术与 AI 化演进
    • 前端实现视频画中画功能 - 主页面与小窗同步控制
    • llama.cpp 新增 Web UI,本地大模型部署新方案实测
    • RAG 实践指南:使用 Ollama 与 RagFlow 构建本地知识库
    • 大模型分布式训练与高效调参技术实战
    • WebUI 界面交互优化:手机检测系统上传失败重试机制与用户体验改进
    • 基于 OpenClaw 与飞书构建自动化服务器运维机器人
    • Python 属性描述符:原理与 ORM 实战
    • Visual C++ 运行库缺失问题排查与修复指南
    • 本地部署大模型首选:深入解析 llama.cpp 框架
    • LLaMA-Factory 自定义评估指标完整实现指南
    • 前端可视化打印设计器:Vue Print Designer 使用指南

    相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online