Kerberos 是由 MIT 设计的一个安全认证协议, 它的设计目标是在不受信任的网络环境中实现对通信方身份的可靠认证, Kerberos 自设计以来经过多次改版, 最新的 Kerberos 协议版本号为 5, 其协议标准文档为 2005 年发布的 RFC 4120, Kerberos 与 SSL/TLS 协议都是为了在不安全的网络中通信而设计, 而 Kerberos 的侧重点在于认证 (Authentication), 身份认证完成之后, 通信双方是否使用加密传输以及使用何种加密套件并不是 Kerberos 标准本身所关心的问题, 本文讨论 Kerberos 5 协议的设计

28.1 Kerberos 协议概述

Kerberos 出自希腊神话, 是看守冥界的一只三头犬, 作为冥界的守门犬, Kerberos 不允许任何人从冥界出去, 该协议以 Kerberos 命名其含义也是表征协议本身非常安全且可靠, 在讨论 Kerberos 认证过程之前, 首先给出 Kerberos 协议所用到的一些概念 (这些协议名词不便翻译, 所以直接使用协议标准的原文)

  • Realm

    Realm 可以理解为一个认证管理域, 同一个 Realm 的客户端使用同一个认证数据库和相同配置, 例如一个公司便可以作为一个 Kerberos Realm, Realm 名称是大小写敏感的, 通常都使用大写字母来表示, 典型的用法是使用 DNS 域名来作为 Kerberos Realm 的名称, 以头条为例, 公司的 Kerberos Realm 便是 BYTEDANCE.COM

  • Principal

    Principal 是 Kerberos 用以标识用户或服务的标识, 每一个 Principal 对应 Kerberos 认证服务器 (AS) 中数据库的一条记录, 对用户来说, Principal 的一般形式为

    其中 Name 为用户名, instance 为可选项可以用来进一步标识用户的身份, 比如对于用户名称为 tom 的管理员, 其 Principal 可以为 tom/admin@Realm, 对服务来说, Principal 的一般形式为

    其中 Service 为服务名称, 例如 ssh, ftp, 以 ftp 为例, 其 Principal 的形式便可以为 ftp/ftp.example.com@EXAMPLE.COM

  • Ticket

    Ticket 是 Kerberos 协议中用以证实用户身份的一个凭证, 其中包含很多信息, 当用户要访问对应服务时需要向服务传递 Ticket 以证实其身份, Ticket 是 Kerberos 协议的核心, 我们将在下面详细讨论 Ticket

  • KDC

    在 Kerberos 协议中, 身份的认证, Ticket 的颁发, 用户和服务信息的存储都是由 KDC 来完成的, KDC 是 Key Distribution Center 的缩写, 顾名思义这是一个密钥分发中心, KDC 主要由 3 个模块构成, 分别是认证服务器 (Authentication Server, 简写为 AS), Ticket 授予服务器 (Ticket Granting Server, 简写为 TGS) 以及信息数据库 (Database), KDC 是 Kerberos 协议的核心模块, 在 Kerberos 协议中, KDC 是一个可信的第三方认证机构, 整个 Kerberos 的认证均由该第三方认证机构来完成

28.2 Kerberos 协议详述

在上一节中, 我们给出了几个 Kerberos 协议的核心概念, 本节我们将围绕这 4 个核心概念来讨论 Kerberos 认证的过程, Kerberos 本身是一个认证协议, 它的作用和目标在于实现对通信双方的可靠认证, 通信双方既可以是用户与用户, 也可以是用户与服务, 以用户和服务之间的认证为例, 可靠认证的意义对用户来说, 可以保证用户真正访问的服务就是其所想要访问的服务, 而不会是一个由攻击者伪造的服务, 对服务来说, 在收到用户请求时可以可靠地判断该用户是否就是用户自身所宣称的身份, Kerberos 假设网络环境是不安全的, 这意味中用户和服务的通信数据可以被攻击者随意地拦截或篡改, 这使得身份认证成为了一件富有挑战性的事情, 但 Kerberos 协议的可靠性是有前提的, KDC 本身必须是可靠的, 因为 Kerberos 认证的本质在于借助 KDC 的可靠性来实现对通信双方的认证可靠性

以用户和服务之间的认证为例, Kerberos 认证的流程如下图所示:

整个认证过程共有 3 方参与, 用户, 服务与 Kerberos KDC, 整个认证的数据包顺序为 AS_REQ / AS_REP / TGS_REQ / TGS_REP / AP_REQ / AP_REP, 接下来我们来逐个讨论每个数据包的内容及其构造方式

