Debian/Ubuntu用户使用基于XMPP即时通信协议的OTR保护隐私的标准化部署流程

Debian/Ubuntu用户使用基于XMPP即时通信协议的OTR保护隐私的标准化部署流程

OTR 协议的基础

Diffie-Hellman(DH)密钥交换

Diffie-Hellman 密钥交换在自然数和某个有限循环群中进行。 以下用小写字母表示整数,用大写字母表示有限循环群的元素,”==”表示恒等。

有限循环群的性质

有限循环群内只有有限个元素,其元素个数 n 称为阶。

基本群运算 P 服从交换律和结合率:P(A,B)==P(B,A), P(P(A,B),C)==P(A,P(B,C))。

群内存在单位元 E,对群内任意元素满足 P(A,E)=A;因此对群内任一元素A都存在唯一的逆元素(-A)使得 P(A,-A)=E。单位元 E 的逆元素即为其自身。

将对同一元素 A 反复进行 m 次 P 运算定义为一新运算 M(m,A);并定义 M(-m,A)=-M(m,A),则依定义 P(M(x,A),M(y,A))=M(x+y,A),且 M(0,A)=E。

群内存在非单位元的生成元(一般不只一个) G 使得 M(n,G)=E,且可证 M(0,G)=E, M(1,G), M(2,G)……M(n-1,G) 即为群的全部元素。

对任意元素 A、自然数 x、y 可证总有 M(y,M(x,A))==M(x,M(y,A))==M(xy,A),DH 便是建立于此性质之上。

实际用于密码工程的循环群需要满足已知 m 和 A 求 M(m,A) 比较简单,而已知 A 和 B 求 m 使得 M(m,A)=B(一般称为该循环群中的离散对数问题)很难。

常用于密码工程的群有模素数 p 的乘法群(运算 P 为模乘法;M 为模乘幂)和离散椭圆曲线上点的加法群(运算 P 为点的模加法;M 为点和整数的模乘法)。

密钥交换过程

以下称 A=M(a,G) 为整数 a 在循环群中关于生成元 G 的像,而 a 为 A 的源。

  1. Alice 和 Bob 商定协商打算使用的,满足密码工程要求的循环群,及其生成元之一 G。
  2. Alice 和 Bob 各自生成随机整数 a 和 b 作为源,然后各自计算其像 A=M(a,G) 和 B=M(b,G) 并发送给对方。
  3. Alice 和 Bob 各自使用对方发来的像和自己手中的源计算共享秘密:S=M(a,B)=M(a,M(b,G))==M(ab,G)==M(b,M(a,G))=M(b,A)。

至此双方得到了相同的共享秘密 S,之后便可从 S 使用相同的算法派生出用于对称加密算法的密钥。

注意出现在通信链路上的信息只有循环群本身的信息,及像 A 和 B,而在满足密码工程要求的循环群中由像反推源(即该循环群中的离散对数问题)是很困难的,从而保证安全性。

密码学散列算法

此类算法接受任意长度的输入打乱并混合重组,产生固定长度的输出,而靠输出反推输入极为困难。一般设计为输入的极小变化会引起输出的极大变化,但算法是确定的,相同的输入产生的输出一定相同。

非对称签名算法

DH 密钥交换本身无法抵御中间人攻击——即你无法确定你收到的像的确是你打算与之通信的对方发来的。解决这个问题的一般方法是使用非对称签名算法。 非对称签名算法使用成对的公私钥,且由公钥推导私钥十分困难。私钥被严格保密,只有持有者知道;公钥则可以发送给任何想要和你通信的人。 对一段数据使用散列算法,再将其结果用私钥处理,所得即为用私钥对该数据生成的数字签名,拿到这段数据和签名值者均可使用散列算法和公钥验证:

  1. 数据没有改变。
  2. 签名经对应的私钥生成,不可抵赖。

对称加密算法

明文经密钥处理后得到密文(一般其长度与明文相同),用同样的密钥可以简单完美地还原明文,但不知道密钥者很难做到这点。好的对称加密算法也设计成输入的极小变化会引起输出的极大变化。

消息认证算法(MAC)

消息认证算法和数字签名算法有类似之处,不过它的工作方式是对称的:任意一段数据经过认证钥处理后得到对应的定长消息认证码,但由消息认证码反推出原始数据和认证钥、由消息认证码和原始数据推出认证钥都很困难。因此只有持有相同认证钥的实体可以检验数据的完整性,但与数字签名不同,任何持有认证钥的实体都能生成相同的消息认证码,因此不能用于认证身份。

消息认证算法常在对称加密算法或密码学散列算法的基础上设计,其中基于散列的消息认证算法被称为 HMAC。

OTR协议简介

不留记录即时通讯 (Off-the-Record Messaging,缩写为OTR)是一种在任何能可靠地双向传输文本消息的低层协议的基础上提供端到端加密支持的安全协议。该协议提供如下特性:

  1. 完备前向安全性:即便有人监听了全部会话并拿到了保存在端点的长寿命密钥也不能恢复会话的内容。
  2. 完整性和可抵赖性的统一:作为通信参与方,你可以确定某条消息的确是对方发出的;但中间的监听者却无法作证谁确实说过什么。

OTR 协议使用一固定的模乘法群实现 DH,其模为 RFC 3526 中定义的 1536-bit 素数,使用的生成元是其原根 2,以简化计算。

因为 DH 密钥交换本身无法抵御中间人攻击而需要使用身份认证技术,最新版本的 OTR 使用 DSA 算法实现数字签名来做身份认证。

但是,基于数字签名的身份认证是无法抵赖的。因此传统的先明文传公钥再发用私钥签名的像的做法(典型的如当前的TLS)会把身份(公钥)泄露给中间可能存在的监听者。

为了在 DH 过程中实现完整性和可抵赖性的统一,OTR 在握手阶段采取“先作无认证的 DH,再在共享秘密派生出的对称加密通道中认证身份、像和共享秘密本身的派生值”的做法。因为身份认证过程本身是加密的,所以身份信息不会泄露给被动监听者;虽然靠中间人攻击可以骗到公钥,但由于像和共享秘密的派生值也被认证,而中间人几乎不可能在两端协商出相同的共享秘密,所以中间人攻击几乎一定会破坏认证过程。因此成功的 OTR 握手能保证有且只有通信参与方在握手过程中拿到了对方的公钥,而实现完整性和可抵赖性的统一——握手仅对握手参与方才有不可抵赖性。

OTR 使用 AES 做对称加密;使用 HMAC-SHA256 实现消息认证。

握手完成后,双方各自拥有自己的源和对方发来的像,由此可各自计算出相同的共享秘密。

OTR 实现完备前向安全性的方式是极其多疑的:

  • 发送方由己方的源和对方的像算出共享秘密,并派生出加密钥,再由加密钥派生出认证钥,用加密钥加密消息,随机生成一个新的 DH 源值替换现有的源并计算出新的像,将双方的像序号、己方的新像、密文等组合在一起发给对方,附加通过认证钥为上述内容生成的消息认证码,也一并发给对方,随后更新己方的像序号。

  • 接收方收到消息后,同样算出共享秘密并派生出加密钥和认证钥,用认证钥确认消息的完整性,用加密钥解密密文,并用发来的数据更新缓存的对方的像及其序号。可见一方的 DH 参数(源和对应的像)是在发送过程中更新的,如果某方在一段时间内只接收而不发送消息,则其 DH 参数保持不变。

可见 OTR 每发一条新消息都会为下一条消息重新做 DH 协商,从而每条消息都使用不同的加密钥加密,以此逼近一次性便条的安全性。

而为了在此处实现完整性和可抵赖性的统一,自上一次发言以来所有收到的消息的认证钥都会被收集起来,并附加在新的消息上明文发送出去,其预期目标并非对端,而是所有可能存在的监听者。任何得到认证钥者都可以制造出能通过认证的消息,OTR 最主要的实现 libotr 甚至为此提供了专门的工具。这样,监听者便无法证明某条消息是某人发出被他监听到,而非由他自己捏造的,如此实现了可抵赖性。而认证钥在通过新的消息公开之前只有消息的发送者和接收者能通过共享秘密推算出来,这样消息的接收者便可确定消息的确是由对方发出的,如此保证了完整性。

欲了解 OTR 协议更深入的细节,可参考其官方文档

使用 OTR 协议

到目前为止,只有 XMPP 协议在其主流客户端中实现了 OTR 的集成,其次是少部分 IRC 客户端集成了 OTR。但如前所述,理论上在任何能可靠地双向传输文本消息的协议的基础上,OTR 都可以工作。所以我在这里推荐使用 Pidgin 这一通过插件系统支持多种协议的多种特性的即时通信客户端——通过 Pidgin,你甚至可以在 MSN 等私有协议上使用 OTR 来保护自己。

