Open z-950 opened 3 years ago
非常不错的文章!想请教下群聊如何做 e2e 加密呢?
请问有完整的使用demo吗 我搞了很久 一点头绪都没有 直接把这些代码拷过来执行 很多报错 比如
KeyHelper.generatePreKey(keyId).then(function (preKey) {
store.storePreKey(preKey.keyId, preKey.keyPair);
});
这里的keyId是从哪里来的 是需要自己提供 还是怎么样
@dzcpy 没有做过群聊加密。js的实现库内缺少群聊加密的方法。但其java版本有,可以参考然后自行实现。
@1111mp 没有demo。此文写于一年前。按照我的记忆和查看依赖库的部分代码,keyId是自行生成的,不同用户的keyId可以重复,keyId主要用于存取key。
@z-950 谢谢回复。刚入手,用都不会用。。。不过在社区找到了一个大佬的帖子,应该有帮助。 @dzcpy https://community.signalusers.org/t/an-unofficial-signal-chatbot-and-javascript-library/4767 群聊的也实现了 目前我还是没有入门成功 唉 但是他是封装好的 用的signal的服务 还是得自己实现
@1111mp 运行例子确实难找。实际上难点还有后端部署和网络交互接口。客户端方面也许还可以参考测试代码。
@z-950 这个不能单纯的用来做消息的加密和解密吗?比如我有自己的IM服务 现在只差一个端到端加密 我就想用这个加密一下 发送的消息的字符串。这样可行吗?
@1111mp 因为我没有完整实现过,所以不能肯定的告诉你是否可以。但是理论上,后端也需要相关的功能。端到端加密为了安全性,设计了特定的密/公钥交换规则,后端一定需要实现这部分内容,这要求你对这些规则有所了解。自然,我不太清楚全部的流程。Signal有文档,你可以结合着它后端的例子看。 如果你不需要如此高的安全性,你大可以自行实现一种协议。
@z-950 官方的文档就是看不懂啊 写的都一笔带过 然后自己就在摸索 根据这个人的用法 issues32 目前大概知道怎么去使用了 服务器需要保存一些pubKey 然后客户端根据这个pubKey和自己的priKey 建立回话 然后加密解密可以做到 但是官方文档说 ‘客户端会生成单个已签名的PreKey以及大量未签名的PreKey,并将它们全部传输到服务器。’这个不是很懂
const preKey = await KeyHelper.generatePreKey(keyId);
这个生成的preKey 本身就已经做到了吗 还是说需要执行大量的这个方法去存到服务器 然后目前只简单了解到这里了 群聊的 想都不敢想
这个生成的preKey 本身就已经做到了吗 还是说需要执行大量的这个方法去存到服务器
@1111mp 每次只生成一个preKey。自己按需生成。
每次只生成一个preKey。自己按需生成。 @z-950 可以这样理解吗
const promise = sessionBuilder.processPreKey({ registrationId, identityKey: identityKeyPair.pubKey, signedPreKey: { ...signedPreKey, publicKey: signedPreKey.keyPair.pubKey }, preKey: { ...preKey, publicKey: preKey.keyPair.pubKey } });
验证的时候需要的这些参数 都是在跟客户端对应的 安装客户端的时候生成一次就行?
@z-950
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bob</title>
<script type="text/javascript" src="./libsignal-protocol.js"></script>
<script type="text/javascript" src="./store.js"></script>
</head>
<body>
<script>
var KeyHelper = libsignal.KeyHelper;
var store = new SignalProtocolStore();
generateKeys(123, function (aliceKeys) {
store.put('identityKey', aliceKeys.identityKeyPair);
store.put('registrationId', aliceKeys.registrationId);
console.log('aliceKeys.registrationId', aliceKeys.registrationId)
generateKeys(456, function (bobKeys) {
console.log('bobKeys.registrationId', bobKeys.registrationId)
var recipientId = "daniel123";
var deviceId = 0;
var address = new libsignal.SignalProtocolAddress(recipientId, deviceId);
// Instantiate a SessionBuilder for a remote recipientId + deviceId tuple.
var sessionBuilder = new libsignal.SessionBuilder(store, address);
// Process a prekey fetched from the server. Returns a promise that resolves
// once a session is created and saved in the store, or rejects if the
// identityKey differs from a previously seen identity for this address.
var promise = sessionBuilder.processPreKey({
registrationId: bobKeys.registrationId,
identityKey: bobKeys.identityKeyPair.pubKey,
signedPreKey: {
keyId: bobKeys.signedPreKey.keyId,
publicKey: bobKeys.signedPreKey.keyPair.pubKey,
signature: bobKeys.signedPreKey.signature
},
preKey: {
keyId: bobKeys.preKey.keyId,
publicKey: bobKeys.preKey.keyPair.pubKey
}
});
promise.then(function onsuccess() {
// encrypt messages
console.log("Vamo a encriptar");
});
promise.catch(function onerror(error) {
// handle identity key conflict
console.log(error);
});
const plaintext = "Hello world";
// let ciphertext;
const sessionCipher = new libsignal.SessionCipher(store, address);
console.log(sessionCipher)
sessionCipher.encrypt(plaintext).then(function (ciphertext) {
// ciphertext -> { type: <Number>, body: <string> }
console.log('ciphertext:', ciphertext)
// ciphertext = ciphertext;
// handle(ciphertext.type, ciphertext.body);
// var sessionCipher = new libsignal.SessionCipher(store, address);
// sessionCipher.decryptWhisperMessage(ciphertext.body).then(function (plaintext) {
// // handle plaintext ArrayBuffer
// console.log(plaintext)
// });
var addressCopy = new libsignal.SignalProtocolAddress(recipientId, deviceId);
var sessionCipherCopy = new libsignal.SessionCipher(store, addressCopy);
// 首先建立一个新的会话来解密PreKeyWhisperMessage。
// 返回一个承诺,该承诺将在消息解密时解析,或者如果identityKey与该地址先前看到的身份不同,则拒绝。
sessionCipherCopy.decryptPreKeyWhisperMessage(ciphertext.body).then(function (plaintext) {
// handle plaintext ArrayBuffer
// 处理纯文本ArrayBuffer
console.log(plaintext)
}).catch(function (error) {
// handle identity key conflict
// 处理身份密钥冲突
console.log(error)
});
});
});
});
function generateKeys(keyId, callback) {
var keys = {};
keys.registrationId = KeyHelper.generateRegistrationId();
// Store registrationId somewhere durable and safe.
KeyHelper.generateIdentityKeyPair().then(function (identityKeyPair) {
// keyPair -> { pubKey: ArrayBuffer, privKey: ArrayBuffer }
// Store identityKeyPair somewhere durable and safe.
keys.identityKeyPair = identityKeyPair;
KeyHelper.generatePreKey(keyId).then(function (preKey) {
store.storePreKey(preKey.keyId, preKey.keyPair);
keys.preKey = preKey;
KeyHelper.generateSignedPreKey(identityKeyPair, keyId).then(function (signedPreKey) {
store.storeSignedPreKey(signedPreKey.keyId, signedPreKey.keyPair);
keys.signedPreKey = signedPreKey;
callback(keys);
});
});
});
}
</script>
</body>
</html>
我这么使用的时候 加密成功了 解密一直失败 解密的时候
var addressCopy = new libsignal.SignalProtocolAddress(recipientId, deviceId);
var sessionCipherCopy = new libsignal.SessionCipher(store, addressCopy);
// 首先建立一个新的会话来解密PreKeyWhisperMessage。
// 返回一个承诺,该承诺将在消息解密时解析,或者如果identityKey与该地址先前看到的身份不同,则拒绝。
sessionCipherCopy.decryptPreKeyWhisperMessage(ciphertext.body).then(function (plaintext) {
// handle plaintext ArrayBuffer
// 处理纯文本ArrayBuffer
console.log(plaintext)
}).catch(function (error) {
// handle identity key conflict
// 处理身份密钥冲突
console.log(error)
});
addressCopy的recipientId和deviceId不是用的同一个吗 能指教一下吗
验证的时候需要的这些参数 都是在跟客户端对应的 安装客户端的时候生成一次就行?
@1111mp 按照介绍,安装时都需要生成,并且持久化储存。后续聊天需要使用新生成的preKey。
@z-950 好的 谢谢 解密刚刚已经成功了 https://github.com/signalapp/libsignal-protocol-javascript/issues/41 非常感谢
@z-950 就是 每发一条消息 都需要生成 一个preKey 每个消息的preKey都不一样 这样吗
各位如果感兴趣的话,不如拉个群一起研究?这块我肯定是要实现一套方案出来的。这样多个人还能多点思路,简化开发
@dzcpy 我自己刚建了一个qq群,691383606 有时间一起沟通下
@dzcpy 看你也没有回复,我在这里分享一下,我自己理解的一种端到端加密的方案:
跟signal protocal的安全性肯定没法比,但是它实在太难了,而且几乎没有相关的基础的文档,太费劲了。有点力不从心。
不过在整理这个的时候,我好想对signal protocol有了更深的理解,后续我看能不能整理出一个完整的简单的从0到1的例子出来。祝我这段时间少掉点头发吧。唉。
想问下 加密解密都通了 但是这里app说 要本地存储session 用来存储棘轮的状态 方便未读消息解密 那这里拿到session 要怎么把他嵌入 原生代码 逻辑里 能否提供下
signal协议是一种棘轮式前向保密协议,适用于同步和异步消息传递环境。
signal协议的js实现:
libsignal-protocol-javascript
注意
此为例子。该js库的实现不完整,缺少了原始版本的部分方法,见issue
概念
使用
引入
dist/libsignal-protocol.js
const registrationId = KeyHelper.generateRegistrationId(); // Store registrationId somewhere durable and safe. // store需要自行实现,可参考最后的例子 // 可以储存在浏览器的localStorage或者indexDB。如果浏览器不安全则毫无办法 // 为2.建立会话中同一个store
KeyHelper.generateIdentityKeyPair().then(function(identityKeyPair) { // keyPair -> { pubKey: ArrayBuffer, privKey: ArrayBuffer } // keyPair格式 // Store identityKeyPair somewhere durable and safe. });
KeyHelper.generatePreKey(keyId).then(function(preKey) { store.storePreKey(preKey.keyId, preKey.keyPair); });
KeyHelper.generateSignedPreKey(identityKeyPair, keyId).then(function(signedPreKey) { store.storeSignedPreKey(signedPreKey.keyId, signedPreKey.keyPair); });
// Register preKeys and signedPreKey with the server // 向服务器注册,服务器有signal相关接口. // 应为2.建立会话
// 使用现有会话解密WhisperMessage // Decrypt a normal message using an existing session const sessionCipher = new SessionCipher(store, address); sessionCipher.decryptWhisperMessage(ciphertext).then(function(plaintext) { // handle plaintext ArrayBuffer });