当用户要访问某服务时, 首先需要向 AS 发送 AS_REQ 包, 发送该包的目的是为了获取 Ticket Granting Ticket (简写为 TGT), TGT 是 Kerberos 中特殊的 Ticket, 它是用于访问 TGS 的凭证, 客户端需要首先申请 TGT, 然后以 TGT 作为凭证去 TGS 上申请访问对应服务所需要的 Ticket (之所以不直接申请目标服务的 Ticket 是为了实现 SSO, 即 Single Sign-On, 一次登录, 可以访问多个服务, 而无需访问每个服务时重新登录, 在看完整个认证过程之后便可以理解 TGT 的作用), AS_REQ 包含的信息如下所示:

AS_REQ = (用户的 Principal, 服务的 Principal, IP List, Lifetime)

在 AS_REQ 请求中, TGS 实际上可以看做是一个应用服务器, 我们向 AS 请求的目的就是为了接下来去 TGS 进一步获取 Ticket, 因此该次请求的服务的 Principal 便为 TGS 的 Principal, 固定为 krbtgt/REALM@REALM, IP List 在 Kerberos 5 中为可选项, 它的本意是声明 Ticket 可以使用的主机的 IP 地址列表, 但由于 NAT 的存在使得请求到达服务时的源 IP 与请求用户的源 IP 不一定是一致的, 因此该字段意义不大, Lifetime 是客户端希望的凭证有效时长(当然在具体的实现中, 该值只是客户端的期望值, 它不会大于 KDC 所配置的 Ticket 有效时长的最大值), AS_REQ 的所有信息都是明文传输的

KDC 的 AS 在收到 AS_REQ 包后, 会查询 KDC 的数据库, 检查 AS_REQ 中的 Principal 是否存在于数据库中, 若不存在, 则向客户端返回一个错误, 此时认证过程便提前终止, 若检查没有问题, AS 会创建一个随机的 Session Key, 我们记作 , 然后 AS 创建 TGT, TGT 包含的内容如下所示:

TGT = (用户的 Principal, krbtgt/REALM@REALM, IP List, Timestamp, Lifetime, )

最后, AS 分别使用用户的密钥和服务的密钥 (二者均提前存储在 KDC 数据库中) 构造 AS_REP, AS_REP 的内容如下所示:

AS_REP = {服务的 Principal, Timestamp, Lifetime, } {TGT}

其中 代表前面括号中的内容使用用户密钥加密, 代表前面括号中的内容使用 TGS 的密钥加密, 当客户端收到 AS_REQ 后会提示用户输入密码, Kerberos 客户端在用户输入完密码后将密码连接一个 salt, 并使用 KDF 函数生成用户密钥 (KDF 即 Key Derivation Function, 密钥派生函数, 通过用户密码+盐哈希得到用户密钥), 当用户输入的密码正确时, 生成的用户密钥便是正确的, 该密钥可以正确解码 AS_REQ 的前半部分 (因为前半部分是 AS 使用用户密钥加密的), 此时客户端可以拿到访问 TGS 所需要的 Session Key, 在这一步中, 用户能够正确输入密码便可以保证当前操作客户端的是用户本人, 从而完成用户身份的认证, 可以看到用户与 AS 的交互过程中, 用户密码始终没有在网络信道上传输, 而 AS_REQ 中的使用 TGS 密钥加密的 TGT 便存储在用户本地缓存中, 作为接下来访问 TGS 时使用的凭证, TGT 的存在也使得 Kerberos 协议实现了 SSO, 用户在接下来的交互中, 只要 TGT 没有过期便不需要再与 AS 交互, 进而也不再需要输入个人密码, 可以直接使用 TGT 向 TGS 申请访问目标服务的 Ticket

接下来客户端与 TGS 进行交互, 为了避免重放攻击, 客户端在向 TGS 发送的数据包里需要包含客户端的时间戳信息, 并将该信息使用 TGS 的 Session Key 加密, 该信息我们记作 Authenticator, Authenticator 的内容如下所示:

Authenticator = {用户的 Principal, 用户的 Timestamp}

然后客户端构造最终的 TGS_REQ 如下所示:

TGS_REQ = (期望访问服务的 Principal, Lifetime, Authenticator) {TGT}

