Kerberos认证协议

今天上课,老师问了一个问题,知道哪些关于认证的协议?然后弹幕中缓缓飘过Kerberos,那我们就去了解一下这个协议

参考文章

kerberos认证原理—讲的非常细致,易懂
确实讲得挺细的,缺点就是图没了hh

简介

Kerberos是MIT提出的一种网络身份验证协议,通过使用密钥技术为分布式环境中客户端/服务器应用程序提供相互的强身份验证。

要解决的问题

在一个开放的分布式网络环境中,用户通过工作站访问服务器提供的服务,有很多问题:

  • 工作站上的用户可以冒充另一用户操作;
  • 用户可以改变工作站地址冒充另一台工作站;
  • 用户可以窃听并回放他人的信息交换,获得对于某种服务的访问权或中断服务的运行;
  • 使用假冒服务器从而骗得用户的机密信息

基本原理

首先,我们先知道一个最基本的如何确定他人身份的机制,即最基本的认证机制。
如果一个秘密仅被A和B知道,那么有人向B说他是A,那么B让他出示该秘密,如果是对的,那么B就可以认为这个人就是A。
进一步引入密钥的话,例如此时有一个Client和一个Server,双方共同知道一个密钥key,该密钥还仅被这两个人知道,那么Server验证Client的身份时,让Client出示自身身份信息和被key加密后的身份信息,然后Server端解密后进行比对。

引入KDC

在基本框架中,c和s两端,需要一个仅双方知道的key,这个key从哪儿来呢,我们已如kdc(Kerberos Distribution Center),它是Client和Server共同信任的第三方。
而Kerberos起源于希腊神话,是一只长着3个头颅的神犬,所以三个头颅分别代表Client、Server和KDC。
后面的session key的分发过程其实和我们之前所了解的密钥分发中的对称密钥分发的过程基本上是一致的
附上我之前写过的文章的链接密钥分发
但是要更完善一些,我仅针对我对于参考文章的理解和顺序整理,进行简单总结

Client与KDC的交互过程

在密钥分发过程中,我们知道Client首先要去向KDC请求会话密钥,这个过程在Kerberos里面比较完善
Client想要从KDC那里获取与Server的会话密钥前,需要先获得一个TGT(Ticket Granting Ticket),TGT的分发方仍是KDC,过程如下:

  1. Client向KDC发起对TGT的申请,申请信息为KRB_AS_REQ,KRB_AS_REQ中包含Pre-authentication data(用以证明自己的身份,一般是Client主密钥加密后的时间戳);Client name & realm;Server Name(KDC的Ticket Granting Service的Server Name),然后使用主密钥加密Pre-authentication data
  2. KDC收到申请后,根据申请中的Client name & realm,从数据库中找到对应的主密钥,然后解密Pre-authentication data,看解密是否成功,生成Client和KDC之间用于通信的session key,然后用Client的主密钥对生成的这个session key进行加密,用自己的主密钥对这个session key、关于Client的一些信息以及TGT到期时间进行加密生成TGT,然后KDC将这两份信息都发给Client,这样做的好处是KDC不用去维护对于不同的Client有着不同的session key的维护
    可能会有疑惑,为什么KDC会有Client的主密钥?因为此时针对的场景是域环境下,AD域扮演着KDC的角色,所以它知道该域下所有账户的主密钥
  3. Client收到这两份信息后,先用自己的主密钥来解密自己能解密的那部分,从而获得与KDC通信的session key,然后将获得的session key和另一部分TGT进行缓存
    需要注意的是与KDC通信的session key是一个短期key,并不是像主密钥那种有效期长的,所以当该session key过期了,TGT也过期了,需要重新申请
    另外TGT并不是针对某个具体的Server的,而是当Client需要与服务器产生会话时去申请的,不管是和哪个服务器通话的

此时,Client可以向KDC申请与Server通信的会话密钥了,过程如下:

  1. Client用与KDC会话的session key加密一些信息(证明信息+所要会话的Server信息),证明信息又被称为Authenticator(Client的一些信息),这个在讲与Server交互时再说,然后连同之前缓存的TGT一起发给KDC;
  2. KDC收到信息后,用自己的主密钥解密TGT,得到session key和Client的一些信息(C-Info1),这也就印证了为啥此时“KDC不用去维护对于不同的Client有着不同的session key的维护”,然后用得到的session key再去解密另一部分得到Client的一些信息(C-Info2),比较C-Info1与C-Info2从而验证Client的身份,验证成功后,会生成Client与Server通信的会话密钥,此时KDC会对该密钥进行两次加密,第一次加密使用Client的主密钥加密,第二次连同Client的一些信息和会话密钥到期时间,使用Server的主密钥进行加密
    感觉有一点参考博客说的非常好,“KDC直接将这两个加密过的包发送给Client和Server不就可以了吗?”,参考博客给出了不这样干的理由:假设Client很快获得session key,并将这个session key作为凭证随同访问请求发送到Server,但是假设此时Server的session key还没有收到,并且很有可能承载这个session key的包永远也到不了Server端,Client将永远得不到认证
    所以此时KDC将这两份信息用与Client的会话密钥加密后发给Client,然后由Client发给Server

此时Client从KDC手里得到了与Server通信的会话密钥,接下来就是Client与Server的交互过程了

Client与Server的交互过程

此时Client用与KDC通信的会话密钥解密得到的数据,得到两份信息,一份是用自己的主密钥加密的会话密钥信息(简称M1),一份是用Server端主密钥加密(会话密钥+Client的一些信息+Ticket到期时间)形成的信息(简称M2),M2在这个体系下也被称为Ticket,此后过程如下:

  1. Client用自己的主密钥解密M1得到会话密钥,然后创建上文中曾提过的Authenticator,只不过此时的Authenticator不仅包括Client的一些信息,还包括一个当前的时间戳,然后用同Server的会话密钥加密该Authenticator,然后将加密后的信息同M2传给Server
    这个Authenticator的作用就是证明一些东西,比如上文中是为了证明是该TGT的拥有者,那么这里的作用就是证明我是该Ticket(也就是M2)的拥有者
  2. Server接收到这两部分信息后,用自己的主密钥去解密M2,获得会话密钥和Client的一些信息(C-Info1),后去解密另一部分得到Authenticator信息,然后比较C-Info1与Authenticator中的Client的一些信息,从而进行认证
    那为啥需要时间戳呢?也是为了避免重放攻击,Server得到Authenticator信息中的时间戳后与当前时间进行比较,如果偏差超过一个可以接受的时间范围时,便断开与该Client的连接,不信任它(Server维护着一个列表,这个列表记录着在这个可接受的时间范围内所有进行认证的Client和认证的时间。对于时间偏差在这个可接受的范围中的Client,Server会从这个这个列表中获得最近一个该Client的认证时间,只有当Authenticator中的Timestamp晚于通过一个Client的最近的认证时间的情况下,Server采用进行后续的认证流程。)括号里的从参考博客里摘的,意思就是首先你Client的时间戳应该在一个时间范围内,其次我Server这边还维护着最近一次认证的时间,你又肯定不能比这个时间早了
  3. 不仅Server端可以判定你Client端传来的信息是否是重放或被劫持的,Client端也可以验证Server端传来的信息是否是重放或被劫持的,如果Client需要对他访问的Server进行认证,会在它向Server发送的消息中设置一个是否需要认证的Flag。Server在对Client认证成功之后,会把Authenticator中的时间戳提取出来,通过session key进行加密,当Client接收到并使用session key进行解密之后,如果确认时间戳和原来的完全一致,那么它可以认定Server正是它试图访问的Server;为啥要单独将时间戳提取出来呢?也是为了避免重放攻击,避免监听者原样返回信息。

大体过程是这样的,然后我在一门课上老师又介绍了一遍这个协议

老师版本

老师的版本和上面的大体思路是一样的,我来总结下(都是渐进改进的)

简单的认证对话

1
一些符号:
C——客户端 AS——认证服务器(第三方) V——服务器
IDc——C的用户标识 IDv——V的标识 Pc——C的用户口令
ADc——C的网络地址 Kv——AS与V共享的密钥
过程:

  1. C与AS通信,C发送(IDc||Pc||IDv)
  2. AS验证C的身份(根据ID找到密钥验证)后返回一个它可以去访问V的票据Ticket
    Ticket=Ekv[IDc||ADc||IDv]
  3. 用户C与V通信,发送(IDc||Ticket)
  4. V解密Ticket得到IDv(发现是自己)和IDc进行验证

存在的问题:

  • 没有设置访问有效时间
  • 在第一步中Pc是明文传递
  • C无法验证V
  • 用户每访问一个新服务就得需要一个新票据,然后就得输入密码
  • 重放攻击威胁

改进

2
增加一个票据许可服务器(TGS)(上面都是集中在KDC里),用户首先向AS请求一张票据许可票Tickettgs,然后就可以凭该许可票向TGS申请访问某个服务的Ticket,这样在许可票的有限期内,即使访问新的服务也不用请求AES,直接请求TGS即可
具体过程如下:

  1. C与AS通信,此时不再传递明文密码,而传递(IDc||IDtgs),因为TGS有多个,所以需指明是申请的哪一个;
  2. AS返给C用(用户口令导出的密钥,比如对用户口令哈希)加密的Tickettgs,Tickettgs=EKtgs[IDc||ADc||IDtgs||TS1||Lifetime1]
    TS1是签发的时间戳,Lifetime1是该许可证存活的有效期
  3. 此时C拿到了许可证,便可以向TGS服务器申请访问某个服务的Ticket,此时C给TGS传递(IDc||IDv||Tickettgs)
  4. TGS拿到Tickettgs对其进行解密验证是否有效合理,然后确认无误后返给其Ticket
    Ticket=EKv[IDc||ADc||IDv||TS2||Lifetime2]
    TS2也是签发的时间戳,Lifetime2是该Ticket的生存期
  5. 然后C就可以使用该Ticket去访问对应的服务了

但是仍然存在问题:

  • 无论是许可证还是票据的生存期长短难以选择
  • 重放攻击
  • C仍不能验证V

进一步改进

3

  1. C还是要向AS申请许可证,C向AS发送(IDc||IDtgs||TS1),此时增加了一个TS1,这个TS1也是C申请时签发的时间戳,目的是为了允许AS验证客户端C的时钟是否与AS同步;
  2. AS验证后返给C(EKc[Kc,tgs||IDtgs||TS2||LifeTime1||Ticket^tgs])
    Kc,tgs是C与TGS通信的会话密钥
    Tickettgs=EKtgs[Kc,tgs||IDc||ADc||IDtgs||TS2||Lifetime1]
  3. 此时C拿到许可证后,与TGS进行通信,C向TGS传递(IDv||Tickettgs||Authenticator)
    Authenticator是C用Kc,tgs加密生成的认证符,用于TGS端认证
    Authenticator=EKc,tgs[IDc||ADc||TS3]
  4. TGS端拿到后,先用Ktgs解密Tickettgs,拿到Kc,tgs,然后用其验证Authenticator是否可以正确解开,其他信息是否正确后,返给C
    Kc,tgs[Kc,v||IDv||TS4||Ticket]
    Kc,v就是CyuV之间通信的密钥
    Ticket=EKv[Kc,v||IDc||ADc||IDv||TS4||Lifetime2]
  5. C与V建立通信,C向V传递(Ticket||Authenticator)
    Authenticator=EKc,v[IDc||ADc||TS5]
  6. V接收到后进行验证解密Ticket拿到Kc,v,验证Authenticator及其他消息无误后,为了让C也验证自己,向C传递(EKc,v[TS5+1])
    然后C就可以确定这不是重放的消息,从而验证了V

总体来说,一张许可证,一张票据。