本文主要介绍Kerberos认证协议的原理以及解决了什么问题
Kerberos是什么
Kerberos是计算机网络世界中的一种身份认证协议。
身份认证是我们日常生活中经常进行的活动,比如我们要去银行取自己账户的钱,就必须先向银行证明你声明想要取钱的账户确实是你自己的。银行采取的认证方法是,需要你现场输入你账户的密码。在假定密码只有自己知道的前提下,只要输入的密码正确,就可以认定你就是账户的所有人。
使用只有自己知道的密码进行身份认证是一种被广泛使用的身份认证方式,对于这种认证方式,显然密码是最重要的凭证,一旦密码丢失别人就有可能冒充你。因此现实生活中除了使用密码进行认证以外,越来越多的地方开始采用生物信息或动态口令进行更安全的身份认证。然而目前这些认证方式都不能完全取代密码认证,主要原因是:对于生物信息认证,需要有生物信息采集设备,而且生物信息天然是暴露在外界的,有被盗取和仿制的风险;对于动态口令认证,需要我们绑定手机号码,手机丢失时会产生被冒充的风险,而且每次认证时都需要等待动态口令下发,认证效率比较低。
由此可知,密码认证仍然是重要且主流的身份认证方式。既然如此,如何保护密码安全就是一个不得不解决的问题。在银行的例子中,我们需要到银行柜台现场输入密码,银行提供输入密码的机器也是专用的,因此密码在传输到银行系统的过程中被盗取的风险被降到最低。然而对于在互联网上提供服务的公司,要求用户现场进行身份认证显然是不可能的,服务提供商和用户之间只能通过不安全的网络系统来交换信息。这就引出了一下几个问题:
- 当服务提供商需要验证用户的密码时,如果没有特殊的保密措施,用户就需要将密码发送到服务器中。密码在网络上进行传播是非常不安全的,有可能被第三方个截获。即使服务提供商提供了加密传输服务,攻击者也有可能通过伪造服务商,让用户将密码发送到自己的服务器上。
- 当某大型互联网公司提供多种产品时,如果没有特殊的认证设计,每种产品都需要储存所有用户的认证信息,用户使用每种产品时都需要进行认证操作。这样不仅效率低下,而且密码多次在网络中传播也带来了被截获的风险。当用户修改身份信息时,如何在所有产品间同步信息也带来了复杂的问题。
Kerberos正是为了解决以上两个问题而设计的一种身份认证协议。 下面我们通过将一个“naive”的认证协议改造成安全认证协议的过程,讲述Kerberos协议的原理。
身份认证协议与加密算法的区别:认证协议是认证双方都必须遵守的认证流程和标准信息格式,只是在信息交换的过程中,需要使用加密算法对数据进行加密。
Kerberos协议与SSL/TLS协议的区别:Kerberos是用于身份认证的协议,SSL是加密传输协议。SSL协议中,客户端和服务端首先使用公钥和私钥的方式协商一个对称加密的Session密钥,后续的通信都使用该Session密钥进行对称加密,可以看出SSL协议不涉及任何身份认证的过程。这也是Https的加密原理。
从0开始设计Kerberos协议
设计背景
- 网络是不安全的,网络中的信息可以被任意读取,修改和插入整个认证过程
- 网络中的节点都是不可信的,不论是客户端还是服务端
- 由于网络中所有节点都是不可信的,任意两个节点想要互相访问都需要认证,如何保证认证效率,如何维护和管理身份信息
设计目标
目标一:提供安全的身份认证能力,整个认证过程,用户密码无需在网络中传播,密码无法在传输过程中被盗取,被篡改的认证信息无法使认证通过。
目标二:提供双向认证,客户端也可以认证服务端。
目标三:任意客户端只需要认证一次,后续访问其他服务都无需再认证。
设计方案
第一版:拍脑袋的naive认证协议
第一版是我们在提到认证时能想到的最简单的模型,客户端访问服务器上的某个服务时需要将用户ID和密码一起发送给服务,服务器保存了所有用户的用户名和对应的密码,如果发送过来的用户名密码与服务器中的匹配,则认证成功。服务端会保存当前用户的会话信息和状态,并返回某个token给用户(web服务中服务器将认证通过的用户信息放入session中),后续用户请求只需要携带这个token,服务端就可以验证用户是否已经登录成功。
有哪些问题
- 密码明文转播
- 每个服务都需要保存所有用户的信息
- 访问每个服务都需要使用密码进行认证
第二版:认证服务与业务服务分离
第一版认证协议是我们自己写web应用demo时最喜欢写的一种认证协议,它实在是太简单太naive了,能够被攻破的手段太多,要想在生产环境中使用,我们必须解决上面列出的所有问题。第二版我们先从最简单的问题入手,解决每个服务都需要保存所有用户信息以进行认证的问题。
第一版我们将认证的过程与业务耦合在一个服务里了,其实整个认证过程其实不涉及任何业务逻辑,所有服务的认证过程是完全一样的,因此我们可以将认证过程单独抽出来成为独立的服务。引入一个统一的认证服务,每个客户端和业务服务都拥有自己的密钥,认证服务保存了所有客户端和业务服务的密钥。第二版的认证步骤如下
- 客户端发送用户名,要访问的服务名和自己的密钥给服务器
- 认证服务验证用户密钥,如果通过则身份认证成功
- 认证服务使用服务A的密钥将用户名,用户地址(IP)和服务名加密并发送给客户端
- 客户端请求服务A时,明文发送自己的用户名和认证服务发过来的Ticket(可重复使用)给服务A
- 服务A使用自己的密钥解密Ticket,如果解密后能读取到正确的服务名(serviceid),则表明解密成功。这时比较解密出来的用户名与明文的用户名是否一致,再比较解密出的用户地址与请求的地址是否一致,如果一致则说明该用户已经通过了认证服务的认证,可以提供服务。
整个过程非常像坐飞机的流程。我们需要首先使用某个票务服务App来获取机票,然后使用机票才可以在某个机场登上某架飞机。这个过程中,我们需要先在票务服务App上进行实名身份认证并选择想要乘坐的航班,认证通过后票务服务会给我们生成一张专属的机票,这张机票上有我们的姓名,航班号,登机时间,身份证号等信息,拿着这张机票我们本人才可以在固定的时间获得某个航班的飞行服务。这里的机票就像步骤3中认证服务给我们返回的加密数据一样,因此步骤3中返回的加密数据在认证领域也被形象的成为票据(Ticket)。
解决了什么问题
所有客户端和服务只需要保存自己的密钥,由认证服务保存所有密钥,认证由统一的服务完成。这样修改密码时只需要通知认证服务即可。通过第二版的改进,第一版遗留的问题2得到了解决。
有哪些问题
- 密码明文传播
- 访问每个服务都需要使用密码进行认证
第三版:转换思路,柳暗花明——真的需要明文传输密码吗?
以上我们只是做了一点跟认证安全无关的改进,让认证协议的架构更合理。那么到了这里,我们不得不开始啃硬骨头,解决最核心的问题——密码明文传输带来的风险。
之前我们的认证思路是,服务器保存了用户的密钥,那必须要让用户在认证时将他的密钥传输过来,直接比较两个密钥是否相等。这样的做法相当于两个人约定以同样的钥匙作为信物来交换信息,认证时两个人必须要在同一个地方比较钥匙齿形是否完全一致,如果两个人距离很远,则必须一个人将他的钥匙邮寄给另一个人进行比较,显然钥匙在邮寄的过程中非常容易丢失。显然这是非常愚蠢的。
我们是否可以换一个思路来思考这个问题:如果其中一个人按照这把钥匙的齿形造了一把锁,他用这把锁将要传递的信息锁在箱子里,并把这个箱子邮寄给另一个人,另一个人只要能用他的钥匙打开这个箱子,那就可以认为对方一定是跟我拥有同样钥匙的人。整个过程中,即使箱子丢失,也由于箱子被上锁了其他人无法打开箱子。
当我们把经验迁移到认证协议时,为了解决密码明文传播的问题,我们可以利用对称加密的特性来验证用户的身份。假设服务端拥有客户的密钥,客户端请求身份认证时,服务端可以使用客户端的密钥加密一段信息并发送给客户端,如果客户端能够使用自己的密钥正确解密这段信息,那么就可判定客户端身份认证通过,整个过程不需要进行密钥的网络传递。
对称加密:采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。
具体的认证步骤如下图所示:
- 客户端明文发送用户名给认证服务。
- 认证服务使用自己的密钥加密用户名和用户地址,生成认证票据,然后使用用户密钥加密认证票据,并发送给客户端。
- 真正的客户端收到加密的认证票据后,可以使用自己的密钥解密,获得认证票据。
- 客户端需要请求服务时,需要将解密出来的认证票据和明文的用户名、请求的服务名发送给认证服务。
- 认证服务使用自己的密钥解密用户发来的认证票据,如果认证票据解密后的用户名和地址与客户端发送的明文相同,则认为客户端身份认证通过。(如果攻击者伪造客户端,他没有客户密钥,因此无法解析获取到认证票据)
- 后续步骤与第二版3-5步一样
可以看到,整个过程产生了两个票据,这些票据都可以重复使用。可以发现,在我们用新方案解决密码明文传播的问题时,竟然带来了另一个好处:我们同时解决了访问每个服务都需要认证的问题。
- 认证票据:获得此票据后,当我们请求其他服务时,不用再从第一步开始认证,只需要将此票据发送给认证服务即可。这就解决了访问每个服务都需要使用密码认证的问题。
- 服务票据:获得此票据后,访问具体服务时,不需要再进行认证的过程,直接将此票据发送给具体服务即可获得服务。
由此上述认证过程可以稍微改动一下
我们把认证服务器拆分成两个服务:认证服务和票据授权服务,这两个服务拥有同样的密钥数据库。其中认证服务只负责用户首次认证时,向用户发送加密的认证票据。后续验证用户是否获得正确的认证票据的工作则交给了票据授权服务。当用户请求业务服务时,就不需要再输入密码进行认证,只需要将解密后的认证票据发送给票据授权服务即可。
解决了什么问题
- 密码不再进行网络传输,避免了密码在传输过程中被窃取。
- 只需要使用密码进行一次认证,后续可以使用认证票据获取所有服务的服务票据。
有什么问题
很好,到这里我们解决了第二版遗留的最后两个安全问题,大功告成~~~
别急,再仔细想想为了解决之前的问题,我们是否引入了新的问题,以上的认证协议真的无懈可击了吗?让我们看看下面两个问题
- 票据不会过期,可以被无限次使用,使用越久被盗取的风险就越高。
- 两张票据都可能在传输时被盗取,入侵者可以伪造自己的身份,使用盗取的票据进行重放攻击,从而获得服务。
T_T~~~ 果然没有那么容易
第四版:最后的改进,地狱恶犬(Kerberos)的诞生
针对第三版的第一个问题,其实非常好解决,我们只要给每条消息里增加timestamp
和lifespan
两个字段来进行有效期验证就行。timestamp
是该消息产生的时刻,lifespan
是该消息的有效期。每次收到消息时,都需要验证timestamp+lifespan
是否小于当前时刻,如果当前时刻已经大于有效期,则认证失败。
针对第三版的第二个问题,仔细观察可以发现,上述过程中第一次认证服务器将加密的认证票据发送给客户端时,基于对称加密的特性,只要客户端正确解密了信息,则可确认该加密信息一定是正确的服务发送过来的。但是后续的两次数据交换,两端均没有使用对称加密的特性来认证发送来的票据一定是正确的节点发送的,入侵者就可以通过重放票据来欺骗服务端。
最简单的解决方案是,后续两次的数据交换也需要使用对称加密的特性来认证发送源的身份。由于第一次数据交换时使用了对称加密的特性保证了消息的安全性,因此我们可以在第一次数据交换时在加密的ticket中包含一个由服务器生成的临时密钥1。第二次数据交换可以使用第一次生成的临时密钥1进行对称加密和身份验证,第二次数据交换的ticket中还会包含一个由服务器生成的临时密钥2,第三次数据交换时可以使用临时密钥2进行对称加密和身份验证。
认证步骤如下:
- 客户端明文发送用户id,要请求的服务id(这里就是票据授权服务),期望的票据授权票据有效期给认证服务
- 认证服务根据用户id查询对应的用户密钥,并生成一个临时的会话密钥1(作为跟TGS通信的密钥)。这时认证服务会发送两条消息给客户端:
- 使用用户密钥加密的【TGS_id+timestamp+lifespan+session key 1】消息,这条消息可被用户解密,使用TGS_id来验证解密是否正确,使用timestamp+lifespan来验证消息是否过期,最后获取到下一次通信的session key 1。
- 使用票据认证服务密钥加密的【user_id+user_address+TGS_id+timestamp+lifespan+session key 1】票据授权票据(TGT),这条消息不能被客户端解密。需要客户端在下一次通信时将该消息原封不动的发送给票据授权服务。
第2步的安全性:客户端可以验证认证服务是否伪造。如果认证服务是伪造的,则不可能使用正确的用户密钥加密消息,客户端就无法正确解析消息i,这就说明了认证服务是伪造的。如果这一步的两条消息被截获,由于两条消息都被加密了,攻击者无法获取消息的内容,也就无法获取下一次通信的临时会话密钥。
相比于第三版,这里返回的票据认证票据没有被放在消息i中被用户密钥加密。这是因为其实这两条消息都已经使用密钥加密过了,票据认证服务会通过TGT中的session key来认证客户端的身份,因此没有必要把TGT再使用用户密钥加密一次。而第三版中,由于不存在session key,因此必须将TGT使用用户密钥加密,只有客户端使用自己的密钥将TGT正确解密出来,才能认证客户端的身份。
- 客户端发送三条消息给票据授权服务:
- 明文的具体服务id,期望的服务票据有效期。
- 使用session key 1加密的【user_id+timestamp】消息。
- 票据授权票据(TGT)
第3步的安全性:票据授权服务使用自己的密钥解密消息iii,使用其中的时间戳和有效期验证票据是否有效,并获取到待授权的用户名,用户地址和临时会话密钥1。如果获取到的会话密钥1能够正确解密消息ii,且消息ii与消息iii的user_id和user_address匹配,则验证了客户端的身份。如果这三条消息被攻击者窃取,攻击者无法解密并获取临时会话密钥1。票据授权服务还会缓存已经认证过的user_id,当攻击者企图重放票据时,票据授权服务发现该用户已经认证过,则会拒绝第二次的认证。
- 票据授权服务发送两条消息给客户端:
- 使用session key 1加密的【service_id+timestamp+lifespan+session key 2】消息。
- 使用具体服务密钥加密的【user_id+user_address_service_id+timestamp+lifespan+session key 2】服务票据。
第4步的安全性:客户端使用session key 1解密消息i,如果正确解密,则说明票据授权票据不是伪造的。如果这一步的两条消息被截获,由于两条消息都被加密了,攻击者无法获取消息的内容,也就无法获取下一次通信的临时会话密钥。
- 客户端发送两条消息给具体服务:
- 使用session key 2加密的【user_id+timestamp】。
- 服务票据
第5步的安全性:服务使用自己的密钥解密服务票据,获取其中的session key 2,使用session key 2解密消息i,如果两条消息的user_id和user_address匹配,则认证通过。攻击者截获消息后无法解密两条消息来获得session key 2。具体服务也会将已经认证过的user_id缓存起来,防止攻击者重放攻击。
- 具体服务发送一条消息给客户端:
- 使用session key 2加密的【service_id+timestamp】消息。
第6步的安全性:客户端收到具体服务返回的加密消息后,如果可以正确使用session key 2解密,则认为具体服务未被伪造。后续请求服务时,只需要向服务发送服务票据和使用session key 2加密的请求数据即可。服务会使用session key 2将具体的响应加密返回。
结论
经过多次改进,第四版的认证协议终于达成了设计目标中的三个要求,这也就是Kerberos认证协议的完整流程了。
Kerberos协议使用对称加密的特性,避免了密钥在网络中的传递,同时也实现了客户端和服务端的双向认证。又通过引入中间的票据授权服务,实现了一次认证即可安全使用多个服务。同时兼顾了安全性和认证效率。
Kerberos协议的安全性只与对称加密算法的安全性相关,只要对称加密算法不被破解,协议的流程即可保证认证的安全性。