65.9K
CodeProject 正在变化。 阅读更多。
Home

离线 (OTR) 安全协议

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (36投票s)

2013 年 8 月 28 日

CPOL

10分钟阅读

viewsIcon

93760

downloadIcon

145794

OTR 协议的 C# 实现。

引言

我需要一个 C# OTR 库,用于在 Windows、Linux 和 Android 平台上运行的即时通讯客户端,但找不到任何现有的库。因此,我决定自己开发一个。附件中的压缩文件包含该库和一个演示如何使用该库的控制台应用程序。OTR 协议以及该库的接口和事件函数将在下文描述。该库与实现协议版本 2 和 3 的 OTR 客户端兼容 [1], [2]。一个实现此库的客户端成功地与 Pidgin [3] 和 Spark IM [4] 客户端建立了 OTR 会话。

请注意,此库的大部分加密功能使用 BouncyCastle 库。

背景

离线记录(OTR)[5] 是一种为实时即时通讯(IM)提供安全保障的协议。它确保以下几点:

  • 保密性:消息经过加密。
  • 身份验证:您就是您所声称的身份。您发送的消息可以由您的聊天伙伴(好友)验证,反之亦然。
  • 完美前向保密性:发送的每条即时消息都使用不同的加密密钥进行加密,使用后即被丢弃。泄露单个加密密钥不会影响其他已发送或将来要发送消息的保密性。此外,每条消息都使用不同的消息验证码(MAC)密钥进行身份验证。
  • 可否认性:已使用且不会再次使用的 MAC 密钥包含在传出消息中。其思想是,由于这些密钥是公开的,任何人都可以创建这些密钥(包括您的聊天伙伴),因此可以伪造消息。

除了上述功能,OTR 还定义了一个社会主义百万富翁协议(SMP),可用于在进行中的对话中检测中间人攻击。为了使 SMP 过程成功完成,您和您的聊天伙伴必须拥有一个只有您和TA知道的秘密。SMP 过程的失败表明您与聊天伙伴之间的加密会话已被第三方劫持。

使用代码

假设 Alice 想和她的朋友 Bob 建立一个 OTR 会话。为此,她必须向 Bob 请求一个 OTR 会话,Bob 收到此请求后便开始正式的 OTR 会话。下面的代码展示了 Alice 如何请求一个 OTR 会话。

 /* Declare OTR variables*/
OTRSessionManager _alice_otr_session_manager = null;

string _my_unique_id = "alice";
string _my_buddy_unique_id = "bob";


/* Create OTR session and Request OTR session */
_alice_otr_session_manager = new OTRSessionManager(_my_unique_id);

_alice_otr_session_manager.OnOTREvent += new OTREventHandler(OnAliceOTRMangerEventHandler); 

_alice_otr_session_manager.CreateOTRSession(_my_buddy_unique_id);

_alice_otr_session_manager.RequestOTRSession(_my_buddy_unique_id, OTRSessionManager.GetSupportedOTRVersionList()[0]);

请注意,OTR 会话管理器使用您的唯一 ID 进行初始化。OTR 管理器初始化后,必须连接到 OTR 事件处理程序。有关这些事件类型的描述,请参阅 OTR 事件部分。

与您通信的每个好友建立的 OTR 会话都必须使用该好友的唯一 ID 创建。完成此操作后,可以通过该唯一 ID 引用会话。换句话说,您好友的唯一 ID 兼作会话 ID。

请求 OTR 会话涉及调用 RequestOTRSession 函数,并向其传递您好友的 ID 和您希望使用的 OTR 版本。此库支持 OTR 协议的第 2 和第 3 版本。此库支持的版本包含在一个字符串列表中,可以通过调用 OTRSessionManagerGetSupportedOTRVersionList 静态函数来访问。

OTR 使用数字签名算法(DSA)公钥作为身份验证和 SMP 过程的一部分。如果客户端已经有 DSA 密钥,那么客户端可以将 DSA 公钥和私钥参数(即 P、Q、G 和 X)传递给 CreateOTRSession 函数。DSA 密钥参数在传递给相关函数之前必须格式化为 DSAKeyParams 对象。如果客户端没有 DSA 密钥,此库将随机创建一个。有关如何实现此功能的示例,请参阅随附的 OTRLibTest 控制台应用程序。

