一文搞懂Dfinity的身份认证

作者:ddd009
本文从基本的密码学概念讲起,详细解释了消息签名认证的过程,最后讲解了Dfinity是如何通过Internet Identity服务帮助用户管理自己的身份(密钥)的。

基本概念

Dfinity中canister通过传递消息来互交,canister A 调用 canister B 时将函数名,参数包含在消息中,B拿到执行完成后把结果告诉A,这一过程通过数字签名来进行身份验证。用户调用canister时与这一过程一样

request
上图是用户发送请求的示意图。

可以看到除了canister ID,函数名称等常规请求信息,请求内容还包括用户的公钥与消息签名,Caller’s Principal是用户的公钥进行哈希得到的,下文中会具体讲解这一过程。被调用方收到消息后会使用用户的公钥验证签名的正确性,并检查公钥与Caller‘s Principal是否对应。

而canister的开发者是不需要理会底层的密码学细节的,只需要编写代码,Dfinity会自动完成身份验证。

principal

这张图片展示了如何通过公钥得到用户的Principal

首先从DER格式的公钥开始,对其进行SHA-224哈希运算,得到一个28字节的字符串,之后添加一字节用于区分用户principal和其他principal,例如canister的principal。

这29个字节是用户principal的二进制表示,可以看到Motoko和JS中principal变量是一个Uint8的Blob数组,里面保存的就是这29个字节

截屏2021-08-16 上午10.48.06.png

之后就是把这29个字节转化为文本表示的过程,首先添加CRC32错误检查码,再进行Base32编码,把最终得到的字符串以五个字符为一组分组,中间以 “-”分隔,就得到用户的prinicpal文本表示。

顺便提一下,有base32的社区实现 by flyq, 已经合并到vessel-package-set,可以通过vessel进行使用。

如果说用户的Principal是与单一密钥绑定的将会非常不方便。如果说你有多个设备,那你需要在这些设备中使用同一个密钥,这即不方便又不安全。

Dfinity使用了委托密钥的机制,如图你可使用黄色的密钥对橙色的密钥签名生成一个委托,包含作用域与过期时间。而橙色的密钥又可以对其他密钥进行委托,所以说这一方式是非常灵活的。

delgation

看到这里大家应该对于密钥对,用户Principal,委托密钥这些基本概念有了大概的认识,那么Dfinity是如何使用这些密码学技术来完成用户身份验证的呢?

委托签名认证

委托密钥的一种应用与Web Auth有关,Web Auth是W3C推出的最新标准,主要针对与web应用的双重认证。也就是除了传统的用户名密码外,用户还需要对服务器发出的质询使用额外的安全设备进行签名,密钥是储存在安全芯片中的,不会发生泄漏,即时你的系统感染了木马,它们也不能对安全芯片中的密钥怎么样。通过这种方式可以确保是用户登录了服务器。

上面说的是传统互联网的做法,在Dfinity上这一过程有些许不同。使用中心化服务器时,你可以与服务器建立有状态的长连接,因此只需要对服务器发送的质询进行一次签名,之后的通信可以使用建立的连接传输。而在Dfinity上,并不存在一个中心化的服务器,我们不能同服务间建立有状态的连接,服务也不能主动向用户发出质询,因此用户需要对发出的每一条请求进行签名,而使用Web Auth时是需要对每次签名进行确认的,我们当然不能每发一次请求就触摸一下Yubikey或者按一次指纹,我们先生成一个短期的会话密钥,再使用Web Auth对会话密钥进行委托签名,最后用委托密钥自动完成对消息的签名。这样就解决了对多个消息签名的问题。

Internet Identity

尽管Web Auth非常适合安全的存储密钥,但是由于浏览器的安全限制,密钥是与特定canister绑定的。在IC上只要canister不同就视为不同源,这种状态分离对于安全性至关重要。而这也给用户带来了麻烦,例如你很难跨设备使用同一个服务。针对这一缺点官方推出了Internet Identity,简称II,类似于Sign With Google的SSO服务,方便用户管理密钥与身份

Dfinity上的大部分应用都提供了Sign With Dfinity的选项,点击后会弹出identity.ic0.app,如果用户同意应用使用其身份,II就会重定向至应用页面,这时就可以通过特定的用户身份使用该应用。这里也用到了会话密钥和委托的机制
identity

如图,应用前端生成会话密钥并将公钥发送至II服务,如果用户同意,II服务将对会话密钥进行委托签名,所有的认证过程都是在用户端完成的。

为了避免用户的信息被跨服务追踪,II对于不同应用前端给出的用户身份是不同的
image.png
这里是根据前端的主机名区分的,例如对于localhost:3000和localhost:3001,II给出的用户Principal就是不同的,这样不仅避免了用户身份被追踪也提升了安全性。如果不这样做,被跨服务追踪信息还是小事,你使用的服务可能会冒充你的身份去调用其他的服务,社交网站的前端可能恶意调用电商网站的服务来冒充你下订单。