Pidgin 简介

Pidgin 的核心部分是一个抽象了即时通信客户端的库——libpurple,它不包含具体的用户界面和通信协议的实现,而是分别留给应用程序和协议插件去完成。在架构的意义上,Pidgin 是 libpurple 使用 GTK+ 编写的官方用户界面;而其 OTR 插件则作为 libotr 和 libpurple、Pidgin 的中间件,和具体通信协议无关。因此,通过 Pidgin,你可以在任何有 libpurple 协议插件形式的实现的协议上使用 OTR。

因为 OTR 协议在 XMPP 上应用最广,以下将以 XMPP 为例介绍 OTR 协议的使用。

安装软件

在 Debian 和 Ubuntu GNU/Linux 的软件源里,一般 xmpp 协议支持随 libpurple 一同分发,一些和用户界面有关的插件随 Pidgin 分发。而中间件 pidgin-otr 是一个单独的包。

分别使用如下命令安装 pidgin 和 otr 插件:

# apt-get install pidgin
# apt-get install pidgin-otr

apt 包管理器会自动解决依赖关系并安装需要的其他包。

编译辅助插件:pidgin-xmpp-receipts

OTR 有一个问题:网络不稳定可能造成己方持有的 DH 源和对方持有的像不一致,这时发过去的消息将无法正确解密,而 OTR 协议一般不能自动恢复同步,而需要手动重新握手(在用户界面上一般表示为“刷新会话”)。

XMPP 协议的一个扩展——回执功能有助于发现 OTR 失步问题:该扩展可向消息的发送者递交协议规定的“回执”以提示消息被成功接收,而当配合 OTR 使用时,“成功接收”会包含“成功解密”——只有成功解密的消息才会生成回执。发送方据此可以发现 OTR 失步,并及时重新握手。

大部分专用的 XMPP 客户端内置了回执功能,但在 Pidgin 的体系中,XMPP 协议插件并未集成此功能,因此有人编写了实现该功能的插件

该插件尚未被 Debian 及其下游发行版收录(尽管已有数个发行版收录了它),故在它被发行版正式收录前需要手动编译部署。

安装 Pidgin 的开发包:

# apt-get install pidgin-dev

然后克隆下该插件的源代码并编译、部署:

$ git clone https://git.assembla.com/pidgin-xmpp-receipts.git
$ cd path/to/pidgin-xmpp-receipts
$ make && make install

编译好的插件xmpp-receipts.so会被复制到当前用户的插件目录${HOME}/.purple/plugins/中,重新启动 Pidgin 后便可在工具(T)菜单下的插件(G)对话框中找到名为XMPP Receipts的该插件。打勾启用后,成功被对方接收的消息后面会被插入一个对勾作为提示。

认证对方身份

在 OTR 握手时你可以拿到对方的公钥以确认对方的身份。如果收到的公钥是你使用的客户端从未见到对方的账户使用过的,系统会针对这一点给出提示,并将其指纹保存于本地,成为见过但尚未认证的公钥。

持有见过但尚未认证的公钥对应的私钥者肯定和你做过 otr 握手,但你还无法仅凭此知道持有此公钥对应私钥者是否和你所收到消息的发送者是同一人,而核实这一点必须在这条被欲核实的公钥认证的信道之外进行。

最基本的认证方法就是在你和对方见面时进行手动的指纹认证:程序会显示出你和对方公钥的指纹,确认你看到的“对方的指纹”与对方的“自己的指纹”一致,且你“自己的指纹”与对方的“对方的指纹”一致,即完成了对对方身份的确认,并可将对方的公钥标记为已验证

如果对方有不只一个公钥,但你只认证过其中的一部分,或你见到曾经认证过公钥的对方在握手时传来了一个从未见过的新公钥,那么你可以让对方用已被你认证过的公钥(所在的实例)和你握手后把他使用的公钥的指纹发给你,如:

“今天你用的 OTR 指纹我好像没见过啊,方便认证一下吗?能否用上次碰面时你用的那台电脑/手机把你现在用的指纹发给我?”

其效力和他当面把指纹交给你无异,只要对方没有把你认证过的公钥对应的私钥交给他人。然后你便可以在 OTR 插件的配置对话框中打开公钥指纹列表手动核对,并标记通过核对的公钥。