可以通过调用 GetSessionDSAHexParams 函数获取您当前的 DSA 密钥。它返回一个包含密钥的 P、Q、G 和 X 元素的 DSAKeyParams 对象。

OTR 规范定义了一个名为“指纹”的对象。指纹是使用 DSA 公钥参数(即 P、Q、G 和 Y)作为输入计算得出的。要以十六进制字符串形式检索您的指纹,请调用 GetSessionDSAFingerPrint 函数。同样,要检索您好友的指纹,请调用 GetMyBuddyFingerPrint 函数。

/* Pass received OTR session Data to OTR session manager */
/* Assume otr_message_string is the OTR message received from bob over the network */

_alice_otr_session_manager.ProcessOTRMessage(_my_buddy_unique_id, otr_message_string);

客户端收到的所有 OTR 会话字符串消息都传递给 ProcessOTRMessage 函数进行进一步处理,如上所示。

/* Encrypt and send a message to Bob */
/* Assume that message_string is the message to be encrypted and sent to bob*/

_alice_otr_session_manager.EncryptMessage(_my_buddy_unique_id, message_string);

要加密的消息被传递给 EncryptMessage 函数。如果加密过程中没有错误,则会触发 OTR SEND 事件。有关此事件的详细信息,请参阅下面的 OTR 事件部分。

还有四个 EncryptMessage 函数接受参数,这些参数允许 (1) 填充加密数据以隐藏消息长度,或 (2) 允许间接启动 SMP 进程,或 (1) 和 (2) 同时进行。一个相关的函数是 EncryptFragments 函数,它允许在传输给好友之前对大消息进行加密和分段。好友应该能够重新组装这些消息片段,并将完整的消息呈现给用户的客户端。

OTR 事件

OTR 管理器有 9 个主要事件和 4 个子事件。请注意,OTR 管理器可以通过简单地读取事件的 GetSessionID 函数来区分由多个 OTR 会话触发的事件。会话 ID 字符串将与传递给 CreateSession 函数的 my_buddy_unique_id 字符串相对应。这些事件描述如下:

ERROR 事件:此类事件表示 OTR 会话中发生了错误。此事件可能是致命的或良性的。一旦收到可能致命的事件通知,IM 接口有责任关闭 OTR 会话。致命事件的一个示例是客户端无法验证好友的 DSA 公钥。在这种情况下,建议关闭 OTR 会话。关闭 OTR 会话会通知好友此情况。非致命事件的一个示例是消息无法解密。发生这种情况时,客户端会收到通知,并自动向好友发送错误消息。此类事件不需要关闭 OTR 会话。

此事件触发时,可以通过调用事件的 GetErrorMessageGetErrorVerbose 函数来读取错误消息。

Debug 事件:此事件输出消息,让您了解 OTR 会话内部正在发生的事情,例如,接收和发送的消息类型。可以通过调用事件的 GetMessage 函数来读取调试消息。可以通过将 CreateOTRSession 函数中的调试变量设置为 true 来启用调试事件。

MESSAGE 事件:此事件表示成功解密收到的 OTR 加密消息。可以通过调用事件的 GetMessage 函数来读取解密后的消息。回想一下背景部分,旧的 MAC 密钥会随传出消息一起传输。当此事件触发时,可以通过调用事件的 GetOldMacKeys 函数来检索旧的 MAC 密钥(以字节为单位)。请注意,每个 MAC 密钥长 20 字节。如果 GetOldMacKeys 函数返回 40 字节,则表示其中包含两个 MAC 密钥。

EXTRA_KEY_REQUEST 事件:此事件表示好友希望您俩使用额外的 AES 对称密钥用于某种目的。可以通过调用 GetExtraSymmetricKey 会话函数来检索 AES 密钥字节。您可以通过调用 RequestExtraKeyUse 函数来请求使用额外的 AES 对称密钥。此事件仅对 OTR 协议版本 3 有效。

