Advertisement

山东大学软件学院项目实训-创新实训-SDUMeeting(六)

阅读量:

山东大学软件学院项目实训-创新实训-SDUMeeting(六)

端对端加密与密钥交换

端到端加密(end-to-end),是一种只有参与通讯的用户可以读取信息的通信系统。它可以防止潜在的窃听者——包括电信供应商、互联网服务供应商以及该通讯系统的提供者——获取双方通信的明文。
密钥交换(英語:Key exchange,也称key establishment)是密码学中两方交换密钥以允许使用某种加密算法的过程。 如果发送方和接收方希望交换加密消息,则双方都必须配有密钥以加密发送的消息和解密收到的消息。
端对端加密的基础是通信双方拥有相同的对称密钥,且仅有通信双方持有该密钥。传统的https保证的是一对一的消息安全性,即客户端-服务器的安全性,而端对端加密是客户端到客户端的,因此https并不适用于e2ee,因此需要自行设计端对端加密方案。端对端加密的基础是密钥交换,下面介绍基本的三种密钥交换方式

1.diffie-hellman密钥交换

1.爱丽丝和鲍伯协商一个有限循环群 G 和它的一个生成元 g。 (这通常在协议开始很久以前就已经规定好; g是公开的,并可以被所有的攻击者看到。)
2.爱丽丝选择一个随机自然数 a 并且将{\displaystyle g^{a}{\bmod {p}}}g^{a} \bmod{p}发送给鲍伯。
3.鲍伯选择一个随机自然数 b 并且将{\displaystyle g^{b}{\bmod {p}}}g^{b} \bmod{p}发送给爱丽丝。
4.爱丽丝计算{\displaystyle \left(g^{b}\right)^{a}{\bmod {p}}}\left ( g^{b} \right )^{a} \bmod{p}
5.鲍伯计算{\displaystyle \left(g^{a}\right)^{b}{\bmod {p}}}\left ( g^{a} \right )^{b} \bmod{p}
6.爱丽丝和鲍伯就同时协商出群元素{\displaystyle g^{ab}}g^{ab},它可以被用作共享秘密。{\displaystyle \left(g^{b}\right)^{a}}\left ( g^{b} \right )^{a}{\displaystyle \left(g^{a}\right)^{b}}\left ( g^{a} \right )^{b}因为群是乘法交换的。
js实现

复制代码
    var crypto = require('crypto');
    function diffieHellman_client_step1(client){    
    var clientKey = client.generateKeys();
    var clientPublicKey =client.getPublicKey();
    global_p=client.getPrime().toString('hex');
    //console.log(global_p);
    global_g=client.getGenerator().toString('hex');
    return new Array(global_p,global_g,clientPublicKey.toString('hex'));
    
    }
    function diffieHellman_client_step2(serverPublicKey){
    serverPublicKey_buffer=Buffer.from(serverPublicKey,'hex');
    var clientSecret = client.computeSecret(serverPublicKey_buffer);
    return clientSecret.toString('hex');
    }
    function diffieHellman_server(global_p,global_g,clientPublicKey){
    global_p_buffer=Buffer.from(global_p,'hex');
    global_g_buffer=Buffer.from(global_g,'hex');
    clientPublicKey_buffer=Buffer.from(clientPublicKey,'hex');
    var server = crypto.createDiffieHellman(global_p_buffer, global_g_buffer);
    var serverKey = server.generateKeys();
    var serverPublicKey = server.getPublicKey();
    var serverSecret = server.computeSecret(clientPublicKey_buffer);
    return new Array(serverSecret.toString('hex'),serverPublicKey.toString('hex'));
    
    }
    var primeLength = 256; // 素数p的长度
    var generator = 5; // 素数a     
    var client = crypto.createDiffieHellman(primeLength, generator);
    
    pgArray=diffieHellman_client_step1(client);
    //console.log(pgArray[0]);
    //client向server发送pgArray
    serverArray=diffieHellman_server(pgArray[0],pgArray[1],pgArray[2]);
    serverSecretKey=serverArray[0];
    //server向client发送serverSecretKey
    clientSecretKey=diffieHellman_client_step2(serverArray[1]);
    console.log(clientSecretKey);
    console.log(serverSecretKey);
    
    
    AI写代码javascript
    
    运行
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-05-31/mZwTeYoFaqWA1VdzcOui0CDU9876.png)

2.rsa密钥交换

客户端连上服务端
服务端发送 CA 证书给客户端
客户端验证该证书的可靠性
客户端从 CA 证书中取出公钥
客户端生成一个随机密钥 k,并用这个公钥加密得到 k’
客户端把 k’ 发送给服务端
服务端收到 k’ 后用自己的私钥解密得到 k
此时双方都得到了密钥 k,协商完成。

3.ECDHE密钥交换

小红和小明使用 ECDHE 密钥交换算法的过程:

双方事先确定好使用哪种椭圆曲线,和曲线上的基点 G,这两个参数都是公开的;
双方各自随机生成一个随机数作为私钥d,并与基点 G相乘得到公钥Q(Q = dG),此时小红的公私钥为 Q1 和 d1,小明的公私钥为 Q2 和 d2;
双方交换各自的公钥,最后小红计算点(x1,y1) = d1Q2,小明计算点(x2,y2) = d2Q1,由于椭圆曲线上是可以满足乘法交换和结合律,所以 d1Q2 = d1d2G = d2d1G = d2Q1 ,因此双方的 x 坐标是一样的,所以它是共享密钥,也就是会话密钥。
这个过程中,双方的私钥都是随机、临时生成的,都是不公开的,即使根据公开的信息(椭圆曲线、公钥、基点 G)也是很难计算出椭圆曲线上的离散对数(私钥)。

4.双棘轮算法

在密码学中,双棘轮算法(Double Ratchet Algorithm,以前称为Axolotl Ratchet)是由Trevor Perrin和Moxie Marlinspike在2013年开发的密钥管理算法。它可以用作安全协议的一部分。为即时通讯系统提供端到端加密。在初始密钥交换之后,它管理持续更新和维护短期会话密钥。它结合了基于迪菲-赫尔曼密钥交换(DH)的密码棘轮和基于密钥导出函数(英语:Key derivation function) (KDF)的棘轮,例如散列函数,因此被称为双棘轮。

通信双方为每个双棘轮消息派生出新密钥,使得旧的密钥不能从新的密钥计算得出。通信双方还将在消息中附加迪菲-赫尔曼公钥值。将迪菲-赫尔曼计算的结果将被混合到派生出的密钥中,使得新的密钥不能从旧的密钥计算得出。在一方密钥泄露的情况下,这些属性为泄漏前或泄漏后加密的消息提供一些保护。

(1)双棘轮算法聊天实现

javascript代码

复制代码
    import * as fs from 'fs';
    
    import {
      AsymmetricRatchet,
      IJsonIdentity,
      Identity,
      PreKeyBundleProtocol,
      PreKeyMessageProtocol,
      setEngine
    } from "2key-ratchet";
    
    import { Convert } from 'pvtsutils';
    import { Crypto } from "@peculiar/webcrypto";
    
    const generateKeys = async (id=0, returnBuffer=false) => {
      let identityKey: Identity;
      try {
    identityKey = await Identity.create(id, 1);
    let preKeyBundle = new PreKeyBundleProtocol();
    await preKeyBundle.identity.fill(identityKey);
    preKeyBundle.registrationId = identityKey.id;
    const preKey = identityKey.signedPreKeys[0];
    preKeyBundle.preKeySigned.id = 0;
    preKeyBundle.preKeySigned.key = preKey.publicKey;
    await preKeyBundle.preKeySigned.sign(identityKey.signingKey.privateKey);
    let keyArrayBuffer: ArrayBuffer = await preKeyBundle.exportProto();
    let uint16String = new Uint16Array(keyArrayBuffer).toString();
    fs.writeFileSync('alice.json', JSON.stringify(await identityKey.toJSON()));
    return {
      identityKey: await identityKey.toJSON(),
      preKeyBuffer: returnBuffer ? keyArrayBuffer : Buffer.from(uint16String).toString('base64')
    };
      } catch (error) {
    throw error;
      }
    }
    
    const generateKeysForUsers = async () => {
      try {
    let AliceKeySet = await generateKeys(16453);
    console.log('Alice\'s Key Set: ', AliceKeySet);
    // let BobKeySet = await generateKeys();
    // console.log('Bob\'s Key Set:', BobKeySet);
      } catch (error) {
    throw error;
      }
    }
    
    const encryptMessage = async (receiversPreKey: string | ArrayBuffer, message: string, processBuffers=false) => {
      try {
    let preKeyBundle;
    if (processBuffers) {
      preKeyBundle = await PreKeyBundleProtocol.importProto(receiversPreKey as ArrayBuffer);
    } else {
      preKeyBundle = await getPreKeyBundle(receiversPreKey as string);
    }
    const senderKey: Identity = await getIdentityKey();
    const cipherObject = await AsymmetricRatchet.create(senderKey, preKeyBundle);
    const preKeyMessage = await cipherObject.encrypt(Convert.FromUtf8String(message));
    const encryptedMessageBuffer = await preKeyMessage.exportProto();
    let encryptedMessage = Convert.ToHex(encryptedMessageBuffer);
    console.log("Encrypted message: ", encryptedMessage);
    return processBuffers ? encryptedMessageBuffer : encryptedMessage;
      } catch (error) {
    throw error;
      }
    }
    
    const decryptMessage = async (messageProtocolEncrypted: string | ArrayBuffer, identityJson=null, processBuffers=false) => {
      try {
    let messageProtocol;
    if (processBuffers) {
      messageProtocol = await PreKeyMessageProtocol.importProto(messageProtocolEncrypted as ArrayBuffer);
    } else {
      messageProtocol = await getPreKeyMessageBundle(messageProtocolEncrypted as string);
    }
    const receiverKey = identityJson ? identityJson : JSON.parse(fs.readFileSync('alice.json', 'utf8')) as IJsonIdentity;
    let receiverKeyIdentity = await Identity.fromJSON(receiverKey);
    const cipherObject = await AsymmetricRatchet.create(receiverKeyIdentity, messageProtocol);
    const signedMessage = await cipherObject.decrypt(messageProtocol.signedMessage);
    const message = Convert.ToUtf8String(signedMessage);
    console.log("Decrypted message: ", message);
    return message;
      } catch (error) {
    throw error;
      }
    }
    
    const getIdentityKey = async () => {
      const { identityKey } = await generateKeys();
      return Identity.fromJSON(identityKey);
    }
    
    const getPreKeyBundle = (preKeyString: string) => {
      const preKeyDecodedArray = Buffer
      .from(preKeyString, 'base64')
      .toString('binary')
      .split(',')
      .map(item => parseInt(item));
      const preKeyBuffer = Uint16Array.of(...preKeyDecodedArray).buffer;
      return PreKeyBundleProtocol.importProto(preKeyBuffer);
    }
    
    const getPreKeyMessageBundle = (messageString: string) => {
      const messageDecodedArray = Buffer
      .from(messageString, 'base64')
      .toString('binary')
      .split(',')
      .map(item => parseInt(item));
      const messageBuffer = Uint16Array.of(...messageDecodedArray).buffer;
      return PreKeyMessageProtocol.importProto(messageBuffer);
    }
    
    const main = async (args) => {
      args = args.slice(2);
    
      const crypto = new Crypto();
      setEngine("@peculiar/webcrypto", crypto);
    
      switch (args[0]) {
    case 'generate':
      generateKeysForUsers();
      break;
    case 'encrypt':
      encryptMessage(args[1], args[2]);
      break;
    case 'decrypt':
      decryptMessage(args[1]);
      break;
    case 'all':
      const {identityKey, preKeyBuffer} = await generateKeys(16453, true);
      console.log("Message to send: ", "Hello there")
      const encryptedMessage = await encryptMessage(preKeyBuffer, "Hello there", true);
      await decryptMessage(encryptedMessage, identityKey, true);
      //console.log("identityKey: ",identityKey)
      //console.log("preKeyBuffer:",preKeyBuffer)
      break;
    default:
      break;
      }
    };
    
    main(process.argv);
    
    
    AI写代码js
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-05-31/v0oKE5k92qstT7Jrn3WymRXFLZue.png)

(1)signal算法

Signal协议(英语:Signal Protocal,以前称为 TextSecure 协议)是一种非互联的加密协议,可被用于为语音呼叫、视讯呼叫和即时消息会话提供端到端加密 。

(2)视频会议中的双棘轮算法

参考实现 https://github.com/jitsi/lib-jitsi-meet/blob/master/doc/e2ee.md

信令

每个参与者将拥有一个随机生成的密钥,用于加密媒体。 密钥分布在 其他参与者(因此他们可以解密媒体)通过 E2EE 通道 与 Olm 。

密钥轮换

每次参与者离开时,每个参与者的密钥都会轮换(生成一个新的随机密钥)。 这个新密钥通过 E2EE Olm 通道发送给所有其他参与者。

钥匙棘轮

当另一个参与者加入时,每个参与者都会棘轮键。 新生成的密钥没有分发,因为 每个参与者都可以通过棘轮获得它。

与 SFrame 4.3.5.1 当我们找不到有效的身份验证标签时,我们会尝试将密钥向前棘轮。 请注意,我们只更新 当我们找到有效签名时的一组密钥,可以避免使用无效签名的拒绝服务攻击。

全部评论 (0)

还没有任何评论哟~