山东大学软件学院项目实训-创新实训-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
运行

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

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