SEND 事件:当 OTR 会话想要向好友发送 OTR 消息时,它会触发 SEND 事件。要发送的数据通过调用事件的 GetMessage 函数来检索。

READY 事件:READY 事件表示两个 IM 客户端之间成功建立加密 OTR 会话。请注意,在此事件触发之前,不能发送任何加密的对话数据。在此事件触发之前尝试加密和发送消息将触发错误事件。可以通过调用 GetSessionMessageState 函数获取 OTR 会话的当前状态。如果 OTR 会话不在 MSG_STATE_ENCRYPTED 状态,则表示它尚未准备好。

CLOSED 事件:此事件表示 OTR 会话的关闭。这通常是响应用户调用 CloseAllSessionsCloseSession 函数。此事件也由从好友收到的关闭会话消息触发。

HEART_BEAT 事件:此事件表示好友想知道您是否仍然在线并愿意通过 OTR 加密会话进行通信。这通常在您闲置一段时间后发送。您可以通过向好友发送消息或简单地使用 SendHeartBeat 函数发送您自己的心跳来响应。

SMP_MESSAGE 事件:此事件在 SMP 过程完成后触发。此事件有四个子事件:

  • SUCCEEDED:SMP 过程成功完成,即 OTR 会话未被劫持。
  • FAILED:SMP 过程未成功,即会话可能已被劫持。
  • ABORT:正在进行的 SMP 过程已中止。
  • SEND:此事件永远不应该被接收,因为它仅在 OTR 会话内部使用。

要检测已触发的 SMP 子事件,请调用主事件的 GetSMPEvent 函数。

SMP 过程可以直接通过调用 StartSMP 函数启动,也可以通过将任何 EncryptMessage 消息函数的 start_smp 参数设置为 true 来间接启动。SMP 过程的共享用户秘密输入可以通过将秘密字符串传递给 SetSMPUserSecret 函数来设置。当前共享秘密可以通过调用 GetSMPUserSecret 来检索。默认的共享秘密字符串是 "小猫真有趣"。 :D

要强制 OTR 将 SMP 消息作为片段发送,请调用 SetSMPFragLength 函数来设置每个片段的最大长度。要获取 SMP 消息的当前片段长度,请调用 GetSMPFragLength 函数。

要停止正在进行的 SMP 过程,请调用 AbortSMP

看点

SMP 过程涉及处理大整数(1536 位),因此速度可能较慢。当我在 Windows 7 平台上,在实现此库的客户端接口和 Pidgin 即时消息客户端 [3] 之间运行它时,该过程平均需要 27 秒才能完成。这在尝试在有限设备(例如智能手机等)上运行 SMP 时会产生影响。实现的 SMP 代码包含在 SMPManager.cs 文件中。如果有人能改进此代码以使其更快并分享此改进,那将不胜感激。

SMP 执行速度现已提高。现在它在 7 秒内运行。我意识到我使用的是 1536 字节而不是最近更新的规范 [6] 规定的 192 字节(即 1536 位)整数指数。请忽略上面划掉的兴趣点 :)

Mohamed Mansour 已将该库移植到 Windows 8/Phone 平台。在此处找到它:此处

历史

  • 2013年8月28日:第一个版本。
  • 2013年9月18日:向库中添加了 SetSMPFragLengthGetSMPFragLength 函数,并更新了文章以反映这些更改。
  • 2013年9月20日:改进了 SMP 的运行时。请参阅兴趣点部分。
  • 2013年11月10日:修正了版本 3 中额外 AES 密钥的计算点。
  • 2014年1月27日:更新了兴趣点部分,添加了指向该库的 Windows 8/Phone 移植版本的链接。
  • 2014年4月4日:添加了该库的 Mono 构建版本。
  • 2014年4月29日:解决了当一方全程对话时密钥同步丢失的问题。

参考文献

  1. OTR 版本 2
  2. OTR 版本 3
  3. Pidgin
  4. Spark
  5. OTR 主页
  6. 更新的规范
© . All rights reserved.