如果你核对过对方的 OpenPGP 公钥,你可以要求对方把 OTR 指纹用他的 OpenPGP 私钥签名后发给你(可以使用任何信道,甚至尚未认证的 OTR 信道),只要你相信对方不会把 OpenPGP 私钥交给他人。

使用这两种方法时要注意,不要把你欲认证的公钥指纹完整地告诉对方,而一定要让对方把欲认证的指纹发给你来核对。

对被认证方,以上两种方法可以合而为一:准备一份自己使用的所有 OTR 公钥的指纹清单,附上 OpenPGP 的 clear-sign,发给你信得过的朋友们确认。

以下模板供参考:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

I hereby claim:

  * I am bob, who owns the following PGP key(s):

	6B25C1C7 7E739D1B 6C5E86B0 D3CED892 A6AEB68B

  * At the same time, I am the owner of the following OTR key(s):

	3340D56D 2C750734 F9869A1B 26CFAC08 04823F52
	1063A0B9 F9EE4D3B 06250142 2A720531 DD2D0DC4

  * The following OTR key(s) are used for mobile phone,
    they may get revoked at any time.

	49AF52C4 53FF6323 635728B6 9325628C 0FF2E547

  * And I declare revocation of the following OTR key(s), their
    private keys are destroyed, please don't trust them:

	8DF79391 B8082682 42D5483E 976503B8 E458AEA8

  * To prove the claims above, this message is signed by my PGP key
    mentioned above.

You could also collect all fingerprints linked to your frequently
used xmpp accounts into a list like this, clear-sign it with gnupg,
and send it to your trustful friends, in order for them to trust you.
-----BEGIN PGP SIGNATURE-----

(signature of your OpenPGP key)
-----END PGP SIGNATURE-----

有一个问题是某些客户端会对消息排版(如 Pidgin 会用 html 排版),而这会破坏 clear-sign 的一致性。一个有效的解决方法是用gpg --store -a将 clear-sign 的文本编码成不怕排版的 radix-64 形式,用gpg -d即可还原。该命令默认会对输入作透明的压缩,故比压缩再 base64 的“传统”做法操作更简便。

关于问答和共享秘密的身份认证

pidgin的otr插件默认支持问答和共享秘密方式(这两种方法的本质其实是一样的)的身份认证。其核心思想是在你和对方两个人之间得出一份只有你们二人知道的秘密,然后使用otr插件的问答或共享秘密功能向对方询问和共享秘密有关的问题,如果对方的答案和你预设的相同,则对方的公钥会被自动标记为可信。对方不会得到你预设的答案,你也不会收到对方的回答,双方通过特定的算法保证在判断两个值是否相等的同时不会向包括对方在内的其他实体泄露值本身。

共享秘密本身是对称的,因此如果你和 N 个人构成联系网,那么你就需要维护 N 份共享秘密。这是该方法的主要缺点,建议和基于指纹的非对称认证方案互补使用,以取长补短。比如你只在一台机器上使用 OTR(即你只有一个 OTR 实例),那么共享秘密便可以在你不慎失去了该实例的私钥时帮助你的朋友们确认你的身份。显然,你已被认证的其他 OTR 实例可以起到同样的作用。

多通道是这种认证模式的核心,这里介绍几种常见的方式:

  • 在咖啡厅,酒吧或者club里见面时交换秘密,物理上见面是相对安全的方式,但也需要把窃听者纳入威胁建模(因为共享秘密是对称的方案)。在物理地点进行otr共享秘密的交换在实际场景中通常是在参加PGP signing party时顺带完成。

  • 使用PGP邮件交换信息,此种方式有两个地方需要注意:1) 验证对方的KEY是否受到其他第三方信任 2) 核对fingerprint(通常也是物理见面) 使用此种方法的前提是要保证PGP本身的安全性。

  • 在安全性需求较低的情况,可以使用电话,短信和其他即时聊天工具完成。安全性较高的场景则可以使用匿名网络,比如洋葱路由网络或者需要SSL/TLS/SSH认证的内网聊天服务。

  • 另外,在以上几种模式的基础上也可以继续拆分多通道信息交换,通过多个通道获得的信息的组合组成最终的共享秘密。组合时需要注意尽量不要在某一通道内提到通过其他通道交换的消息的内容。

共享秘密商定后,可以按需更新,如在见面时或通过认证的otr信道内商议:

“嗨哥们,我们在上次共享秘密的基础上加个777怎么样?”