前言
对于软件研发来说,即使只限定在“软件架构设计”这个语境下,系统安全仍然是一个很大的话题。它不仅包括“防御系统被黑客攻击”这样狭隘的安全,还包括一些与管理、运维、审计等领域主导的相关安全性问题,比如说安全备份与恢复、安全审计、防治病毒,等等。
当前在架构安全这个领域下,这里只讨论如下话题:
- 认证(Authentication):系统如何正确分辨出操作用户的真实身份?(你是谁)
- 授权( Authorization):系统如何控制一个用户该看到哪些数据、能操作哪些功能?(你能干什么)
- 凭证(Credentials):系统如何保证它与用户之间的承诺是双方当时真实意图的体现,是准确、完整且不可抵赖的?(你如何证明)
- 保密(Confidentiality):系统如何保证敏感数据无法被包括系统管理员在内的内外部人员所窃取、滥用?
- 传输(Transport Security):系统如何保证通过网络传输的信息无法被第三方窃听、篡改和冒充?
- 验证(Verification):系统如何确保提交到每项服务中的数据是合乎规则的,不会对系统稳定性、数据一致性、正确性产生风险?
简而言之:信息系统在为用户提供服务之前,总是希望先弄清楚“你是谁?”(认证)、“你能干什么?”(授权)以及“你如何证明?”(凭证)这三个基本问题的答案。
认证
概要
认证主要解决的问题是:【你】是谁。可能是用户,可能是外部的代码,外部的计算机。
认证主要应用场景
- 通讯信道上的认证:你和我建立通讯连接之前,要先证明你是谁。在网络传输(Network)场景中的典型是基于SSL/TLS传输安全层的认证。
- 通讯协议上的认证:你请求获取我的资源之前,要先证明你是谁。在互联网(Internet)场景中的典型是基于HTTP协议的认证。
通讯内容上的认证:你使用我提供的服务之前,要先证明你是谁。在万维网(World Wide Web)场景中的典型是基于Web内容的认证。
基于通讯协议:HTTP认证
HTTP认证框架提出的认证方案,是希望能把认证“要产生身份凭证”的目的,与“具体如何产生凭证”的实现给分开来。无论客户端是通过生物信息(指纹、人脸)、用户密码、数字证书,还是其他方式来生成凭证,都是属于如何生成凭证的具体实现,都可以包容在HTTP协议预设的框架之内
实现方案
HTTP Basic Authentication:HTTP Basic认证是一种以演示为目的的认证方案,通过采用BASE64加密来完成,在一些不要求安全性的场合也有实际应用,比如你家里的路由器登录,有可能就是这种认证方式。
- Digest:RFC 7616,HTTP摘要认证,你可以把它看作是Basic认证的改良版本,针对Base64明文发送的风险,Digest认证把用户名和密码加盐(一个被称为Nonce的变化值作为盐值)后,再通过MD5/SHA等哈希算法取摘要发送出去。这种认证方式依然是不安全的,无论客户端使用何种加密算法加密,无论是否采用了Nonce这样的动态盐值去抵御重放和冒认,当遇到中间人攻击时,依然存在显著的安全风险
- Bearer:RFC 6750,基于OAuth 2.0规范来完成认证,OAuth 2.0是一个同时涉及到认证与授权的协议
- HOBA:RFC 7486 ,HOBA是HTTP Origin-Bound Authentication的缩写,这是一种基于自签名证书的认证方案。基于数字证书的信任关系主要有两类模型,一类是采用CA(Certification Authority)层次结构的模型,由CA中心签发证书;另一种是以IETF的Token Binding协议为基础的OBC(Origin Bound Certificates)自签名证书模型
基于通讯内容:Web认证
依靠内容而不是传输协议来实现的认证方式,在万维网里就被称为“Web认证”,由于在实现形式上,登录表单占了绝对的主流,因此它通常也被称为“表单认证”(Form Authentication)。
安全框架(认证实现)
Apache Shiro
Spring Security
只从目标上来看,两个安全框架提供的功能都很类似,大致包括以下四类:
- 认证功能:以HTTP协议中定义的各种认证、表单等认证方式确认用户身份
- 安全上下文:用户获得认证之后,要开放一些接口,让应用可以得知该用户的基本资料、用户拥有的权限、角色,等等。
- 授权功能:判断并控制认证后的用户对什么资源拥有哪些操作许可
- 密码的存储与验证:密码是烫手的山芋,不管是存储、传输还是验证,都应该谨慎处理.
授权
概要
授权主要解决的问题是:你能干什么。“授权”这个概念通常伴随着“认证”“审计”“账号”一同出现,被合称为AAAA(Authentication、Authorization、Audit、Account)。授权行为在程序中的应用也是非常广泛的,我们给某个类或某个方法设置范围控制符(如public、protected、private、),本质上也是一种授权(访问控制)行为。
而在安全领域中,我们所谈论的授权就更要具体一些,它通常涉及到以下两个相对独立的问题:
- 确保授权的过程可靠:如何既让第三方系统能够访问到所需的资源,又能保证其不泄露用户的敏感数据?现在,常用的多方授权协议主要有OAuth 2.0和SAML 2.0
确保授权的结果可控:授权的结果是用于对程序功能或者资源的访问控制(Access Control)。现在,已形成理论体系的权限控制模型有很多,比如自主访问控制(Discretionary Access Control,DAC)、强制访问控制(Mandatory Access Control,MAC)、基于属性的访问控制(Attribute-Based Access Control,ABAC),还有最为常用的基于角色的访问控制(Role-Based Access Control,RBAC)
OAuth2.0
OAuth 2.0是一种相对复杂繁琐的认证授权协议。它是在RFC 6749中定义的国际标准,RFC 6749正文的第一句就阐明了OAuth 2.0是面向于解决第三方应用(Third-Party Application)的认证授权协议。
OAuth2.0的解决方案核心是:用令牌来代替用户的密码等作为授权凭证,有了令牌之后,哪怕令牌被泄漏,也不会导致密码的泄漏;令牌上可以设定访问资源的范围以及时效性;每个应用都持有独立的令牌,哪个失效都不会波及其他。授权流程
关键术语
例如Javis(AI应用)接入淘宝授权登录,获取淘宝的昵称
第三方应用(Third-Party Application):需要得到授权访问我资源的那个应用,即Javis.
- 授权服务器(Authorization Server):能够根据我的意愿提供授权(授权之前肯定已经进行了必要的认证过程,但它与授权可以没有直接关系)的服务器,即此场景中的“淘宝的登录服务器”。
- 资源服务器(Resource Server):能够提供第三方应用所需资源的服务器,它与认证服务可以是相同的服务器,也可以是不同的服务器,即此场景中的“淘宝用户昵称”。
- 资源所有者(Resource Owner): 拥有授权权限的人,即此场景中的“授权登录人”。
操作代理(User Agent):指用户用来访问服务器的工具,对于人类用户来说,这个通常是指浏览器。但在微服务中,一个服务经常会作为另一个服务的用户,此时指的可能就是HttpClient、RPCClient或者其他访问途径。
OAuth2.0授权流程
为了解决用令牌替代密码的问题,OAuth 2.0一共提出了四种不同的授权方式:
授权码模式(Authorization Code)
- 简化模式(隐式)(Implicit)
- 密码模式(Resource Owner Password Credentials)
- 客户端模式(Client Credentials)
授权码模式
授权码模式是四种模式中最严谨的,它考虑到了几乎所有敏感信息泄露的预防和后果。
在开始进行授权过程之前,第三方应用要先到授权服务器上进行注册。所谓的注册,是指第三方应用向认证服务器提供一个域名地址,然后从授权服务器中获取ClientID和ClientSecret,以便能够顺利完成如下的授权过程:
- 第三方应用将资源所有者(用户)导向授权服务器的授权页面,并向授权服务器提供ClientID及用户同意授权后的回调URI,这是第一次客户端页面转向。
- 授权服务器根据ClientID确认第三方应用的身份,用户在授权服务器中决定是否同意向该身份的应用进行授权。注意,用户认证的过程未定义在此步骤中,在此之前就应该已经完成。
- 如果用户同意授权,授权服务器将转向第三方应用在第1步调用中提供的回调URI,并附带上一个授权码和获取令牌的地址作为参数,这是第二次客户端页面转向。
- 第三方应用通过回调地址收到授权码,然后将授权码与自己的ClientSecret一起作为参数,通过服务器向授权服务器提供的获取令牌的服务地址发起请求,换取令牌。该服务器的地址应该与注册时提供的域名处于同一个域中。
- 授权服务器核对授权码和ClientSecret,确认无误后,向第三方应用授予令牌。令牌可以是一个或者两个,其中必定要有的是访问令牌(Access Token),可选的是刷新令牌(Refresh Token)。访问令牌用于到资源服务器获取资源,有效期较短,刷新令牌用于在访问令牌失效后重新获取,有效期较长。
- 资源服务器根据访问令牌所允许的权限,向第三方应用提供资源。
常见问题:
- 会不会有其他应用冒充第三方应用骗取授权?
ClientID代表一个第三方应用的“用户名”,这项信息是可以完全公开的。但ClientSecret应当只有应用自己才知道,这个代表了第三方应用的“密码”。在第5步发放令牌时,调用者必须能够提供ClientSecret才能成功完成。只要第三方应用妥善保管好ClientSecret,就没有人能够冒充它。
- 为什么要先发放授权码,再用授权码换令牌?
这是因为客户端转向(通常就是一次HTTP 302重定向)对于用户是可见的。换言之,授权码可能会暴露给用户以及用户机器上的其他程序,但由于用户并没有ClientSecret,光有授权码也无法换取到令牌,所以就避免了令牌在传输转向过程中被泄漏的风险。
- 为什么要设计一个时限较长的刷新令牌和时限较短的访问令牌?不能直接把访问令牌的时间调长吗?
这是为了缓解OAuth 2.0在实际应用中的一个主要缺陷。因为通常情况下,访问令牌一旦发放,除非超过了令牌中的有效期,否则很难有其他方式让它失效。所以访问令牌的时效性一般会设计得比较短,比如几个小时,如果还需要继续用,那就定期用刷新令牌去更新,授权服务器可以在更新过程中决定是否还要继续给予授权。
尽管授权码模式是很严谨的,但它并不够好用,这不仅仅体现在它那繁复的调用过程上,还体现在它对第三方应用提出了一个“貌似不难”的要求:第三方应用必须有应用服务器,因为第4步要发起服务端转向,而且要求服务端的地址必须与注册时提供的地址在同一个域内。
如果无法提供应用服务器,就引出了OAuth 2.0的第二种授权模式:隐式授权。
隐式授权
隐式授权省略掉了通过授权码换取令牌的步骤,整个授权过程都不需要服务端的支持,一步到位。而使用的代价是在隐式授权中,授权服务器不会再去验证第三方应用的身份,因为已经没有应用服务器了,ClientSecret没有人保管,就没有存在的意义了。
但隐式授权中的授权服务器,还是会限制第三方应用的回调URI地址必须与注册时提供的域名一致,虽然有可能会被DNS污染之类的攻击所攻破,但这也算是它尽可能地努力了一下吧。同样的原因,隐式授权也不能避免令牌暴露给资源所有者,不能避免用户机器上可能意图不轨的其他程序、HTTP的中间人攻击等风险。
在这个交互过程里,隐式模式与授权码模式的显著区别是授权服务器在得到用户授权后,直接返回了访问令牌,这很明显会降低授权的安全性。
但OAuth 2.0仍然在尽可能地努力做到相对安全,比如前面我提到在隐式授权中,尽管不需要用到服务端,但仍然需要在注册时提供回调域名,此时会要求该域名与接受令牌的服务处于同一个域内。此外,同样基于安全考虑,在隐式模式中也明确禁止发放刷新令牌。
Fragment就是地址中“#”号后面的部分,比如这个地址:https://example.com/#/detail/1. 后面的/detail/1便是Fragment.这个语法是在RFC 3986中定义的,该规范中解释了这是用于客户端定位的URI从属资源,比如在HTML中,就可以使用Fragment来做文档内的跳转而不会发起服务端请求。
此外,RFC 3986还规定了,如果浏览器对一个带有Fragment的地址发出Ajax请求,那Fragment是不会跟随请求被发送到服务端的,只能在客户端通过Script脚本来读取。
所以,隐式授权巧妙地利用这个特性,尽最大努力地避免了令牌从操作代理到第三方服务之间的链路,存在被攻击而泄露出去的可能性。
至于认证服务器到操作代理之间的这一段链路的安全,则只能通过TLS(即HTTPS)来保证不会受到中间人攻击了,我们可以要求认证服务器必须都是启用HTTPS的,但无法要求第三方应用同样都支持HTTPS。
密码模式
授权码与隐式模式都属于授权模式,与认证没有直接关系,如何认证用户的真实身份,跟如何进行授权是两个互相独立的过程。但在密码模式里,认证和授权就被整合成了同一个过程。
密码模式原本的设计意图是,仅限于在用户对第三方应用是高度可信任的场景中使用,因为用户需要把密码明文提供给第三方应用,第三方以此向授权服务器获取令牌。这种高度可信的第三方是非常罕见的,尽管在介绍OAuth 2.0的材料中,经常举的例子是“操作系统作为第三方应用向授权服务器申请资源”,但真实应用中极少遇到这样的情况,合理性依然十分有限。
一般而言,如果要采用密码模式,那“第三方”属性就必须弱化,把“第三方”看作是系统中与授权服务器相对独立的子模块,在物理上独立于授权服务器部署,但是在逻辑上与授权服务器仍同属一个系统。这样把认证和授权一并完成的密码模式,才会有合理的应用场景。
在密码模式下,“如何保障安全”的职责无法由OAuth 2.0来承担,只能由用户和第三方应用来自行保障,尽管OAuth 2.0在规范中强调到“此模式下,第三方应用不得保存用户的密码”,但这并没有任何技术上的约束力。
客户端模式
客户端模式是四种模式中最简单的,它只涉及到两个主体:第三方应用和授权服务器。如果我们严谨一点,现在叫“第三方应用”其实已经不合适了,因为已经没有了“第二方”的存在,资源所有者、操作代理在客户端模式中都是不必出现的。甚至严格来说,叫“授权”都已经不太恰当,毕竟资源所有者都没有了,也就不会有谁授予谁权限的过程。
设备码模式
设备码模式用于在无输入的情况下区分设备是否被许可使用,典型的应用就是手机锁网解锁,或者设备激活。
采用设备码模式在进行验证时,设备需要从授权服务器获取一个URI地址和一个用户码,然后需要用户手动或设备自动地到验证URI中输入用户码。在这个过程中,设备会一直循环,尝试去获取令牌,直到拿到令牌或者用户码过期为止。
RBAC
概要
授权的结果是用于对程序功能或者资源的访问控制(Access Control),那如何做访问控制呢?最常用的权限控制模型为RBAC(Role-Based Access Control)
RBAC基础概念
所有的访问控制模型,实质上都是在解决同一个问题:谁(User)拥有什么权限(Authority)去操作(Operation)哪些资源(Resource)。
RBAC模型在业界有很多种说法,其中,最具系统性且得到了普遍认可的说法,是美国乔治梅森(George Mason)大学信息安全技术实验室提出的RBAC96模型。
为了避免对每一个用户设定权限,RBAC将权限从用户身上剥离,改为绑定到“角色”(Role)上,“权限控制”这项工作,就可以具体化成针对“角色拥有操作哪些资源的许可”这个逻辑表达式的值是否为真的求解过程。
RBAC的要素关系:
除了前面提到的用户、角色和资源以外,图上还出现了一个新的名词“许可”(Permission)。
许可其实是抽象权限的具象化体现。权限在RBAC系统中的含义是“允许何种操作作用于哪些资源之上”,这句话的具体实例即为“许可”。
提出许可这个概念的目的,其实跟提出角色的目的是完全一致的,只是许可会更抽象。角色为的是解耦用户与权限之间的多对多关系,而许可为的是解耦操作与资源之间的多对多关系。比如说,不同的数据都能够有增、删、改等操作,而如果把操作与数据搅和在一起,也会面临前面我提到的权限配置膨胀的问题。
访问控制
建立访问控制模型的基本目的就是为了管理垂直权限和水平权限。垂直权限即功能权限,比如前面提到的审稿编辑有通过审核的权限、开发经理有代码提交的权限、出纳有从账户提取资金的权限,这一类某个角色完成某项操作的许可,都可以直接翻译为功能权限。
由于实际应用与权限模型具有高度对应的关系,因此把权限从具体的应用中抽离出来,放到通用的模型中是相对容易的,Spring Security、Apache Shiro等权限框架就是这样的抽象产物,大多数系统都能采用这些权限框架来管理功能权限。
一般来说,数据权限是很难抽象与通用的,仅在角色层面进行控制并不能满足全部业务的需要。很多时候,数据权限必须具体到用户,甚至具体管理到发生数据的某一行、某一列之上,因此数据权限基本上只能由信息系统自主来完成,并不存在能放之四海皆准的通用数据权限框架。
Spring Security的RBAC实现
从实现角度来看,Spring Security中Role和Authority的差异很小,它们完全共享同一套存储结构,唯一的差别就只是Role会在存储时,自动带上“ROLE_”前缀罢了。
但从使用角度来看,Role和Authority的差异可以很大,用户可以自行决定系统中到底Permission只能对应到角色身上,还是可以让用户也拥有某些角色中没有的权限。
参考模型
基础模型
角色分级模型
给角色可以分成几个等级,每个等级权限不同,从而实现更细粒度的权限管理。
用户组模型
增加用户组概念,直接给用户组分配角色,再把用户加入用户组。这样用户除了拥有自身的权限外,还拥有了所属用户组的所有权限
http://www.woshipm.com/pd/440765.html
凭证
JWT:解决认证授权问题的无状态方案
JWT(JSON Web Token)定义于RFC 7519标准之中,是目前广泛使用的一种令牌格式,尤其经常与OAuth 2.0配合应用于分布式的、涉及多方的应用系统中。
它最常见的使用方式是附在名为Authorization的Header发送给服务端,其前缀在RFC 6750中被规定为Bearer。
JWT是OAuth2.0 协议的的令牌方案,举个例子:
GET /restful/products/1 HTTP/1.1
Host: icyfenix.cn
Connection: keep-alive
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJpY3lmZW5peCIsInNjb3BlIjpbIkFMTCJdLCJleHAiOjE1ODQ5NDg5NDcsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiIsIlJPTEVfQURNSU4iXSwianRpIjoiOWQ3NzU4NmEtM2Y0Zi00Y2JiLTk5MjQtZmUyZjc3ZGZhMzNkIiwiY2xpZW50X2lkIjoiYm9va3N0b3JlX2Zyb250ZW5kIiwidXNlcm5hbWUiOiJpY3lmZW5peCJ9.539WMzbjv63wBtx4ytYYw_Fo1ECG_9vsgAn8bheflL8
JWT只解决防篡改的问题,并不解决防泄露的问题,所以令牌默认是不加密的. 实际场景中,生成JWT时,会对对应的内容做Base64转码。
JWT令牌的三部分结构
令牌的第一部分是令牌头(Header),其内容如下所示:
{ "alg": "HS256", "typ": "JWT" }
它描述了令牌的类型(统一为typ:JWT)以及令牌签名的算法,示例中HS256为HMAC SHA256算法的缩写,其他各种系统支持的签名算法你可以参考JWT官网。
额外知识:散列消息认证码
HMAC: 散列消息认证码(Hash-based Message Authentication Code,HMAC)。你可以简单将它理解为一种带有密钥的哈希摘要算法,实现形式上通常是把密钥以加盐方式混入,与内容一起做哈希摘要。
HMAC哈希与普通哈希算法的差别是,普通的哈希算法通过Hash函数结果易变性,保证了原有内容未被篡改,而HMAC不仅保证了内容未被篡改过,还保证了该哈希确实是由密钥的持有人所生成的
令牌的第二部分是负载(Payload),这是令牌真正需要向服务端传递的信息。针对认证问题,负载至少应该包含能够告知服务端“这个用户是谁”的信息;针对授权问题,令牌至少应该包含能够告知服务端“这个用户拥有什么角色/权限”的信息。
JWT的负载部分是可以完全自定义的,我们可以根据具体要解决的问题,设计自己所需要的信息,只是总容量不能太大,毕竟它受HTTP Header大小的限制。下面我们来看一个JWT负载的例子:
{
"username": "icyfenix",
"authorities": [
"ROLE_USER",
"ROLE_ADMIN"
],
"scope": [
"ALL"
],
"exp": 1584948947,
"jti": "9d77586a-3f4f-4cbb-9924-fe2f77dfa33d",
"client_id": "bookstore_frontend"
}
另外,JWT在RFC 7519标准中,推荐(非强制约束)了七项声明名称(Claim Name),如果你在设计令牌时需要用到这些内容,我建议其字段名要与官方的保持一致:
- iss(Issuer):签发人。
- exp(Expiration Time):令牌过期时间。
- sub(Subject):主题。
- aud (Audience):令牌受众。
- nbf (Not Before):令牌生效时间。
- iat (Issued At):令牌签发时间。
- jti (JWT ID):令牌编号。
令牌的第三部分是签名(Signature)。签名的意思是:使用在对象头中公开的特定签名算法,通过特定的密钥(Secret,由服务器进行保密,不能公开)对前面两部分内容进行加密计算,产生签名值。
这里我们继续以前面例子里使用的JWT默认的HMAC SHA256算法为例,它会通过以下公式产生签名值:
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload) , secret)
签名的意义在于,它可以确保负载中的信息是可信的、没有被篡改的,也没有在传输过程中丢失任何信息。因为被签名的内容哪怕是发生了一个字节的变动,也会导致整个签名发生显著变化。
此外,由于签名这件事情,只能由认证授权服务器完成(只有它知道Secret),任何人都无法在篡改后重新计算出合法的签名值,所以服务端才能够完全信任客户端传上来的JWT中的负载信息。
JWT默认的签名算法HMAC SHA256是一种带密钥的哈希摘要算法,加密与验证过程都只能由中心化的授权服务来提供,所以这种方式一般只适合于授权服务与应用服务处于同一个进程中的单体应用。
JWT的缺陷
- 令牌难以主动失效
- 相对更容易遭受重放攻击
- 只能携带相当有限的数据
- 必须考虑令牌在客户端如何存储
- 无状态也不总是好的
保密
保密是加密和解密的统称,意思就是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因为不知道解密的方法,或者就算知晓解密的算法、但缺少解密所需的必要信息,所以仍然无法了解数据的真实内容。
那么,根据需要保密信息所处的不同环节,我们可以将其划分为“信息在客户端时的保密”“信息在传输时的保密”和“信息在服务端时的保密”三类,或者也可以进一步概括为“端的保密”和“链路的保密”两类
保密的强度
保密是有成本的,追求越高的安全等级,我们就要付出越多的工作量与算力消耗。
- 摘要代替明文
如果密码本身比较复杂,那么一次简单的哈希摘要就至少可以保证,即使在传输过程中有信息泄露,也不会被逆推出原信息;即使密码在一个系统中泄露了,也不至于威胁到其他系统的使用。但这种处理不能防止弱密码被彩虹表攻击所破解
- 先加盐值)再做哈希
盐值可以替弱密码建立一道防御屏障,在一定程度上可以防御已有的彩虹表攻击。但它并不能阻止加密结果被监听、窃取后,攻击者直接发送加密结果给服务端进行冒认。
- 将盐值变为动态值能有效防止冒认
- 加入动态令牌防止重放攻击
- 启用HTTPS来应对因嗅探而导致的信息泄露问题
传输
传输安全的基础,摘要、加密与签名
摘要与加密和签名的本质区别
摘要也被叫做是数字摘要(Digital Digest)或数字指纹(Digital Fingerprint)。在JWT令牌中,默认的签名信息就是通过令牌头中指定的哈希算法(HMAC SHA256),针对令牌头、负载和密钥所计算出来的摘要值。
理想的哈希算法都具备这样两个特性:
一是易变性。这是指算法的输入端发生了任何一点细微变动,都会引发雪崩效应(Avalanche Effect),导致输出端的结果产生极大的变化。
这个特性经常被用来做校验,以此保护信息在传输的过程中不会被篡改。比如在互联网下载大文件,通常都会附有一个哈希校验码,用来确保下载下来的文件没有因网络或其他原因,与原文件产生任何偏差。
二是不可逆性。要知道,摘要的过程是单向的,我们不可能从摘要的结果中,逆向还原出输入值来。
加密与摘要的本质区别就在于,摘要是不可逆的,而加密是可逆的,逆过程就是解密
加密算法的两大类型:对称与非对称
首先我们来看看对称加密算法。
对称加密的缺点显而易见:因为加密和解密都使用相同的密钥,那么当通讯的成员数量增加时,为了保证两两通讯都能采用独立的密钥,密钥数量就要与成员数量的平方成正比,这必然就会面临密钥管理的难题
因此,在1970年代中后期出现的非对称加密算法,就从根本上解决了密钥分发的难题。
非对称加密算法把密钥分成了公钥和私钥,公钥可以完全公开,无需安全传输的保证。私钥由用户自行保管,不参与任何通讯传输。这两个密钥谁加密、谁解密,就构成了两种不同的用途:
- 公钥加密,私钥解密,这种就是加密,用于向私钥所有者发送信息,这个信息可能被他人篡改,但是无法被他人得知。举个例子,如果甲想给乙发一个安全保密的数据,那么应该甲乙各自有一个私钥,甲先用乙的公钥加密这段数据,再用自己的私钥加密这段加密后的数据,最后再发给乙。这样确保了内容既不会被读取,也不能被篡改。
- 私钥加密,公钥解密,这种就是签名,用于让所有公钥所有者验证私钥所有者的身份,并能用来防止私钥所有者发布的内容被篡改。但是它不用来保证内容不被他人获得。
这两种用途理论上肯定是成立的,但在现实中一般却不成立,因为单靠非对称加密算法,既做不了加密也做不了签名。原因是,不管加密还是解密,非对称加密算法的计算复杂度都相当高,性能比对称加密要差上好几个数量级(不是好几倍)
所以在加密方面,现在一般会结合对称与非对称加密的优点,通过混合加密来保护信道传输的安全
通常我们的做法是,用非对称加密来安全地传递少量数据给通讯的另一方,然后再以这些数据为密钥,采用对称加密来安全高效地大量加密传输数据。这种由多种加密算法组合的应用形式,就被称为“密码学套件”,非对称加密在这个场景中发挥的作用被称为“密钥协商”。
然后,在签名方面,现在一般会结合摘要与非对称加密的优点,通过对摘要结果做加密的形式来保证签名的适用性。由于对任何长度的输入源做摘要之后,都能得到固定长度的结果,所以只要对摘要的结果进行签名,就相当于对整个输入源进行了背书,这样就能保证一旦内容遭到了篡改,摘要结果就会变化,签名也就马上失效了。
如何通过数字证书达成共同信任?
有了哈希摘要、对称和非对称加密之后,签名还是无法保证负载中的信息不可篡改、不可抵赖。所以,当我们无法以“签名”的手段来达成信任时,就只能求助于其他途径。
与现实世界类似,我们一般会借助权威机构来验证陌生信息,在计算机世界里的权威机构就是:PKI
额外知识:公开密钥基础设施(Public Key Infrastructure,PKI)
又称公开密钥基础架构、公钥基础建设、公钥基础设施、公开密码匙基础建设或公钥基础架构,是一组由硬件、软件、参与者、管理政策与流程组成的基础架构,其目的在于创造、管理、分配、使用、存储以及撤销数字证书。
密码学上,公开密钥基础建设借着数字证书认证中心(Certificate Authority,CA)将用户的个人身份跟公开密钥链接在一起。对每个证书中心用户的身份必须是唯一的。链接关系通过注册和发布过程创建,取决于担保级别,链接关系可能由CA的各种软件或在人为监督下完成。PKI的确定链接关系的这一角色称为注册管理中心(Registration Authority,RA)。RA确保公开密钥和个人身份链接,可以防抵赖。
证书是权威CA中心对特定公钥信息的一种公证载体,你也可以理解为是权威CA对特定公钥未被篡改的签名背书。由于客户的机器上已经预置了这些权威CA中心本身的证书(可以叫做CA证书或者根证书),这样就让我们在不依靠网络的前提下,使用根证书里面的公钥信息,对其所签发的证书中的签名进行确认。
PKI中采用的证书格式是X.509标准格式,它定义了证书中应该包含哪些信息,并描述了这些信息是如何编码的。其中最关键的,就是认证机构的数字签名和公钥信息两项内容。
第一是版本号(Version):它会指出该证书使用了哪种版本的X.509标准(版本1、版本2或是版本3)。版本号会影响证书中的一些特定信息,在这个例子当中,目前的版本为3。
Version: 3 (0x2)
第二是序列号(Serial Number):这是由证书颁发者分配的本证书的唯一标识符。
Serial Number: 04:00:00:00:00:01:15:4b:5a:c3:94
第三是签名算法标识符(Signature Algorithm ID):它是签证书的算法标识,由对象标识符加上相关的参数组成,用于说明本证书所用的数字签名算法。比如,SHA1和RSA的对象标识符就用来说明,该数字签名是利用RSA对SHA1的摘要结果进行加密。
Signature Algorithm: sha1WithRSAEncryption
第四是认证机构的数字签名(Certificate Signature):这是使用证书发布者私钥生成的签名,以确保这个证书在发放之后没有被篡改过。
第五是认证机构(Issuer Name): 即证书颁发者的可识别名。
Issuer: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Organization Validation CA - SHA256 - G2
第六是有效期限(Validity Period): 即证书起始日期和时间以及终止日期和时间,意为指明证书在这两个时间内有效。
Validity
Not Before: Nov 21 08:00:00 2020 GMT
Not After : Nov 22 07:59:59 2021 GMT
第七是主题信息(Subject):证书持有人唯一的标识符(Distinguished Name),这个名字在整个互联网上应该是唯一的,通常使用的是网站的域名。
Subject: C=CN, ST=GuangDong, L=Zhuhai, O=Awosome-Fenix, CN=*.icyfenix.cn
第八是公钥信息(Public-Key): 它包括了证书持有人的公钥、算法(指明密钥属于哪种密码系统)的标识符和其他相关的密钥参数。
传输安全层是如何隐藏繁琐的安全过程的?
在计算机科学里,隔离复杂性的最有效手段(没有之一)就是分层,如果一层不够就再加一层.
以广泛使用的TLS 1.2为例,传输安全层是如何保障所有信息都是第三方无法窃听(加密传输)、无法篡改(一旦篡改通讯算法会立刻发现)、无法冒充(证书验证身份)的。
TLS 1.2在传输之前的握手过程中,一共需要进行上下两轮、共计四次的通讯。握手过程的时序图如下:
1.浏览器将自己支持的一套加密规则发送给网站。
2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
3.浏览器获得网站证书之后浏览器要做以下工作:
a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
c) 使用约定好的HASH算法计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
4.网站接收浏览器发来的数据之后要做以下的操作:
a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
b) 使用密码加密一段握手消息,发送给浏览器。
5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
验证
缺失的校验会影响数据质量,而过度的校验也不会让系统更加健壮,反而在某种意义上会制造垃圾代码,甚至还会有副作用。数据校验类代码也是迫切需要被架构约束的。