基于SM9的JWT强身份认证方案

2024-03-25 02:05罗娇燕左黎明陈艺琳
计算机技术与发展 2024年3期
关键词:令牌数字签名私钥

罗娇燕,左黎明,陈艺琳,郝 恬

(华东交通大学 理学院,江西 南昌 330013)

0 引 言

随着互联网应用的普及和数据交换的增加,安全问题已成为互联网应用开发中不可忽视的挑战[1]。传统的基于session-cookie模式的有状态身份认证方式不仅面临着CSRF等安全问题,而且在可扩展性和跨域方面也存在缺陷[2]。针对上述问题,基于Token身份认证机制提出了一种无状态、轻量级、可扩展和支持跨域的身份认证方案。根据这些优点,Token身份认证机制在API接口中得到了广泛应用,其中以JWT技术为代表[3]。

JWT技术是由RFC 7519[4]定义的一套开放标准,具有紧凑且安全的特点,可用于执行身份认证和信息交换。但由于JWT标准的复杂性以及开发者对技术细节和加密算法的理解不够透彻,为开发带来便利的同时也为应用程序的安全性埋下了隐患。2015年Tim McLean[5]发现,有些类库在实现JWT规范的过程中会产生一些漏洞;例如利用“none”算法或者直接在交互过程中删除签名均可以绕过签名验证过程。2016年,安全研究人员Sjoerd Langkemper[6]提出一种针对签名算法的攻击,将RSA和HMAC算法混淆使用来绕过服务器的身份认证。2021年,Apache发布的安全公告中,公开了Apache ShenYu中的身份验证绕过漏洞(编号:CVE-2021-37580);由于产品对客户端提交的token进行解析的同时没有进行校验,导致攻击者可绕过认证,直接进入系统后台。2022年Khaled Nassar[7]在github上披露了Oracle JavaSE数字签名验证机制的漏洞利用代码(编号:CVE-2022-21449),该漏洞是由于开发人员在实现椭圆曲线数字签名算法(ECDSA)时未充分考虑签名验证机制所导致的,攻击者可以伪造签名从而绕过身份验证。同年,nodejs的开源基础库JsonWebToken被曝存在远程代码执行漏洞(编号:CVE-2022-23529),攻击者可以通过该漏洞远程执行恶意代码,导致系统崩溃、数据泄露、账户被劫持等风险,从而给用户的隐私和财产带来巨大威胁。此外,由于该库每月在NPM上下载量超过3 600万次,应用于超过22 000个开源项目,其中包括微软、Twilio、Salesforce、Intuit、Box、IBM、Docusign、Slack、SAP等知名公司创建的开源项目,这也意味着该漏洞的影响范围极其广泛。

JWT中存在的安全问题,例如“none”算法绕过(漏洞编号:CVE-2018-1000531、CVE-2022-23540)、敏感信息泄露(漏洞编号:CVE-2019-7644、CVE-2022-23541)、密钥穷举攻击、算法混淆攻击(漏洞编号:CVE-2016-10555)等,主要是由于开发人员对于JWT技术理解程度的不够深入而引发的[8]。基于上述安全问题,该文提出了一种基于国密SM9的JWT强身份认证方案,利用国密SM9算法对认证参数进行数字签名,实现对用户身份的安全认证。

1 JWT身份认证及其攻击

1.1 JWT技术

消息保护技术在应用层中很少单独使用,通常融合在整个安全技术体系之中,而JWT就是代表性的消息保护技术[9]。JWT可有效保证多方通信中的信息安全,在无需频繁调用资源服务器或者数据库的情况下,JWT就可验证后续客户端请求,可适用于分布式站点的单点登录场景[10-11]。该文主要基于JWT的身份认证功能进行分析和优化。图1展示了基于JWT的身份认证流程[12]。首先,客户端使用登录凭证(比如用户名和密码)请求授权服务器;凭据验证通过后,授权服务器利用密钥生成一个JWT令牌返回客户端。然后,客户端携带此JWT令牌请求资源服务器上受保护的资源;资源服务器从JWT中解析用户请求并进行处理。

图1 JWT工作流程

JWT是一个由三部分组成的字符串,分别是头部(Header)、载荷(Payload)和签名(Signature),点(“.”)号作为相邻部分的分隔符。其中头部和载荷部分本身是JSON格式的数据,可以利用Base64url算法对内容进行解码,解码后的内容如下所示。

Headers = {

"typ": "JWT",

"alg": "HS256"

}

Payload = {

"iss": "Token Builder",

"iat": 1680163910,

"exp": 1680163970,

"sub": "tom@webgoat.org",

"user name": "Tom",

"Email": "tom@webgoat.org",

"admin": "true"

}

SIGNATURE =HMACSHA256{

base64UrlEncode(header) + "." + base64UrlEncode(payload),

密钥

}

头部定义令牌类型以及所采用的加密算法,HS256代表该JWT令牌的签名算法为HMAC SHA256。载荷部分主要是对实体(通常是用户实体)和附加数据的声明,声明通常分为三类,分别是注册声明、公共声明和私有声明。图中的iss(发布者)、iat(发布时间)、exp(到期时间)、sub(主题)等字段都是属于注册声明,并非强制性使用的;公共声明和私有声明开发者可根据实际交互情况进行设置。签名部分是利用算法对头部和载荷部分进行签名,防止数据被篡改[4];在头部声明的签名算法不同,签名的过程也是不同的。

1.2 针对基于JWT身份认证的攻击

JWT是API技术中消息传递的重要形式,各个API服务提供方在OAuth 2.0和OpenID Connect使用它在各方之间交换信息[9]。尽管JWT的使用范围很广,但常常因为提供方的实现不规范产生了一些安全问题。JWT由三部分组成,该文针对各部分产生的漏洞进行分析,并对一些典型漏洞进行研究。

(1)针对头部的攻击。

“none”算法绕过。

JWT的头部通过设置typ参数和alg参数分别标明了Token类型和签名算法。在RFC 7518[13]标准中,定义了JWT可以使用一系列算法进行签名,也可以不使用算法进行签名,即将alg参数的值设置为none。如图2所示,在JWT中设置“none”算法时,在签名部分将不使用任何算法进行签名,即签名部分为空字符串,后端服务器不执行签名验证,此时前两部分符合规范的任何token都是有效的。“none”算法最初的目的是为了方便开发者在开发环境中进行调试,如果在生产环境中也使用该功能,攻击者便可以伪造任意用户的token进行身份认证[5]。

图2 基于“none”算法生成的JWT

(2)针对载荷的攻击。

当JWT用于身份认证时,通常需要包含用户的相关信息,而载荷部分便是用于声明用户实体及其要发送的数据信息,是JWT不可缺少的一部分[4]。根据身份认证所需要的信息生成一个JSON对象,利用Base64url算法对其进行编码,编码后的字符串便是JWT的第二部分。按照RFC4648[14]的定义,Base64url算法是基于Base64算法而形成的一种加密方式,对内容进行了简单编码,无法保证传输过程中信息的机密性。因此可以直接利用Base64url算法对JWT中的载荷部分进行解码,如图3所示,从解码后的内容可以获取身份认证过程所包含的敏感信息。通过载荷部分泄露的敏感信息,推断身份认证的参数及其含义,再结合前面提到的“none”算法漏洞,攻击者可以直接接管账号或提取权限[9]。

图3 基于载荷部分的敏感信息泄露漏洞

(3)针对签名的攻击。

①算法混淆攻击。

签名部分主要是根据头部的alg参数设置的算法类型对Base64url算法编码后的头部和载荷两部分内容进行签名,从而保证了JWT在数据传输过程的不可篡改性。RFC 7518[13]标准声明了对称算法和非对称算法均可以作为JWT的签名算法,由于有些开发者的技术实现不规范,提供了与算法无关的签名验证方法verify()。算法伪码如下:该方法只是根据头部(Header)的alg参数值确定签名的算法类型,而不验证提供的Token加密算法,将导致身份认证服务器验证JWT签名的算法与开发人员预期的算法不相同,从而产生对称和非对称算法混淆漏洞。

publicKey = ;

token = request.getCookie("session");

verify(token, publicKey);

……

function verify(token, secretOrPublicKey){

algorithm = token.getAlgHeader();

if(algorithm == "RS256"){

// Use the provided key as an RSA public key

} else if (algorithm == "HS256"){

// Use the provided key as an HMAC secret key

} }

