LibSignalProtocolSwift 1.3

LibSignalProtocolSwift 1.3

Christoph Hagen维护。



 
依赖
SwiftProtobuf>= 0
Curve25519>= 0
CommonCryptoModule>= 0
 

  • Christoph Hagen

LibSignalProtocolSwift

Signal 协议的 Swift 实现。Signal 协议可用于在同步和异步环境中进行安全、端到端的加密消息传递。它具有许多理想的加密功能,可以处理缺失和顺序错误的消息。Signal 协议被 Signal Messenger 以及 WhatsApp、Facebook、Skype 等应用使用。更多信息请参阅 此处

目的

此 Swift 库仅用于教学目的,以展示 Signal 协议的工作方式。它某种程度上模仿了Signal 协议 C 实现的功能性和结构。

安装

您可以通过Cocoapods来安装 LibSignalProtocolSwift,只需在您的 Podfile 中添加以下内容

pod 'LibSignalProtocolSwift', '~> 1.3'

安装后,可以通过导入来访问 Framework

import SignalProtocol

先决条件

本地存储

Signal 协议需要本地存储消息密钥、身份和其他状态信息。您可以通过实现协议 KeyStore 来提供此功能,该协议需要为单个数据存储实现四个代理。

  • IdentityKeyStore 用于存储和检索身份密钥
  • PreKeyStore 用于存储和检索预密钥
  • SessionStore 用于存储和检索会话
  • SignedPreKeyStore 用于存储和检索签名预密钥

可选

存在一个用于群组更新的功能,其中只有一名管理员可以发送消息,而其他人只能接收。如果您想使用此功能,则需实现 GroupKeyStore 协议,并增加代理 SenderKeyStore 以存储和检索发送者密钥。

示例实现

您可以通过查看 测试实现 获取灵感。

消息交付服务器

存储消息以便检索的服务器需要为每个 SignalAddress 存储以下数据:

  • 公钥身份密钥:设备身份密钥的公钥部分
  • 签名预密钥:当前的签名预密钥
  • 预密钥:一组未签名的预密钥
  • 消息:要发送到该地址的消息,包括发送者

使用方式

在两个设备(两个不同的 SignalAddress)之间建立加密会话的标准流程通常如下:

  • Alice 将她的 Identity 和一个 SignedPreKey 以及一系列未签名的 PreKey 上传到服务器。
  • Bob 从服务器获取一个 PreKeyBundle,其中包含 Alice 的 IdentitySignedPreKey 和一个 PreKey(然后从服务器上删除)。
  • Bob 通过处理 PreKeyBundle 并加密一个上传到服务器的 PreKeyMessage 来创建一个会话。
  • Alice 从服务器接收 Bob 的 PreKeyMessage 并解密信息。
  • Alice 和 Bob 都建立了加密会话。

创建标识和密钥

在安全通信发生之前,至少需要一个用户将创建 PreKeyBundle 所需的所有必要组件上传到服务器。

// Create the identity key ata install time
let identity = try SignalCrypto.generateIdentityKeyPair()

// Store the data in the key store

// Get the public key from the store
let publicKey: Data = try bobStore.getPublicIdentityKey()

// Create pre keys and save them in the store
let preKeys: [Data] = try bobStore.createPreKeys(count: 10)

// Create a signed pre key and save it in the store
let signedPreKey: Data = try bobStore.updateSignedPrekey()

// Upload publicKey, preKeys, and signedPreKey to the server

从 PreKeyBundle 创建会话

假设 Alice(拥有 SignalAddress aliceAddress)想与 Bob(下面有 SignalAddress bobAddress)建立一个会话

// Download Bob's identity, current signedPreKey and one of the preKeys from the server

// Create PreKeyBundle
let preKeyBundle = try SessionPreKeyBundle(
    preKey: preKey,
    signedPreKey: signedPreKey,
    identityKey: identity)

// Create a new session by processing the PreKeyBundle
let session = SessionCipher(store: aliceStore, remoteAddress: bobAddress)
try session.process(preKeyBundle: preKeyBundle)

// The message to encrypt
let message = "Hello Bob, it's Alice".data(using: .utf8)!

// Here Alice can send messages to Bob
let encryptedMessage = try session.encrypt(message)

// Upload the message to the server

从接收到的 PreKeySignalMessage 创建会话

我们继续上面的例子,假设 Bob 收到了来自 Alice 的消息。然后 Bob 可以建立会话

// Get the message from the server

// Create the session
let session = SessionCipher(store: bobStore, remoteAddress: aliceAddress)

// Process the message
let decryptedMessage = try session.decrypt(preKeyMessage)

使用已建立的会话

现在 Alice 和 Bob 都可以随意发送和接收消息。

发送

// Compose a message
let message =  "Hello there".data(using: .utf8)!

// Send message to Bob
let session = SessionCipher(store: aliceStore, remoteAddress: bobAddress)

// Encrypt
let encryptedMessage = try session.encrypt(message)

接收

// Get message from the server

// Receive message from Alice
let session = SessionCipher(store: bobStore, remoteAddress: aliceAddress)

// Decrypt
let decryptedMessage = try session.decrypt(message)

验证身份密钥

为了防止中间人攻击,通过手动比较指纹或者扫描某种代码(例如QR码)来比较身份密钥可能是有益的。库提供了一种方便的方法来完成这个任务。

// Create the fingerprint
let aliceFP = try aliceStore.fingerprint(for: bobAddress, localAddress: aliceAddress)

// Display the string...
let display = fingerprint.displayText

// ... or transmit the scannable data to the other client...
let scanData = try fingerprint.scannable.protoData()

// ... or compare to a received fingerprint
fingerprint.matches(scannedFingerprint)

杂项

客户端标识符

库被设计为允许不同的标识符区分不同用户。《信号地址》结构体被用于此目的,它由一个《String》(例如电话号码)和一个《Int》,设备ID组成。然而,只要有符合《Hashable》、《Equatable》和《CustomStringConvertible》协议的结构体、类或类型,就可以使用不同的结构体。例如,可以使用简单的字符串。

class MyCustomKeyStore: KeyStore {

    typealias Address = String

    ...
}

现在,可以使用《MyCustomKeyStore》

let aliceStore = MyCustomKeyStore()
let session = SessionCipher(store: aliceStore, remoteAddress: "Bob")

提供一个自定义加密实现

任何《SignalCryptoProvider》协议的自定义实现都可以作为协议的加密骨干。这可以通过设置《SignalCrypto》类的静态变量《provider》来实现。

SignalCrypto.provider = MyCustomCryptoProvider()

椭圆曲线函数由部署在libsignal-protocol-c中的相同C代码处理,并由Curve25519框架打包以使函数在Swift中可用。

文档

该项目文档丰富,因为这有助于其他人理解代码。文档是用jazzy(网址:https://github.com/realm/jazzy)创建的,它可生成类似苹果的出色文档。

您可以在项目目录中运行以下命令来(重新)生成文档:

jazzy --min-acl private -a 'Christoph Hagen' -u 'https://github.com/christophhagen' -g 'https://github.com/christophhagen/LibSignalProtocolSwift' -e 'Sources/ProtocolBuffers/*' -o 'Documentation'

免责声明

此代码**不适用于生产使用**!此代码未经过错误检查,也未由专家编写。如果您不完全清楚自己在做什么,请不要实施自己的加密软件。