前面 3 项是明文传输的, TGT 则使用 TGS 的密钥进行加密, 当 TGS 收到 TGS_REQ 后, 它首先检查其中的服务的 Principal 是否存在, 若不存在, 则向客户端返回错误, 检查没有问题之后, TGS 使用自己的密钥解码得到 TGT 的原文, 从而可以取出 , 进而可以解码 Authenticator, 此时 TGS 继续校验 Authenticator 中的客户端的 Principal 与 TGT 中的 Principal 是否相同, 并且检查 TGT 是否过期, 除此之外, TGS 维护了一个 Replay Cache, 当它收到 TGS_REQ 时会检查 Authenticator 是否存在与 Reply Cache 中, 若存在并且其中对应的条目没有过期则会拒绝此次请求, 否则将 Authenticator 放入该 Cache 中以避免重放攻击, 所有检查完成之后, TGS 可以确认用户请求使用的 TGT 确实是之前 AS 所授予该用户的 TGT, 这时, TGS 会生成一个新的 Session Key, 这个 Session Key 是用于用户与服务之间认证使用的 Session Key (之前的 Session Key 是由 AS 生成的, 用户用户与 TGS 之间认证的 Session Key), 我们记作 , 然后 TGS 为用户创建访问服务所使用的 Ticket, 我们记作 , 该 Ticket 包含的内容如下所示:

= (用户的 Principal, 服务的 Principal, IP List, Timestamp, Lifetime, )

最后 TGS 构造分别使用 TGS 的 Session Key 和用户所期望访问的目标服务的密钥 (我们记作 ) 加密信息, 构成 TGS_REP, 其内容如下所示:

TGS_REP = {服务的 Principal, Timestamp, Lifetime, }{}

其 TGS_REP 的前半段信息使用 TGS 的 Session Key 加密 (用户在 AS_REP 中解码已经得到了 TGS Session Key), 后半段使用用户所期望访问的服务的密钥加密, 至此用户已经拿到了访问目标服务所需要的 Ticket 以及目标服务的 Session Key, 接下来, 用户使用该 Ticket 向期望访问的目标服务器发送 AP_REQ, 与访问 TGS 时类似, 客户端首先使用服务的 Session Key 构造 Authenticator, 内容与访问 TGS 时相同, 形式如下:

Authenticator = {用户的 Principal, Timestamp}

最后客户端构造 AP_REQ 形式如下所示:

AP_REQ = Authenticator {}

应用服务器在收到 AP_REQ 后使用自己的密钥 () 解码得到 Ticket, 首先校验 Ticket 是否过期, 从 Ticket 中可以取出 Session Key, 然后使用 Session Key 解码 Authenticator, 之后进一步检查 Authenticator 中的用户 Principal 与 Ticket 中的 Principal 是否一致 (当然在检查之前与 TGS 对 Authenticator 的处理一致, 本地都维护了一个 Replay Cache 检查 Authenticator 是否重复来避免重放攻击), 从哪完成对用户身份的认证, 至此 Kerberos 认证过程完成

28.3 Kerberos 协议简要分析

在上面我们讨论了 Kerberos 协议的认证过程, 可以看到 Kerberos 协议本身比较复杂, 实际上从上面的流程中我们可以知道, Kerberos 的可靠性在于 KDC 的可信性, 如果 KDC 本身是不可信的, 则整个认证便没有安全性可言, 在文章的开头我们提到 Kerberos 和 https 所使用的 SSL/TLS 协议都是为了在不安全的网络中通信的安全性而设计, 那么二者可不可以相互替代? 答案是不可以或者说很大程度上不可以, 二者的设计目标不同, 设计理念与侧重点以及适用场景都不同, 从 Kerberos 的交互过程中我们可以看到要使用 Kerberos 协议, 则无论是用户还是服务都必须事先注册到 KDC 中, 所以 Kerberos 实际上不仅仅起到了可靠认证同时可以方便地实现权限管控 (如 ACL), 对于 Kerberos 来说, 在通信之前, KDC 已经完全知晓了所有用户和服务各自的密钥, Kerberos 完全使用对称加密来传输数据, 而对于 https 来说, 服务端接面向的用户是随机出现的, 可能是完全陌生的用户 (对于一个公网可访问的 Web 站点来说, 世界上任意一台遵守 TCP/IP/HTTP 协议的主机都可以进行访问), 因此服务端不会存储服务端和客户端通信的密钥, 对于 SSL/TLS 协议来说则要通过诸如 Diffie-Hellman 算法等在通信时临时协商出密钥, SSL/TLS 更适合于 Web 场景, 但不便于做权限管控, Kerberos 协议涉及的细节比较多, 本文只是讨论了协议的整体流程, 一些细节如预认证 (Pre-Authentication), 跨 Realm 交叉认证 (Cross-Authentication) 等在本文没有提及, 读者可以进一步阅读 RFC 4120 了解协议更多细节

Kerberos 5 的 MIT 实现: