Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

SSL/TLS协议运行机制

发表于 2016-10-14

互联网的通信安全,建立在SSL/TLS协议之上。

一、作用

不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。

1
2
3
(1) 窃听风险(eavesdropping):第三方可以获知通信内容。
(2) 篡改风险(tampering):第三方可以修改通信内容。
(3) 冒充风险(pretending):第三方可以冒充他人身份参与通信。

SSL/TLS协议是为了解决这三大风险而设计的,希望达到:

1
2
3
(1) 所有信息都是加密传播,第三方无法窃听。
(2) 具有校验机制,一旦被篡改,通信双方会立刻发现。
(3) 配备身份证书,防止身份被冒充。

互联网是开放环境,通信双方都是未知身份,这为协议的设计带来了很大的难度。而且,协议还必须能够经受所有匪夷所思的攻击,这使得SSL/TLS协议变得异常复杂。

二、历史

互联网加密通信协议的历史,几乎与互联网一样长。

1
2
3
4
5
1994年,NetScape公司设计了SSL协议(Secure Sockets Layer)的1.0版,但是未发布。
1995年,NetScape公司发布SSL 2.0版,很快发现有严重漏洞。
1996年,SSL 3.0版问世,得到大规模应用。
1999年,互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。
2006年和2008年,TLS进行了两次升级,分别为TLS 1.1版和TLS 1.2版。最新的变动是2011年TLS 1.2的修订版。

目前,应用最广泛的是TLS 1.0,接下来是SSL 3.0。但是,主流浏览器都已经实现了TLS 1.2的支持。

TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。

三、基本的运行过程

SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

但是,这里有两个问题。

(1)如何保证公钥不被篡改?

解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。

(2)公钥加密计算量太大,如何减少耗用的时间?

解决方法:每一次对话(session),客户端和服务器端都生成一个”对话密钥”(session key),用它来加密信息。由于”对话密钥”是对称加密,所以运算速度非常快,而服务器公钥只用于加密”对话密钥”本身,这样就减少了加密运算的消耗时间。

因此,SSL/TLS协议的基本过程是这样的:

(1) 客户端向服务器端索要并验证公钥。

(2) 双方协商生成”对话密钥”。

(3) 双方采用”对话密钥”进行加密通信。

上面过程的前两步,又称为”握手阶段”(handshake)。

四、握手阶段的详细过程

img

“握手阶段”涉及四次通信,我们一个个来看。需要注意的是,”握手阶段”的所有通信都是明文的。

4.1 客户端发出请求(ClientHello)

首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。

在这一步,客户端主要向服务器提供以下信息。

1
2
3
4
(1) 支持的协议版本,比如TLS 1.0版。
(2) 一个客户端生成的随机数,稍后用于生成"对话密钥"。
(3) 支持的加密方法,比如RSA公钥加密。
(4) 支持的压缩方法。

这里需要注意的是,客户端发送的信息之中不包括服务器的域名。也就是说,理论上服务器只能包含一个网站,否则会分不清应该向客户端提供哪一个网站的数字证书。这就是为什么通常一台服务器只能有一张数字证书的原因。

对于虚拟主机的用户来说,这当然很不方便。2006年,TLS协议加入了一个Server Name Indication扩展,允许客户端向服务器提供它所请求的域名。

4.2 服务器回应(SeverHello)

服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。

1
2
3
4
(1) 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
(2) 一个服务器生成的随机数,稍后用于生成"对话密钥"。
(3) 确认使用的加密方法,比如RSA公钥加密。
(4) 服务器证书。

除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供”客户端证书”。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。

4.3 客户端回应

客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。

如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。

1
2
3
(1) 一个随机数。该随机数用服务器公钥加密,防止被窃听。
(2) 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
(3) 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称”pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把”会话密钥”。

至于为什么一定要用三个随机数,来生成”会话密钥”,dog250解释得很好:

1
2
3
"不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。
对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,三个随机数通过一个密钥导出器最终导出一个对称密钥。
pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。"

此外,如果前一步,服务器要求客户端证书,客户端会在这一步发送证书及相关信息。

同时需要注意前两个随机数都是明文传输的,窃听者是可以轻易获取到的,只有最后一个PreMaster Secret 是加密传输的,只有拥有服务器私钥才能解密,一旦 PreMaster Secret 泄露,那么本次通信就就完全可被破解了。

  • PreMaster Secret 说明

PreMaster secret是在客户端使用RSA或者Diffie-Hellman等加密算法生成的。它将用来跟服务端和客户端在Hello阶段产生的随机数结合在一起生成Master secret(包含会话对称密钥)。在客户端使用服务单的公钥对PreMaster secret进行加密之后传送给服务端,服务端将使用私钥进行解密得到PreMaster secret。也就是说服务端和客户端都有一份相同的PreMaster secret和随机数。

premaster_secret 长度为 48 个字节,前 2 个字节是协议版本号,剩下的 46 个字节填充一个随机数。结构如下:

1
2
3
4
Struct {
byte Version[2];
byte random[46];
}

PreMaster secret前两个字节是TLS的版本号,这是一个比较重要的用来核对握手数据的版本号,因为在Client Hello阶段,客户端会发送一份加密套件列表和当前支持的SSL/TLS的版本号给服务端,而且是使用明文传送的,如果握手的数据包被破解之后,攻击者很有可能串改数据包,选择一个安全性较低的加密套件和版本给服务端,从而对数据进行破解。所以,服务端需要对密文中解密出来对的PreMaster版本号跟之前Client Hello阶段的版本号进行对比,如果版本号变低,则说明被串改,则立即停止发送任何消息。

  • Master secret 说明

需要注意的是,上面握手过程中存在三个Key(random_1,random_2,PreMaster Secret),而且服务器和客户端都保留着这三个值,客户端和服务端通过这三个值计算出同样的Master secret。Master secret是由系列的hash值组成的,结构如下:

Master secret 结构

其中,Client/Server write MAC key 是用来对数据进行验证的,Client/Server write encryption Key 是用来对数据进行加解密的会话密钥(session secret)。

关于Master Secret的计算请参考《Htttps SSL/TLS Session Secret(Key)计算》。

4.4 服务器的最后回应

服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的”会话密钥”。然后,向客户端最后发送下面信息。

1
2
(1)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
(2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用”会话密钥”加密内容。

img

五、应用数据传输

在所有的握手阶段都完成之后,就可以开始传送应用数据了。应用数据在传输之前,首先要附加上MAC secret,然后再对这个数据包使用write encryption key进行加密。在服务端收到密文之后,使用Client write encryption key进行解密,客户端收到服务端的数据之后使用Server write encryption key进行解密,然后使用各自的write MAC key对数据的完整性包括是否被串改进行验证。

六、对称加密 & 非对称加密

HTTPS 的通信过程中只在握手阶段使用了非对称加密,后面的通信过程均使用的对称加密。尽管非对称加密相比对称加密更加安全,但也存在两个明显缺点:

  1. CPU 计算资源消耗非常大。一次完全 TLS 握手,密钥交换时的非对称解密计算量占整个握手过程的 90% 以上。而对称加密的计算量只相当于非对称加密的 0.1%,如果应用层数据也使用非对称加解密,性能开销太大,无法承受。
  2. 非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是 2048 位,意味着待加密内容不能超过 256 个字节。

所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密。

非对称密钥交换算法是整个 HTTPS 得以安全的基石,充分理解非对称密钥交换算法是理解 HTTPS 协议和功能的关键。

七、证书

下面是使用 chrome 访问 Google 时查看的证书详细信息

Google  HTTPS证书

数字证书的格式普遍采用的是X.509V3国际标准,一个标准的X.509数字证书包含以下一些内容:

  • 证书的版本信息;
  • 证书的序列号,每个证书都有一个唯一的证书序列号;
  • 证书所使用的签名算法;
  • 证书的发行机构名称,命名规则一般采用X.500格式;
  • 证书的有效期,通用的证书一般采用UTC时间格式,它的计时范围为1950-2049;
  • 证书所有人的名称,命名规则一般采用X.500格式;
  • 证书所有人的公钥;
  • 证书发行者对证书的签名。

证书以链的形式组织,上级标识该证书的签发机构,验证证书的时候也是顺着这个链向上层层验证的,只有所有证书都是受信的,整个验证结果才是可信的。那么根证书是如何验证的呢?根证书是自信任的,在操作系统或者浏览其中都会默认一些受信任的 CA 机构根证书。

除非对这个根证书有绝对的信任才可以加入信任列表中,因为根证书是有权签发子证书的,如果根证书失信,那么对应的子证书的可信性就无从谈起,那么与使用相应证书的 HTTPS 网站通信的安全性就得不到保障了。当访问12306的时候,网站会提示把其提供的根证书加入到可信任列表里面,这就是为什么许多人反对这种做法的原因了。另外中国互联网信息中心(CNNIC)也曾发布过用于中间人攻击证书,详情可以参考这篇文章:谷歌称CNNIC发布伪造CA证书。

Mac 用户可以通过 钥匙串->系统根证书 查看系统默认信任的所有根证书,如果想取消信任(比如 CNNIC证书),可以双击,改为永不信任

八、安全性

针对 HTTPS 的攻击最主要的就是 SSL 劫持攻击,其分为两种:

HTTPS 替换为 HTTP

这种方式就是攻击者充当中间人和服务器通信,然后把相应的通信内容通过 HTTP 协议发送给客户端,由于 HTTP 协议是未加密的,于是就可以截获用户的访问数据。

这种攻击方式比较简单,通过代理,可以很容易的把 HTTPS 变成 HTTP,这个一方面需要用户留意网站是否有从 HTTPS 跳转到 HTTP 的行为,另一方面服务器也可以通过配置将所有HTTP的请求强制转移到HTTPS上。

HTTPS 劫持

这种方式攻击者为了获得 HTTPS 的明文传输内容,需要充当中间人,替换服务器发给用户的包含公钥的证书。攻击者既和用户之间建立了 HTTPS 链接,又和服务器建立了 HTTPS 链接。

在上面握手建立的过程中,由于用户的公钥是攻击者生成的,所以攻击者可以轻易获得握手中的数据。也就可以获取到和用户通信过程中的对称加密的密钥,攻击者可以通过密钥获取用户发送的数据,同时在使用和服务器通信的密钥加密后再发送给服务器。

这种攻击方式也有一个明显的问题就是攻击者生成的证书几乎是不可能被用户信任的,在这种情况下,用户浏览器通常会提示该网站的证书不可信,是否继续访问,这已经对用户进行了一个明显的警告了。

另外我们也可以通过这种对基于 HTTPS 的通信进行抓包分析。Mac 平台著名的抓包工具 Charles 就是基于这种方式,首先要求你信任一个它的证书,然后自己充当中间人对你与某个服务器的 HTTPS 通信进行抓包分析。

mysql 存储引擎

发表于 2016-09-27

什么是存储引擎?

存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和操作此表的类型)。

关系数据库表是用于存储和组织信息的数据结构,可以将表理解为由行和列组成的表格,类似于Excel的电子表格的形式。有的表简单,有的表复杂,有的表根本不用来存储任何长期的数据,有的表读取时非常快,但是插入数据时去很差;而我们在实际开发过程中,就可能需要各种各样的表,不同的表,就意味着存储不同类型的数据,数据的处理上也会存在着差异,那么。对于MySQL来说,它提供了很多种类型的存储引擎,我们可以根据对数据处理的需求,选择不同的存储引擎,从而最大限度的利用MySQL强大的功能。

MyISAM存储引擎

在mysql客户端中,使用以下命令可以查看MySQL支持的引擎。

1
show engines;

MyISAM表是独立于操作系统的,这说明可以轻松地将其从Windows服务器移植到Linux服务器;每当我们建立一个MyISAM引擎的表时,就会在本地磁盘上建立三个文件,文件名就是表名。例如,我建立了一个MyISAM引擎的tb_Demo表,那么就会生成以下三个文件:

  • tb_demo.frm,存储表定义;
  • tb_demo.MYD,存储数据;
  • tb_demo.MYI,存储索引。

MyISAM表无法处理事务,这就意味着有事务处理需求的表,不能使用MyISAM存储引擎。MyISAM存储引擎特别适合在以下几种情况下使用:

  • 选择密集型的表。MyISAM存储引擎在筛选大量数据时非常迅速,这是它最突出的优点。
  • 插入密集型的表。MyISAM的并发插入特性允许同时选择和插入数据。例如:MyISAM存储引擎很适合管理邮件或Web服务器日志数据。

InnoDB存储引擎

InnoDB给Mysql的表提供了 事务、回滚、崩溃修复能力、多版本并发控制的事务安全、间隙锁(可以有效的防止幻读的出现)、支持辅助索引、聚簇索引、自适应hash索引、支持热备、行级锁。还有InnoDB是Mysql上唯一一个提供了外键约束的引擎。

InnoDB存储引擎中,创建的表的表结构是单独存储的并且存储在.frm文件中。数据和索引存储在一起的并且存储在表空间中。但是默认情况下mysql会将数据库的所有InnoDB表存储在一个表空间中的。

InnoDB是一个健壮的事务型存储引擎,mysql 5.5 之后。InnoDB 被认定为默认的存储引擎. InnoDB就是作为默认的存储引擎**。InnoDB还引入了行级锁定和外键约束,在以下场合下,使用InnoDB是最理想的选择:

  1. 更新密集的表。InnoDB存储引擎特别适合处理多重并发的更新请求。
  2. 事务。InnoDB存储引擎是支持事务的标准MySQL存储引擎。
  3. 自动灾难恢复。与其它存储引擎不同,InnoDB表能够自动从灾难中恢复。
  4. 外键约束。MySQL支持外键的存储引擎只有InnoDB。
  5. 支持自动增加列AUTO_INCREMENT属性。

一般来说,如果需要事务支持,并且有较高的并发读取频率,InnoDB是不错的选择。