产生算法混淆漏洞的流程如图4所示。当开发人员调用verify()方法但仅使用非对称算法(如RS256)作为JWT的签名算法,即在身份认证过程中,认证服务器使用私钥对数据签名,使用公钥进行签名验证[15]。由于公钥对所有人可见,攻击者获取RS256公钥并将其作为HS256算法的签名密钥,同时将头部的alg参数值设为“HS256”,然后对修改后头部和载荷部分结合HS256算法进行签名生成JWT令牌。在身份认证过程中,认证服务器将使用HS256算法和相同的公钥去验证签名。

图4 基于签名部分的算法混淆漏洞流程

②密钥穷举攻击。

密钥穷举攻击(Brute Force Attack)是通过暴力破解的方式对所有可能的密钥值进行尝试来达到破解加密数据的目的的一种攻击方式。当开发者在使用某些签名算法(比如HS256)时,可将任意长度的字符串作为签名密钥生成签名,此时若使用一个长度较短的弱密钥,将会导致密钥穷举攻击。图5为利用python暴力破解脚本破解弱密钥签名的JWT令牌的运行结果。

图5 python破解弱密钥签名的JWT令牌

2 基于国密SM9的JWT强身份认证方案

2.1 国密SM9算法简介

SM9标识密码算法[16]是利用椭圆曲线对实现的基于标识的密码算法,算法的安全性主要是建立在有关双线性对问题难解性的基础之上。SM9算法主要由三部分组成,分别是数字签名算法、密钥交换协议、密钥封装机制和公钥加密算法[17]。该文主要使用SM9算法中的数字签名部分,主要分为三个步骤:密钥产生、生成签名和签名验证。

参数说明见文献[17],下面主要描述SM9数字签名算法的各个步骤。

(1)密钥产生。

KGC(Key Generation Center,密钥生成中心)[16]产生随机数ks∈[1,N-1]作为签名主私钥,Ppub=[ks]P2作为签名主公钥,则签名主密钥对为(s,Ppub),Ppub公开,ks由KGC保存。而用户签名密钥对为(Ppub-A,dA),其中用户签名公钥(Ppub-A=[H1(IDA‖hid)]P+Ppub,用户签名私钥dA=[s·(H1(IDA‖hid)+s)-1]P1.

(2)生成签名。

授权服务器根据用户A的签名私钥dA运用SM9数字签名算法对消息M进行计算,生成数字签名(h,S),签名生成流程如表1所示。

表1 生成数字签名的算法流程

(3)签名验证。

资源服务器根据SM9数字签名算法对用户A发送的消息M'和数字签名(h',S')进行验证,签名验证过程中需要进行的步骤如表2所示。

表2 验证数字签名的算法流程

2.2 基于SM9的JWT强身份认证方案的令牌生成过程

基于SM9的JWT强身份认证方案提出了一种与传统的JWT技术不同的令牌生成方式,生成过程如图6所示。头部(Header)和载荷(Payload)部分内容分别为Cheader和Cpayload,从Payload中获取发行时间Valiat和用户Alice的身份标识UidAlice,将UidAlice作为国密SM9数字签名算法的用户标识,Valiat作为新鲜因子。

图6 JWT令牌生成过程

(1)通过国密SM3密码杂凑算法Hsm3将头部和载荷部分均映射成长度为M的数字串,即分别计算Sh=Hsm3(Cheader,Valiat)和Sp=Hsm3(Cpayload,Valiat);

(2)计算消息Msg=Sh⊕Sp;

(3)利用国密SM9数字签名算法对Msg进行签名,运算结果Valsig作为令牌JwtToken的签名部分;

(4)将Cheader和Cpayload依次作为自变量x代入公式y=fbase64(x)进行计算,其中x={Cheader,Cpayload},运算结果为Valheader和Valpayload;

(5)最终,将三部分结合起来得到JWT令牌,即JwtToken=Valheader+Valpayload+Valsig。

2.3 强身份认证方案工作原理

基于JWT的身份认证方案可应用的场景有Web应用[18]、s桌面应用[19]、移动应用[19]和嵌入式应用[20],该文提出的基于SM9的JWT强身份认证方案也可以在这些场景中实现对用户的身份认证。身份认证流程如图7所示,主要包括用户Alice、客户端C、授权服务器Sa和资源服务器Sr四个节点;KGC生成Sa的签名主私钥ks和签名主公钥Ppub,Sa保存主私钥ks,公开主公钥Ppub。

图7 基于JWT的强身份认证方案工作流程

(1)用户Alice访问客户端C。

(2)Alice通过客户端C向授权服务器Sa提交用户凭据TAlice。

(3)授权服务器Sa验证凭据TAlice,验证通过后,生成成功状态码,并利用主私钥ks和身份标识UidAlice生成签名私钥dAlice,然后结合国密SM9算法生成令牌JwtToken;若验证不通过,则授权服务器Sa将会生成失败状态码。

(4)授权服务器Sa将状态码和JWT令牌返回给客户端C。

(5)客户端C携带JWT令牌向资源服务器Sr请求相关资源。

(6)资源服务器Sr根据主公钥Ppub和身份标识UidAlice生成Alice的签名公钥Ppub-Alice,利用Ppub-Alice验证令牌JwtToken。

(7)若资源服务器Sr验证通过,则处理请求并返回对应的服务器资源信息;反之,则返回处理失败状态码。

3 安全性分析与比较

3.1 数据完整性

授权服务器对客户端发送的用户凭据验证通过后,利用国密SM9数字签名算法生成令牌JwtToken的签名部分。如果在交互过程中,攻击者截获数据包,并对令牌JwtToken内容进行了篡改,那么相对应的签名部分也会发生变化,从而无法验证签名。因此,该方案可以保证数据完整性,防止数据在传输过程中被篡改。

3.2 不可抵赖性

授权服务器首先对令牌JwtToken的前两部分进行运算,然后利用私钥对运算结果进行签名生成第三部分。客户端可利用授权服务器发布的公钥对签名进行验证,从而确定签名来源的正确性;同时资源服务器也可利用公钥对令牌JwtToken进行验证,验证通过后再对用户请求进行处理。因此,该方案可以实现授权服务器的不可抵赖性,从而保证了数据的可靠性和真实性。

3.3 抗重放攻击

重放攻击(Replay Attacks)[21]又称回放攻击,指的是攻击者发送一个服务器已经接收过的包,从而实现欺骗服务器的目的。该方案选取时间戳作为新鲜因子嵌入在待签名的消息中,服务器在验证签名时,首先会验证令牌JwtToken的新鲜性,如果请求的时间不在有效时间范围内,即令牌JwtToken已经失效了,将会跳转到登录页面,重放数据包如图8所示。加入时间戳作为新鲜因子,有效预防了重放攻击,保证了数据的唯一性和失效性。

图8 抗重放攻击测试数据包

3.4 可抵抗针对头部(Header)的“none”算法绕过攻击

该文在前面提到将头部alg参数设置为“none”时,后端服务器将不执行验证签名的过程,从而导致客户端携带无签名的Token也可进行资源访问。该文提出的认证方案中签名认证过程是基于代码中定义的国密SM9算法进行认证,与alg参数无关;同时当alg参数值不等于SM9时,令牌JwtToken将会失效。图9为模拟客户端携带“none”算法的JWT向服务器请求资源的过程。

图9 可抵抗针对头部(Header)的“none”算法绕过攻击测试数据包

3.5 可抵抗针对载荷(Payload)的敏感信息泄露攻击

JWT在载荷(Payload)部分存放于用户实体的相关信息进行身份认证[4],如果开发者在声明用户实体的过程中直接明文存储相关信息,将会导致敏感信息泄露攻击。该文提出的认证方案针对载荷(Payload)部分所需要声明的用户实体信息均进行了编码操作,并且不将用户敏感信息存放在令牌JwtToken中,保证了用户信息的安全性,防止敏感信息泄露漏洞的出现。图10为模拟攻击者截获令牌JwtToken后解密的内容。

图10 解密令牌JwtToken

3.6 可抵抗针对签名(Signature)的算法混淆攻击

该文提出的认证方案中授权服务器利用私钥生成签名,客户端和资源服务器可以利用公钥进行签名验证,并且签名的生成和验证过程都是基于国密SM9算法,与alg参数无关。不过当alg参数值不等于SM9时,令牌JwtToken将被判定为无效令牌。

3.7 可抵抗针对签名(Signature)的密钥穷举攻击

该文提出的认证方案中的签名是国密SM9算法生成的,该算法是用椭圆曲线实现的基于标识的数字签名算法,具有很高的安全性和抗暴力破解能力。并且用户私钥是根据大整数类型的主私钥和用户标识生成的,假设攻击者获得了用户标识,也不可能通过猜测的方式获取用户私钥,从而无法破解加密数据,预防了密钥穷举攻击。

4 实验仿真与安全性比较

4.1 安全性比较

选用国内外应用了JWT技术实现了身份验证和授权的平台[22-23]进行安全性分析与对比,分析结果如表3所示。

表3 安全性对比分析

方案1:Microsoft Azure Active Directory;

方案2:百度数据开放平台;

方案3:文中方案。

4.2 方案实现与仿真

该文在Windows 10 64位操作系统下,使用基于 JDK 1.8的环境和IntelliJ IDEA 2021.2.1开发平台,实现了一个基于B/S架构的身份认证方案,其中服务器端采用了 SpringMVC和Mybatis框架进行开发,方案的核心代码如下:

服务器端根据用户Alice身份标识生成数字签名:

// 用户标识

String id_A = userId;

//初始化Header和Payload部分

String orgHeader = headerJson.to String();

String orgPayload = bodyJson.to String();

// 生成待签名的消息msg

String msg = getMsg(orgHeader,orgPayload,iatBytes);

// Header和Payload分别进行base64url编码

String header = base64URLEn(orgHeader);

String payload = base64URLEn(orgPayload);

// 对消息msg进行签名

String signatrue = sm9JwtUtils.sm9_sign(kgc,sm9,P_pub,ks,id_A,msg);

// 生成最终的JWT

String JwtToken = joinWithDot(header,payload,signatrue);

服务器端验证签名:

// JWT令牌解析

String header = base64URLDe(tokenParts[0]);

String payload = base64URLDe(tokenParts[1]);String signature = tokenParts[2];

// 解析payload部分

JSONObject payloadJson = new JSONObject(payload);

// 获取用户标识

String id_A = payloadJson.get("useId").to String();

// 获取 iat 字段的值,并转换为字节数组

byte[ ] iatBytes = ByteBuffer.allocate(Long.BYTES).putLong(payloadJson.optLong("iat")).array();

// 生成消息msg

String msg = getMsg(header,payload,iatBytes);

//验证签名

boolean flag = sm9JwtUtils.sm9_verify(kgc,sm9,P_pub,ks,id_A,msg,signature);

(1)用户Alice在客户端通过登录页面向授权服务器端提交用户凭据,验证通过后,授权服务器端根据用户标识UidAlice和主私钥ks生成令牌JwtToken,并返回给客户端,数据包如图11所示。

图11 服务器端将JWT令牌给客户端的数据包

(2)客户端携带令牌JwtToken向资源服务器请求资源,资源服务器解析传递令牌JwtToken,获取用户标识UidAlice,并计算消息Msg,然后再根据授权服务器公开的主公钥Ppub和UidAlice验证令牌JwtToken中的签名部分Valsig。如果验证通过,资源服务器将根据UidAlice处理客户端请求,并返回对应的资源,资源请求交互过程的数据包如图12所示。

图12 资源请求交互过程

5 结束语

针对JWT技术中存在的一些漏洞,该文提出了一种基于国密SM9算法的JWT强身份认证方案。与现有开放平台所使用的JWT技术进行安全性分析与比较,可知该认证方案具有数据完整性、不可抵赖性和抗重放攻击等特点,并且可抵抗“none”算法绕过、敏感信息泄露、算法混淆和密钥穷举等攻击。但实验表明该方案的运行效率不高,有待进一步完善。

猜你喜欢
令牌数字签名私钥
清扫机器人避障系统区块链私钥分片存储方法
称金块
比特币的安全性到底有多高
基于改进ECC 算法的网络信息私钥变换优化方法
浅析计算机安全防护中数字签名技术的应用
基于路由和QoS令牌桶的集中式限速网关
动态令牌分配的TCSN多级令牌桶流量监管算法
一种基于虚拟私钥的OpenSSL与CSP交互方案
基于数字签名的QR码水印认证系统
数字签名简述