context:annotation-config 与 context:component-scan区别

发表于 2016-09-21

<context:annotation-config> 是用于激活那些已经在spring容器里注册过的bean(无论是通过xml的方式还是通过package sanning的方式)上面的注解。

<context:component-scan>除了具有<context:annotation-config>的功能之外,<context:component-scan>还可以在指定的package下扫描以及注册javabean 。

下面通过例子来详细查看他们的区别,有三个class A,B,C,并且B,C的对象被注入到A中.

通过xml的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.xxx;
public class B {
public B() {
System.out.println("creating bean B: " + this);
}
}
package com.xxx;
public class C {
public C() {
System.out.println("creating bean C: " + this);
}
}
package com.yyy;
import com.xxx.B;
import com.xxx.C;
public class A {
private B bbb;
private C ccc;
public A() {
System.out.println("creating bean A: " + this);
}
public void setBbb(B bbb) {
System.out.println("setting A.bbb with " + bbb);
this.bbb = bbb;
}
public void setCcc(C ccc) {
System.out.println("setting A.ccc with " + ccc);
this.ccc = ccc;
}
}

在applicationContext.xml中加入下面的配置 :

1
2
3
4
5
6
<bean id="bBean"class="com.xxx.B"/>
<bean id="cBean"class="com.xxx.C"/>
<bean id="aBean"class="com.yyy.A">
<property name="bbb" ref="bBean"/>
<property name="ccc" ref="cBean"/>
</bean>

加载applicationContext.xml配置文件,将得到下面的结果:

1
2
3
4
5
creating bean B: com.xxx.B@c2ff5
creating bean C: com.xxx.C@1e8a1f6
creating bean A: com.yyy.A@1e152c5
setting A.bbb with com.xxx.B@c2ff5
setting A.ccc with com.xxx.C@1e8a1f6

通过注解的方式

使用<context:annotation-config>和 <context:component-scan>

阅读全文 »

context:annotation-config 作用

发表于 2016-09-21

在基于注解方式配置Spring的配置文件中,可能会见到<context:annotation-config/>这样一条配置,他的作用是隐式地向 Spring 容器注册

  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • RequiredAnnotationBeanPostProcessor 这 4 个BeanPostProcessor。

注册这4个BeanPostProcessor的作用,就是为了你的系统能够识别相应的注解。

例如:

  1. 如果你想使用@Autowired注解,那么就必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessorBean。传统声明方式如下
1
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>
  1. 如果想使用@ Resource 、@ PostConstruct、@ PreDestroy等注解就必须声明CommonAnnotationBeanPostProcessor
  2. 如果想使用@PersistenceContext注解,就必须声明PersistenceAnnotationBeanPostProcessor的Bean。
  3. 如果想使用 @Required的注解,就必须声明RequiredAnnotationBeanPostProcessor的Bean。同样,传统的声明方式如下:
1
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>

一般来说,这些注解我们还是比较常用,尤其是@Autowired的注解,在自动注入的时候更是经常使用,所以如果总是需要按照传统的方式一条一条配置显得有些繁琐和没有必要,于是spring给我们提供<context:annotation-config/>的简化配置方式,自动帮你完成声明。

不过,我们使用注解一般都会配置扫描包路径选项

1
<context:component-scan base-package="XX.XX"/>

该配置项其实也包含了自动注入上述processor的功能,因此当使用<context:component-scan/>后,就可以将 <context:annotation-config/>移除了。

Spring的@Autowired注入规则

发表于 2016-09-21

Spring 2.5引入了@Autowired注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。通过@Autowired的使用来消除set,get方法。

@Autowired默认是按照byType进行注入的,但是当byType方式找到了多个符合的bean,又是怎么处理的?

Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个,则报出异常。

例子:

1
2
@Autowired
private ExamUserMapper examUserMapper;  - ExamUserMapper是一个接口
  • spring先找类型为ExamUserMapper的bean
  • 如果存在且唯一,则OK;
  • 如果不唯一,在结果集里,寻找name为examUserMapper的bean。因为bean的name有唯一性,所以,到这里应该能确定是否存在满足要求的bean了

@Autowired也可以手动指定按照byName方式注入,使用@Qualifier标签,例如:

1
2
@Autowired
@Qualifier ( "baseDao" )

因为bean的name具有唯一性,理论上是byName会快一点,但spring默认使用byType的方式注入

要使用@Autowired实现要精简程序的目的,需要这样来处理:

在applicationContext.xml中加入:

1
2
<!-- 该 BeanPostProcessor 将自动对标注 @Autowired 的 Bean 进行注入 -->   
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

Spring通过一个BeanPostProcessor对@Autowired进行解析,所以要让@Autowired起作用必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor Bean。

1…192021…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces