精华内容
下载资源
问答
  • 公钥密钥加密原理

    万次阅读 多人点赞 2019-09-05 10:12:22
    6.1 公钥密钥加密原理 6.1.1 基础知识 密钥:一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算法。 对称加密算法:加密和解密都是使用的同一个密钥。因此对称加密算法要保证安全性的话,密钥要...

    转载自:https://www.jianshu.com/p/0bf1c3c047e5

    第6章 网络编程与网络框架

    6.1 公钥密钥加密原理

    6.1.1 基础知识

    • 密钥:一般就是一个字符串或数字,在加密或者解密时传递给加密/解密算法。
    • 对称加密算法:加密和解密都是使用的同一个密钥。因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道,不能对外公开。
    • 非对称加密算法:加密使用的密钥和解密使用的密钥是不同的。 公钥密码体制就是一种非对称加密算法。

    (1) 公钥密码体制

    分为三个部分:公钥、私钥、加密/解密算法

    加密解密过程如下:
    加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。
    解密:通过解密算法和私钥对密文进行解密,得到明文。

    注意:由公钥加密的内容,只能由私钥进行解密。

    公钥密码体制的公钥和算法都是公开的,私钥是保密的。在实际的使用中,有需要的人会生成一对公钥和私钥,把公钥发布出去给别人使用,自己保留私钥。

    (2) RSA加密算法

    一种公钥密码体制,公钥公开,私钥保密,它的加密解密算法是公开的。 RSA的这一对公钥、私钥都可以用来加密和解密,并且一方加密的内容可以由并且只能由对方进行解密。

    (3) 签名

    就是在信息的后面再加上一段内容,可以证明信息没有被修改过。

    一般是对信息做一个hash计算得到一个hash值(该过程不可逆),在把信息发送出去时,把这个hash值加密后做为一个签名和信息一起发出去。 接收方在收到信息后,会重新计算信息的hash值,并和信息所附带的hash值(解密后)进行对比,如果一致,就说明信息的内容没有被修改过,因为这里hash计算可以保证不同的内容一定会得到不同的hash值,所以只要内容一被修改,根据信息内容计算的hash值就会变化。

    当然,不怀好意的人也可以修改信息内容的同时也修改hash值,从而让它们可以相匹配,为了防止这种情况,hash值一般都会加密后(也就是签名)再和信息一起发送,以保证这个hash值不被修改。

    6.1.2 基于RSA算法的加密通信的例子

    “客户”->“服务器”:你好
    “服务器”->“客户”:你好,我是服务器
    “客户”->“服务器”:向我证明你就是服务器
    “服务器”->“客户”:你好,我是服务器{你好,我是服务器}[私钥|RSA]
    “客户”->“服务器”:{我们后面的通信过程,用对称加密来进行,这里是对称加密算法和密钥}[公钥|RSA]
    “服务器”->“客户”:{OK,收到!}[密钥|对称加密算法]
    “客户”->“服务器”:{我的帐号是aaa,密码是123,把我的余额的信息发给我看看}[密钥|对称加密算法]
    “服务器”->“客户”:{你的余额是100元}[密钥|对称加密算法]

    总结一下,RSA加密算法在这个通信过程中所起到的作用主要有两个:
    1. 因为私钥只有“服务器”拥有,因此“客户”可以通过判断对方是否有私钥来判断对方是否是“服务器”。
    2. 客户端通过RSA的掩护,安全的和服务器商量好一个对称加密算法和密钥来保证后面通信过程内容的安全。

    但是这里还留有一个问题,“服务器”要对外发布公钥,那“服务器”如何把公钥发送给“客户”呢?
    我们可能会想到以下的两个方法:
    a) 把公钥放到互联网的某个地方的一个下载地址,事先给“客户”去下载。
    b) 每次和“客户”开始通信时,“服务器”把公钥发给“客户”。

    但是这个两个方法都有一定的问题,
    对于a)方法,“客户”无法确定这个下载地址是不是“服务器”发布的,你凭什么就相信这个
    地址下载的东西就是“服务器”发布的而不是别人伪造的呢,万一下载到一个假的怎么办?另外要所有的“客户”都在通信前事先去下载公钥也很不现实。
    对于b)方法,也有问题,因为任何人都可以自己生成一对公钥和私钥,他只要向“客户”发送他
    自己的私钥就可以冒充“服务器”了。示意如下:

    “客户”->“黑客”:你好 //黑客截获“客户”发给“服务器”的消息
    “黑客”->“客户”:你好,我是服务器,这个是我的公钥 //黑客自己生成一对公钥和私钥,把
    公钥发给“客户”,自己保留私钥
    “客户”->“黑客”:向我证明你就是服务器
    “黑客”->“客户”:你好,我是服务器 {你好,我是服务器}[黑客自己的私钥|RSA] //客户收到

    “黑客”用私钥加密的信息后,是可以用“黑客”发给自己的公钥解密的,从而会误认为“黑客”是“服务器”因此“黑客”只需要自己生成一对公钥和私钥,然后把公钥发送给“客户”,自己保留私钥,这样由于“客户”可以用黑客的公钥解密黑客的私钥加密的内容,“客户”就会相信“黑客”是“服务器”,从而导致了安全问题。这里问题的根源就在于,大家都可以生成公钥、私钥对,无法确认公钥对到底是谁的。 如果能够确定公钥到底是谁的,就不会有这个问题了。例如,如果收到“黑客”冒充“服务器”发过来的公钥,经过某种检查,如果能够发现这个公钥不是“服务器”的就好了。

    6.1.3 数字证书

    为了解决上述问题,数字证书出现了,它可以解决我们上面的问题。先大概看下什么是数字证书,一个证书包含下面的具体内容:

    • 证书的发布机构
    • 证书的有效期
    • 证书所有者(Subject)
    • 公钥
    • 指纹和指纹算法
    • 签名算法

    指纹和指纹算法
    这个是用来保证证书的完整性的,也就是说确保证书没有被修改过。 其原理就是在发布证书时,发布者根据指纹算法(一个hash算法)计算整个证书的hash值(指纹)并和证书放在一起,使用者在打开证书时,自己也根据指纹算法计算一下证书的hash值(指纹),如果和刚开始的值对得上,就说明证书没有被修改过,因为证书的内容被修改后,根据证书的内容计算的出的hash值(指纹)是会变化的。
    注意,这个指纹会用"SecureTrust CA"这个证书机构的私钥用签名算法加密后和证书放在一起

    签名算法
    就是指的这个数字证书的数字签名所使用的加密算法,这样就可以使用证书发布机构的证书里面的公钥,根据这个算法对指纹进行解密。指纹的加密结果就是数字签名

    数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方的身份。也就是说,我们拿到一个数字证书,我们可以判断出这个数字证书到底是谁的。至于是如何判断的,后面会在详细讨论数字证书时详细解释。现在把前面的通信过程使用数字证书修改为如下:

    “客户”->“服务器”:你好
    “服务器”->“客户”:你好,我是服务器,这里是我的数字证书 //这里用证书代替了公钥
    “客户”->“服务器”:向我证明你就是服务器
    “服务器”->“客户”:你好,我是服务器 {你好,我是服务器}[私钥|RSA]

    在每次发送信息时,先对信息的内容进行一个hash计算得出一个hash值,将信息的内容和这个hash值一起加密后发送。接收方在收到后进行解密得到明文的内容和hash值,然后接收方再自己对收到信息内容做一次hash计算,与收到的hash值进行对比看是否匹配,如果匹配就说明信息在传输过程中没有被修改过。如果不匹配说明中途有人故意对加密数据进行了修改,立刻中断通话过程后做其它处理。

    如何向证书的发布机构去申请证书

    举个例子,假设我们公司"ABC Company"花了1000块钱,向一个证书发布机构"SecureTrust CA"为我们自己的公司"ABC Company"申请了一张证书,注意,这个证书发布机构"SecureTrust CA"是一个大家公认并被一些权威机构接受的证书发布机构,我们的操作系统里面已经安装了"SecureTrust CA"的证书。"SecureTrust CA"在给我们发布证书时,把Issuer,Public key,Subject,Valid from,Valid to等信息以明文的形式写到证书里面,然后用一个指纹算法计算出这些数字证书内容的一个指纹,并把指纹和指纹算法用自己的私钥进行加密,然后和证书的内容一起发布,同时"SecureTrust CA"还会给一个我们公司"ABC Company"的私钥给到我们。
    我们"ABC Company"申请到这个证书后,我们把证书投入使用,我们在通信过程开始时会把证书发给对方。

    对方如何检查这个证书的确是合法的并且是我们"ABC Company"公司的证书呢?首先应用程序(对方通信用的程序,例如IE、OUTLook等)读取证书中的Issuer(发布机构)为"SecureTrust CA" ,然后会在操作系统中受信任的发布机构的证书中去找"SecureTrust CA"的证书,如果找不到,那说明证书的发布机构是个水货发布机构,证书可能有问题,程序会给出一个错误信息。 如果在系统中找到了"SecureTrust CA"的证书,那么应用程序就会从证书中取出"SecureTrust CA"的公钥,然后对我们"ABC Company"公司的证书里面的指纹和指纹算法用这个公钥进行解密,然后使用这个指纹算法计算"ABC Company"证书的指纹,将这个计算的指纹与放在证书中的指纹对比,如果一致,说明"ABC Company"的证书肯定没有被修改过并且证书是"SecureTrust CA" 发布的,证书中的公钥肯定是"ABC Company"的。对方然后就可以放心的使用这个公钥和我们"ABC Company"进行通信了。

    6.2 Http协议原理

    6.2.1 基础知识

    1. TCP/IP协议族

    • IP协议:网络层协议,保证了计算机之间可以发送和接收数据。
    • TCP协议:传输层协议,一种端到端的协议,建立一个虚拟链路用于发送和接收数据,基于重发机制,提供可靠的通信连接。为了方便通信,将报文分割成多个报文段发送。
    • UDP协议:传输层协议,一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

    通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。

    2. TCP3次握手,4次挥手过程

    (1) 建立连接协议(三次握手)

    a)客户端发送一个带SYN标志的TCP报文到服务器。(听得到吗?)
    b)服务端回应客户端的报文同时带ACK(acknowledgement,确认)标志和SYN(synchronize)标志。它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。(听得到,你能听到我吗?)
    c)客户必须再次回应服务端一个ACK报文。(听到了,我们可以说话了)

    为什么需要“三次握手”?
    在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”。 主要目的防止server端一直等待,浪费资源。

    (2) 连接终止协议(四次挥手)

    由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
    a) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
    b) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
    c) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
    d) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

    为什么需要“四次挥手”?
    那可能有人会有疑问,在tcp连接握手时为何ACK是和SYN一起发送,这里ACK却没有和FIN一起发送呢。原因是因为tcp是全双工模式,接收到FIN时意味将没有数据再发来,但是还是可以继续发送数据。

    3. 请求报文

    (1) 请求行

    由3部分组成,分别为:请求方法、URL以及协议版本,之间由空格分隔

    请求方法:GET、HEAD、PUT、POST等方法,但并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的
    协议版本:常用HTTP/1.1

    (2) 请求头部

    请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之间使用冒号分隔

    • Host 接受请求的服务器地址,可以是IP:端口号,也可以是域名
    • User-Agent 发送请求的应用程序名称
    • Accept-Charset 通知服务端可以发送的编码格式
    • Accept-Encoding 通知服务端可以发送的数据压缩格式
    • Accept-Language 通知服务端可以发送的语言
    • Range 正文的字节请求范围,为断点续传和并行下载提供可能,返回状态码是206
    • Authorization 用于设置身份认证信息
    • Cookie 已有的Cookie

    请求头部的最后会有一个空行,表示请求头部结束,接下来为请求正文,这一行非常重要,必不可少

    (3) 请求正文

    可选部分,比如GET请求就没有请求正文

    4. 响应报文

    由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔

    状态码:为3位数字,2XX表示成功,3XX表示资源重定向,4XX表示客户端请求出错,5XX表示服务端出错
    206状态码表示的是:客户端通过发送范围请求头Range抓取到了资源的部分数据,得服务端提供支持

    (1) 响应头部

    • Server 服务器应用程序软件的名称和版本
    • Content-Type 响应正文的类型。如:text/plain、application/json
    • Content-Length 响应正文长度
    • Content-Charset 响应正文使用的编码
    • Content-Language 响应正文使用的语言
    • Content-Range 正文的字节位置范围
    • Accept-Ranges bytes:表明服务器支持Range请求,单位是字节;none:不支持
    • Set-Cookie 设置Cookie

    正文的内容可以用gzip等进行压缩,以提升传输速率

    5. 通用首部

    (1) Cache-Control

    用于操作浏览器缓存的工作机制。取值如下:

    • max-age:表示缓存的新鲜时间,在此时间内可以直接使用缓存。单位秒。
    • no-cache:不做缓存。
    • max-stale:可以接受过期多少秒的缓存。

    (2) Connection

    用于管理持久连接。目前大部分浏览器都是用http1.1协议,也就是说默认都会发起Keep-Alive的连接请求。所以是否能完成一个完整的Keep-Alive连接就看服务器设置情况。取值如下:

    • keep-alive:使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,避免了建立或者重新建立连接。
    • close:每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接。

    (3) Transfer-Encoding

    在Http/1.1中,仅对分块传输编码有效。Transfer-Encoding: chunked 表示输出的内容长度不能确定,普通的静态页面、图片之类的基本上都用不到这个,但动态页面就有可能会用到。一般使用Content-Length就够了。

    Http/1.1 200 OK
    ....
    Transfer-Encoding:chunked
    Connection:keep-alive
    
    cf0  //16进制,值为3312
    
    ...3312字节分块数据...
    
    392  //16进制,值为914
    
    ...914字节分块数据...
    
    0
    

    (4) Content-Encoding

    请求体/响应体的编码格式,如gzip

    6. HTTP Authentication

    两种常见的Authentication机制:HTTP Basic和Digest。(现在用的并不多,了解一下)

    (1) Http Basic

    最简单的Authentication协议。直接方式告诉服务器你的用户名(username)和密码(password)。

    request头部:

    GET /secret HTTP/1.1
    Authorization: Basic QWxpY2U6MTIzNDU2//由“Alice:123456”进行Base64编码以后得到的结果
    ...
    

    response头部:

    HTTP/1.1 200 OK
    ...
    

    因为我们输入的是正确的用户名密码,所以服务器会返回200,表示验证成功。如果我们用错误的用户的密码来发送请求,则会得到类似如下含有401错误的response头部:

    HTTP/1.1 401 Bad credentials
    WWW-Authenticate: Basic realm="Spring Security Application"
    ...
    

    (2) Http Digest

    当Alice初次访问服务器时,并不携带密码。此时服务器会告知Alice一个随机生成的字符串(nonce)。然后Alice再将这个字符串与她的密码123456结合在一起进行MD5编码,将编码以后的结果发送给服务器作为验证信息。

    因为nonce是“每次”(并不一定是每次)随机生成的,所以Alice在不同的时间访问服务器,其编码使用的nonce值应该是不同的,如果携带的是相同的nonce编码后的结果,服务器就认为其不合法,将拒绝其访问。

    curl和服务器通信过程:

    curl -------- request1:GET ------->> Server
    curl <<------ response1:nonce ------- Server
    curl ---- request2:Digest Auth ---->> Server
    curl <<------- response2:OK --------  Server
    

    request1头部:

    GET /secret HTTP/1.1
    ...
    

    请求1中没有包含任何用户名和密码信息

    response1头部:

    HTTP/1.1 401 Full authentication is required to access this resource
    WWW-Authenticate: Digest realm="Contacts Realm via Digest Authentication",
    qop="auth",nonce="MTQwMTk3OTkwMDkxMzo3MjdjNDM2NTYzMTU2NTA2NWEzOWU2NzBlNzhmMjkwOA=="
    ...
    

    当服务器接收到request1以后,认为request1没有任何的Authentication信息,所以返回401,并且告诉curl nonce的值是MTQwMTk3OTkwMDkxMzo3MjdjNDM2NTYzMTU2NTA2NWEzOWU2NzBlNzhmMjkwOA

    request2头部:

    GET /secret HTTP/1.1
    Authorization: Digest username="Alice", realm="Contacts Realm via Digest  
    Authentication",nonce="MTQwMTk3OTkwMDkxMzo3MjdjNDM2NTYzMTU2NTA2NWEzOWU2NzBlNzhmMjkwOA==", uri="/secret",
    cnonce="MTQwMTk3", nc=00000001, qop="auth",response="fd5798940c32e51c128ecf88472151af"
     ...
    

    curl接收到服务器的nonce值以后,就可以把如密码等信息和nonce值放在一起然后进行MD5编码,得到一个response值,如前面红色标出所示,这样服务器就可以通过这个值验证Alice的密码是否正确。

    response2头部:

    HTTP/1.1 200 OK
    ...
    

    当我们完成Authentication以后,如果我们再次使用刚才的nonce值,将会收到错误信息。Digest Authentication比Basic安全,但是并不是真正的什么都不怕了,Digest Authentication这种容易方式容易收到Man in the Middle式攻击。

    7. 请求体的3种形式

    据应用场景的不同,HTTP请求的请求体有三种不同的形式。
    第一种:
    移动开发者常见的,请求体是任意类型,服务器不会解析请求体,请求体的处理需要自己解析,如 POST JSON时候就是这类。
    第二种:
    这里的格式要求就是URL中Query String的格式要求:多个键值对之间用&连接,键与值之前用=连接,且只能用ASCII字符,非ASCII字符需使用UrlEncode编码。
    第三种:
    请求体被分成为多个部分,文件上传时会被使用,这种格式最先应该是被用于邮件传输中,每个字段/文件都被boundary(Content-Type中指定)分成单独的段,每段以-- 加 boundary开头,然后是该段的描述头,描述头之后空一行接内容,请求结束的标制为boundary后面加--。(见下面详细说明)

    8. http协议中的多部分对象(multipart/form-data)

    默认是application/x-www-form-urlencoded,但是在传输大型文件的时候效率比较低下。所以需要multipart/form-data。
    报文的主体内可以包含多部分对象,通常用来发送图片、文件或表单等。

    Connection: keep-alive
    Content-Length: 123
    X-Requested-With: ShockwaveFlash/16.0.0.296
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36
    Content-Type: multipart/form-data; boundary=Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.8
    Range: bytes=0-1024
    Cookie: bdshare_firstime=1409052493497
    
    --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1
    Content-Disposition: form-data; name="position"
    
    1425264476444
    --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1
    Content-Disposition: form-data; name="pics"; filename="file1.txt"
    Content-Type: text/plain
    
    ...(file1.txt的数据)...
    ue_con_1425264252856
    --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1
    Content-Disposition: form-data; name="cm"
    
    100672
    --Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1--
    

    a)在请求头中Content-Type: multipart/form-data; boundary=Ij5ei4KM7KM7ae0KM7cH2ae0Ij5Ef1是必须的,boundary字符串可以随意指定
    b)上面有3个部分,分别用--boundary进行分隔。Content-Disposition: form-data; name="参数的名称" + "\r\n" + "\r\n" + 参数值 
    c)--boundary-- 作为结束

    6.2.2Https

    (1) Http的缺点

    • 通信使用明文,内容可能会被窃听 —— 加密通信线路
    • 不验证通信方,可能遭遇伪装 —— 证书
    • 无法验证报文的完整性,可能已被篡改 —— 数字签名

    Http+加密+认证+完整性保护=Https

    Https就是身披SSL(Secure Socket Layer,安全套接层)协议这层外壳的Http。当使用了SSL之后,Http先和SSL通信,SSL再和TCP通信。

    SSL(secure sockets layer):安全套接层,它是在上世纪90年代中期,由网景公司设计的,为解决 HTTP 协议传输内容会被偷窥(嗅探)和篡改等安全问题而设计的,到了1999年,SSL成为互联网上的标准,名称改为TLS(transport layer security):安全传输层协议,两者可视为同一种东西的不同阶段。

    (2) Https的工作原理

    HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。握手过程的具体描述如下:

    • 浏览器将自己支持的一套加密规则发送给网站。
    • 网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
    • 浏览器获得网站证书之后浏览器要做以下工作:
      a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
      b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
      c) 使用约定好的HASH算法计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
    • 网站接收浏览器发来的数据之后要做以下的操作:
      a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
      b) 使用密码加密一段握手消息,发送给浏览器。
    • 浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。

    这里浏览器与网站互相发送加密的握手消息并验证,目的是为了保证双方都获得了一致的密码,并且可以正常的加密解密数据,为后续真正数据的传输做一次测试。另外,HTTPS一般使用的加密与HASH算法如下:

    • 非对称加密算法:RSA,DSA/DSS
    • 对称加密算法:AES,RC4,3DES
    • HASH算法:MD5,SHA1,SHA256

    HTTPS对应的通信时序图如下:

    (3) 证书分类

    SSL 证书大致分三类:

    • 认可的证书颁发机构(如: VeriSign), 或这些机构的下属机构颁发的证书.
    • 没有得到认可的证书颁发机构颁发的证书.
    • 自签名证书, 自己通过JDK自带工具keytool去生成一个证书,分为临时性的(在开发阶段使用)或在发布的产品中永久性使用的两种.

    只有第一种, 也就是那些被安卓系统认可的机构颁发的证书, 在使用过程中不会出现安全提示。对于向权威机构(简称CA,Certificate Authority)申请过证书的网络地址,用OkHttp或者HttpsURLConnection都可以直接访问 ,不需要做额外的事情 。但是申请需要$$ (每年要交 100 到 500 美元不等的费用)。

    CA机构颁发的证书有3种类型:
    域名型SSL证书(DV SSL):信任等级普通,只需验证网站的真实性便可颁发证书保护网站;
    企业型SSL证书(OV SSL):信任等级强,须要验证企业的身份,审核严格,安全性更高;
    增强型SSL证书(EV SSL):信任等级最高,一般用于银行证券等金融机构,审核严格,安全性最高,同时可以激活绿色网址栏。

    (4) HTTPS协议和HTTP协议的区别:

    • https协议需要到ca申请证书,一般免费证书很少,需要交费。
    • http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
    • http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
    • http的连接很简单,是无状态的 。
    • HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议, 要比http协议安全。

    6.3 Android SDK支持

    6.3.1 InetAddress

    (1) 简介

    • 代表IP地址,还有两个子类,Inet4AddressInet6Address
    • 没有构造方法

    (2) 方法

    • InetAddress.getByAddress(byte[] addr) 根据IP地址获取InetAddress对象,如:new byte[]{127,0,0,1}
    • InetAddress.getByName(String host)根据主机名获取InetAddress对象 www.baidu.com 没有http://
    • InetAddress.getLocalHost()返回本机
    • getHostAddress() String 返回IP地址
    • getHostName() String 返回主机名
    • isReachable(int timeout) boolean 测试是否可以达到该地址,毫秒数

    2. URLDecoder和URLEncoder

    (1) 简介

    • URLDecoder和URLEncoder用于完成普通字符串和application/x-www-form-urlencoded MIME字符串之间的相互转换
    • 若每个中文占2个字节,每个字节转换成2个十六进制的数字,所以每个中文字符转换成“%XX%XX”的形式

    (2) 方法

    • URLEncoder.encode(String s, String enc) String
    • URLDecoder.decode(String s, String enc) String

    6.3.2 Socket通信

    1. Socket通信简介

    Socket又称套接字,是程序内部提供的与外界通信的端口,即端口通信。通过建立socket连接,可为通信双方的数据传输传提供通道。主要特点有数据丢失率低,使用简单且易于移植。

    (1) Socket的分类

    在TCP/IP协议族当中主要的Socket类型为流套接字(streamsocket)和数据报套接字(datagramsocket)。流套接字将TCP作为其端对端协议,提供了一个可信赖的字节流服务。数据报套接字使用UDP协议,提供数据打包发送服务。

    2. Socket 基本通信模型

    (1) Socket通信模型

    Socket基本通信模型

    (2) TCP通信模型

    TCP通信模型

    (3) UDP通信模型

    UDP通信模型

    4. Demo

    首先添加权限:

    <!--允许应用程序改变网络状态-->    
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>    
    <!--允许应用程序改变WIFI连接状态-->    
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>      
    <!--允许应用程序访问有关的网络信息-->    
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>     
    <!--允许应用程序访问WIFI网卡的网络信息-->    
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>    
    <!--允许应用程序完全使用网络-->    
    <uses-permission android:name="android.permission.INTERNET"/>
    

    (1) 使用TCP协议通信

    客户端实现:

        protected void connectServerWithTCPSocket() {
            Socket socket;
            try {// 创建一个Socket对象,并指定服务端的IP及端口号
                socket = new Socket("192.168.1.32", 1989);
                // 创建一个InputStream用户读取要发送的文件。
                InputStream inputStream = new FileInputStream("e://a.txt");
                // 获取Socket的OutputStream对象用于发送数据。
                OutputStream outputStream = socket.getOutputStream();
                // 创建一个byte类型的buffer字节数组,用于存放读取的本地文件
                byte buffer[] = new byte[4 * 1024];
                int temp = 0;
                // 循环读取文件
                while ((temp = inputStream.read(buffer)) != -1) {
                    // 把数据写入到OuputStream对象中
                    outputStream.write(buffer, 0, temp);
                }
                // 发送读取的数据到服务端
                outputStream.flush();
                /** 或创建一个报文,使用BufferedWriter写入**/
                //String socketData = "[2143213;21343fjks;213]";
                //BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                //socket.getOutputStream()));
                //writer.write(socketData.replace("\n", " ") + "\n");
                //writer.flush();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    服务端实现:

        public void serverReceviedByTcp() {
            // 声明一个ServerSocket对象  
            ServerSocket serverSocket = null;
            try {
                // 创建一个ServerSocket对象,并让这个Socket在1989端口监听  
                serverSocket = new ServerSocket(1989);
                // 调用ServerSocket的accept()方法,接受客户端所发送的请求,  
                // 如果客户端没有发送数据,那么该线程就阻塞,等到收到数据,继续执行。
                Socket socket = serverSocket.accept();
                // 从Socket当中得到InputStream对象,读取客户端发送的数据  
                InputStream inputStream = socket.getInputStream();
                byte buffer[] = new byte[1024 * 4];
                int temp = 0;
                // 从InputStream当中读取客户端所发送的数据  
                while ((temp = inputStream.read(buffer)) != -1) {
                    System.out.println(new String(buffer, 0, temp));
                }
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    5. 方法

    (1) ServerSocket

    监听来自于客户端的Socket连接,如果没有连接,它将一直处于等待状态

    • ServerSocket(int port, int backlog, InetAddress bindAddr) 在机器存在多IP的情况下,允许通过bindAddr来指定绑定到哪个IP;backlog是队列中能接受的最大socket客户端连接数(accept()之后将被取出)
    • ServerSocket() 创建非绑定服务器套接字
    • ServerSocket(int port) 创建绑定到特定端口的服务器套接字,等价于ServerSocket(port, 50, null)
    • accept() Socket 如果接收到一个客户端Socket的连接请求,返回一个与客户端Socket相对应的Socket
    • close()

    (2) Socket

    • Socket() 创建未连接套接字
    • Socket(String host, int port)
    • Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号,默认使用本地主机默认的IP和系统动态分配的端口
    • Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 创建一个套接字并将其连接到指定远程地址上的指定远程端口,多IP时
    • getOutputStream() OutputStream 返回此套接字的输出流
    • getInputStream() inputStream 返回此套接字的输入流
    • connect(SocketAddress endpoint, int timeout) 将此套接字连接到服务器,并指定一个超时值
    • close()

    6.3.3HttpURLConnection

    1. URL

    1)对象代表统一资源定位器,是指向互联网“资源”的指针
    2)通常由协议名、主机、端口、资源路径组成
    3)URL包含一个可打开到达该资源的输入流,可以将URL理解成为URI的特例

    • URL(String spec)
    • openConnection() URLConnection
    • getProtocol() String
    • getHost() String
    • getPort() int
    • getPath() String 获取路径部分,/search.html
    • getFile() String 获取资源名称,/search.html?keyword='你好'
    • getQuery() String 获取查询字符串,keyword='你好'
    • openStream() InputStream

    2. URLConnection

    抽象类,表示应用程序和URL之间的通信连接,可以向URL发送请求,读取URL指向的资源

    • setDoInput(boolean doinput) 发送POST请求,必须设置,设置为true
    • setDoOutput(boolean dooutput) 发送POST请求,必须设置,设置为true
    • setUseCaches(boolean usecaches)是否使用缓存
    • setRequestProperty(String key, String value) 设置普通的请求属性
    • setConnectTimeout(int timeout)设置连接超时的时间
    • setReadTimeout(int timeoutMillis) 读取输入流的超时时间
    • connect() 抽象方法,建立实际的连接
    • getHeaderField(String key) String
    • getHeaderFields() Map<String,List<String>> 获取所有的响应头;getHeaderField(String name)获取指定的响应头
    • getOutputStream() OutputStream
    • getInputStream() InputStream
    • getContentLength() int

    3. HttpURLConnection

    (1) 简介

    抽象类,是URLConnection的子类,增加了操作Http资源的便捷方法

    • 对象不能直接构造,需要通过URL类中的openConnection()方法来获得。
    • connect()函数,实际上只是建立了一个与服务器的TCP连接,并没有实际发送HTTP请求。HTTP请求实际上直到我们获取服务器响应数据(如调用getInputStream()、getResponseCode()等方法)时才正式发送出去。
    • 对HttpURLConnection对象的配置都需要在connect()方法执行之前完成。
    • HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。
    • HTTP正文的内容是通过OutputStream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成HTTP正文。
    • 调用getInputStream()方法时,返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息。
    • 我们可以使用connect()方法手动的发送一个HTTP请求,但是如果要获取HTTP响应的时候,请求就会自动的发起,比如我们使用getInputStream()方法的时候,所以完全没有必要调用connect()方法。

    (2) 方法

    • setRequestMethod(String method)
    • setInstanceFollowRedirects(boolean followRedirects)
    • getResponseCode() int 获取服务器的响应码
    • getResponseMessage() String 获取响应消息
    • getRequestMethod() String 获取发送请求的方法,GET或POST
    • disconnect() 抽象方法

    (3) 实现多线程下载

    步骤:
    ①:创建URL对象
    ②:获取URL对象指向资源的大小(getContentLength()方法)
    ③:在本地创建一个与网路资源相同大小的空文件
    ④:计算每条线程应该下载网络资源的哪个部分(从哪个字节开始,到哪个字节结束)
    ⑤:依次创建,启动多条线程来下载网络资源的指定部分

    (4) 实例

    使用GET方式访问HTTP

    public static void main(String[] args) {
            try {
                // 1. 得到访问地址的URL
                URL url = new URL(
                        "http://localhost:8080/Servlet/do_login.do?username=test&password=123456");
                // 2. 得到网络访问对象java.net.HttpURLConnection
                HttpURLConnection connection = (HttpURLConnection) url
                        .openConnection();
                /* 3. 设置请求参数(过期时间,输入、输出流、访问方式),以流的形式进行连接 */
                // 设置是否向HttpURLConnection输出
                connection.setDoOutput(false);
                // 设置是否从httpUrlConnection读入
                connection.setDoInput(true);
                // 设置请求方式
                connection.setRequestMethod("GET");
                // 设置是否使用缓存
                connection.setUseCaches(true);
                // 设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
                connection.setInstanceFollowRedirects(true);
                // 设置超时时间
                connection.setConnectTimeout(3000);
                // 连接
                connection.connect();
                // 4. 得到响应状态码的返回值 responseCode
                int code = connection.getResponseCode();
                // 5. 如果返回值正常,数据在网络中是以流的形式得到服务端返回的数据
                String msg = "";
                if (code == 200) { // 正常响应
                    // 从流中读取响应信息
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(connection.getInputStream()));
                    String line = null;
    
                    while ((line = reader.readLine()) != null) { // 循环从流中读取
                        msg += line + "\n";
                    }
                    reader.close(); // 关闭流
                }
                // 6. 断开连接,释放资源
                connection.disconnect();
                // 显示响应结果
                System.out.println(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    使用POST方式访问HTTP

    public static void main(String[] args) {
            try {
                // 1. 获取访问地址URL
                URL url = new URL("http://localhost:8080/Servlet/do_login.do");
                // 2. 创建HttpURLConnection对象
                HttpURLConnection connection = (HttpURLConnection) url
                        .openConnection();
                /* 3. 设置请求参数等 */
                // 请求方式
                connection.setRequestMethod("POST");
                // 超时时间
                connection.setConnectTimeout(3000);
                // 设置是否输出
                connection.setDoOutput(true);
                // 设置是否读入
                connection.setDoInput(true);
                // 设置是否使用缓存
                connection.setUseCaches(false);
                // 设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
                connection.setInstanceFollowRedirects(true);
                // 设置使用标准编码格式编码参数的名-值对
                connection.setRequestProperty("Content-Type",
                        "application/x-www-form-urlencoded");
                // 连接
                connection.connect();
                /* 4. 处理输入输出 */
                // 写入参数到请求中
                String params = "username=test&password=123456";
                OutputStream out = connection.getOutputStream();
                out.write(params.getBytes());
                out.flush();
                out.close();
                // 从连接中读取响应信息
                String msg = "";
                int code = connection.getResponseCode();
                if (code == 200) {
                    BufferedReader reader = new BufferedReader(
                            new InputStreamReader(connection.getInputStream()));
                    String line;
    
                    while ((line = reader.readLine()) != null) {
                        msg += line + "\n";
                    }
                    reader.close();
                }
                // 5. 断开连接
                connection.disconnect();
    
                // 处理结果
                System.out.println(msg);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    4. HttpClient

    (Android6.0之后的SDK中移除了对于HttpClient的支持,仅作了解) 
    在一般情况下,如果只是需要向Web站点的某个简单页面提交请求并获取服务器响应,HttpURLConnection完全可以胜任。但在绝大部分情况下,Web站点的网页可能没这么简单,这些页面并不是通过一个简单的URL就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况下,就需要涉及Session、Cookie的处理了,如果打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,只是处理起来难度就大了。

    为了更好地处理向Web站点请求,包括处理Session、Cookie等细节问题,Apache开源组织提供了一个HttpClient项目。HttpClient就是一个增强版的HttpURLConnection,HttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection没有提供的有些功能,HttpClient也提供了,但它只是关注于如何发送请求、接收响应,以及管理HTTP连接。

    (1) HttpClient的使用

    步骤:
    ①:创建HttpClient对象
    ②:需要GET请求,创建HttpCet对象;需要POST请求,创建HttpPost对象
    ③:需要发送请求参数,调用HttpGet、HttpPost共同的setParams(HttpParams params),HttpPost对象还可以使用setEntity(HttpEntity entity)方法来设置请求参数。
    ④:调用HttpClient对象的execute(HttpUriRequest request) HttpResponse发送请求,执行该方法返回一个HttpResponse。
    ⑤:调用HttpResponse的getAllHeaders()getHeader(String name)获取响应头;调用HttpResponse的getEntity()获取HttpEntity对象(包装了服务器的响应内容)

    (2) 使用GET方式访问HTTP

    public static void main(String[] args) {
            // 1. 创建HttpClient对象
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            // 2. 创建HttpGet对象
            HttpGet httpGet = new HttpGet(
                    "http://localhost:8080/Servlet/do_login.do?username=test&password=123456");
            CloseableHttpResponse response = null;
            try {
                // 3. 执行GET请求
                response = httpClient.execute(httpGet);
                System.out.println(response.getStatusLine());
                // 4. 获取响应实体
                HttpEntity entity = response.getEntity();
                // 5. 处理响应实体
                if (entity != null) {
                    System.out.println("长度:" + entity.getContentLength());
                    System.out.println("内容:" + EntityUtils.toString(entity));
                }
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 6. 释放资源
                try {
                    response.close();
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    

    (3) 使用POST方式访问HTTP

    public static void main(String[] args) {
            // 1. 创建HttpClient对象
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            // 2. 创建HttpPost对象
            HttpPost post = new HttpPost(
                    "http://localhost:8080/Servlet/do_login.do");
            // 3. 设置POST请求传递参数
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("username", "test"));
            params.add(new BasicNameValuePair("password", "12356"));
            try {
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params);
                post.setEntity(entity);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            // 4. 执行请求并处理响应
            try {
                CloseableHttpResponse response = httpClient.execute(post);
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    System.out.println("响应内容:");
                    System.out.println(EntityUtils.toString(entity));
                }
                response.close();
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 释放资源
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    

    6.4 OkHttp

    6.4.1 简介

    HttpClient是Apache基金会的一个开源网络库,功能十分强大,API数量众多,但正是由于庞大的API数量使得我们很难在不破坏兼容性的情况下对它进行升级和扩展,所以Android团队在提升和优化HttpClient方面的工作态度并不积极。官方在Android 2.3以后就不建议用了,并且在Android 5.0以后废弃了HttpClient,在Android 6.0更是删除了HttpClient。

    HttpURLConnection是一种多用途、轻量极的HTTP客户端,提供的API比较简单,可以容易地去使用和扩展。不过在Android 2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能。因此一般推荐是在2.2之前使用HttpClient,因为其bug较少。在2.2之后推荐使用HttpURLConnection,因为API简单、体积小、有压缩和缓存机制,并且Android团队后续会继续优化HttpURLConnection。

    自从Android4.4开始,google已经开始将源码中的HttpURLConnection替换为OkHttp,而市面上流行的Retrofit同样是使用OkHttp进行再次封装而来的。

    OkHttp是一个快速、高效的网络请求库,它的设计和实现的首要目标便是高效,有如下特性:

    • 支持HTTP/2, HTTP/2通过使用多路复用技术在一个单独的TCP连接上支持并发, 通过在一个连接上一次性发送多个请求来发送或接收数据;
    • 如果HTTP/2不可用, 连接池复用技术也可以极大减少延时;
    • 支持Gzip压缩响应体,降低传输内容的大小;
    • 支持Http缓存,避免重复请求;
    • 如果您的服务器配置了多个IP地址, 当第一个IP连接失败的时候, OkHttp会自动尝试下一个IP;
    • 使用Okio来简化数据的访问与存储,提高性能;
    • OkHttp还处理了代理服务器问题和SSL握手失败问题;

    6.4.2 OkHttp类与Http请求响应的映射

    1. Http请求

    http请求包含:请求方法, 请求地址, 请求协议, 请求头, 请求体这五部分。这些都在okhttp3.Request的类中有体现, 这个类正是代表http请求的类。

    public final class Request {
      final HttpUrl url;//请求地址
      final String method;//请求方法
      final Headers headers;//请求头
      final RequestBody body;//请求体
      final Object tag;
      ...
    }
    

    2. Http响应

    Http响应由访问协议, 响应码, 描述信息, 响应头, 响应体来组成。

    public final class Response implements Closeable {
      final Request request;//持有的请求
      final Protocol protocol;//访问协议
      final int code;//响应码
      final String message;//描述信息
      final Handshake handshake;//SSL/TLS握手协议验证时的信息,
      final Headers headers;//响应头
      final ResponseBody body;//响应体
      ...
    }
    

    6.4.3 相关方法

    1. OkHttpClient

    • OkHttpClient()
    • OkHttpClient(OkHttpClient.Builder builder)
    • newCall(Request request) Call

    OkHttpClient.Builder

    • connectTimeout(long timeout, TimeUnit unit)
    • readTimeout(long timeout, TimeUnit unit)
    • writeTimeout(long timeout, TimeUnit unit)
    • pingInterval(long interval, TimeUnit unit)
    • cache(Cache cache) 入参如:new Cache(File directory, long maxSize)
    • cookieJar(CookieJar cookieJar) CookieJar是一个接口
    • hostnameVerifier(HostnameVerifier hostnameVerifier) HostnameVerifier是一个接口,只有boolean verify(String hostname, SSLSession session)
    • sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)

    2. Request

    • Request(Request.Builder builder)

    Request.Builder

    • addHeader(String name, String value) 添加键值对,不会覆盖
    • header(String name, String value) 添加键值对,会覆盖
    • url(String url)
    • method(String method, RequestBody body)
    • post(RequestBody body) 本质:method("POST", body)
    • build() Request

    RequestBody

    • create(MediaType contentType, final File file) RequestBody
    • create(MediaType contentType, String content) RequestBody
    • create(MediaType contentType, byte[] content) RequestBody

    FormBody

    RequestBody的子类

    FormBody.Builder

    • add(String name, String value) FormBody.Builder
    • build() FormBody

    MultipartBody

    RequestBody的子类

    MultipartBody.Builder

    • Builder()
    • Builder(String boundary)
    • setType(MediaType type)
    • addPart(Headers headers, RequestBody body)
    • addFormDataPart(String name, String filename, RequestBody body)
    • build() MultipartBody

    3. Call

    Call负责发送请求和读取响应

    • enqueue(Callback responseCallback) 加入调度队列,异步执行
    • execute() Response 同步执行
    • cancel()

    4. Response

    • body() ResponseBody
    • code() int http请求的状态码
    • isSuccessful() code为2XX时,返回true,否则false
    • headers() Headers

    ResponseBody

    • string() String
    • bytes() byte[]
    • byteStream() InputStream
    • charStream() Reader
    • contentLength() long

    5. MediaType

    RequestBody的数据格式都要指定Content-Type,就是指定MIME,常见的有三种:

    • application/x-www-form-urlencoded 数据是个普通表单(默认)
    • multipart/form-data 数据里有文件
    • application/json 数据是个json

    方法:

    • parse(String string) MediaType
    参数 说明
    text/html HTML格式
    text/plain 纯文本格式
    image/gif gif图片格式
    image/jpeg jpg图片格式
    image/png png图片格式
    application/json JSON数据格式
    application/pdf pdf格式
    application/msword Word文档格式
    application/octet-stream 二进制流数据
    application/x-www-form-urlencoded 普通表单数据
    multipart/form-data 表单数据里有文件

    6. 自动管理Cookie

    Request经常都要携带Cookie,request创建时可以通过header设置参数,Cookie也是参数之一。就像下面这样:

    Request request = new Request.Builder()
        .url(url)
        .header("Cookie", "xxx")
        .build();
    

    然后可以从返回的response里得到新的Cookie,你可能得想办法把Cookie保存起来。
    但是OkHttp可以不用我们管理Cookie,自动携带,保存和更新Cookie。
    方法是在创建OkHttpClient设置管理Cookie的CookieJar:

    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .cookieJar(new CookieJar() {
            @Override
            public void saveFromResponse(HttpUrl httpUrl, List<Cookie> list) {
                cookieStore.put(httpUrl.host(), list);
            }
    
            @Override
            public List<Cookie> loadForRequest(HttpUrl httpUrl) {
                List<Cookie> cookies = cookieStore.get(httpUrl.host());
                return cookies != null ? cookies : new ArrayList<Cookie>();
            }
        })
        .build();
    

    这样以后发送Request都不用管Cookie这个参数也不用去response获取新Cookie什么的了。还能通过cookieStore获取当前保存的Cookie。
    最后,new OkHttpClient()只是一种快速创建OkHttpClient的方式,更标准的是使用OkHttpClient.Builder()。后者可以设置一堆参数,例如超时时间什么的。

    6.4.4 get请求

    1. 异步的get请求

    //step 1: 创建 OkHttpClient 对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //step 2: 创建一个请求,不指定请求方法时默认是GET。
    Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
    //可以省略,默认是GET请求
    requestBuilder.method("GET", null);
    //step 3:创建 Call 对象
    Call call = okHttpClient.newCall(requestBuilder.build());
    //step 4: 开始异步请求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
            //获得返回体
            ResponseBody body = response.body();
        }
    });
    

    首先要将OkHttpClient的对象与Request的对象建立起来联系,使用okHttpClient的newCall()方法得到一个Call对象,这个Call对象的作用就是相当于将请求封装成了一个任务,既然是任务,自然就会有execute()cancel()等方法。

    最后,我们希望以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。但要注意的是,call的回调是子线程,所以是不能直接操作界面的。当请求成功时就会回调onResponse()方法,我们可以看到返回的结果是Response对象,在此我们比较关注的是请求中的返回体body(ResponseBody类型),大多数的情况下我们希望获得字符串从而进行json解析获得数据,所以可以通过body.string()的方式获得字符串。如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputStream,则调用response.body().byteStream()。

    2. 同步的get请求

    调用Call#execute()方法,在主线程运行

    6.4.5 post请求

    1. Post上传表单(键值对)

    //step1: 同样的需要创建一个OkHttpClick对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //step2: 创建 FormBody.Builder
    FormBody formBody = new FormBody.Builder()
        .add("name", "dsd") //添加键值对
        .build();
    //step3: 创建请求
    Request request = new Request.Builder().url("http://www.baidu.com")
        .post(formBody)
        .build()
    //step4: 建立联系 创建Call对象
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
        }
    });
    

    2. Post异步上传文件

    // step 1: 创建 OkHttpClient 对象
    OkHttpClient okHttpClient = new OkHttpClient();
    //step 2:创建 RequestBody 以及所需的参数
    //2.1 获取文件
    File file = new File(Environment.getExternalStorageDirectory() + "test.txt");
    //2.2 创建 MediaType 设置上传文件类型
    MediaType MEDIATYPE = MediaType.parse("text/plain; charset=utf-8");
    //2.3 获取请求体
    RequestBody requestBody = RequestBody.create(MEDIATYPE, file);
    //step 3:创建请求
    Request request = new Request.Builder().url("http://www.baidu.com")
        .post(requestBody)
        .build();
    //step 4 建立联系
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
    
        @Override
        public void onResponse(Call call, Response response) throws IOException {
        }
    });
    

    3. Post方式提交流

    以流的方式POST提交请求体. 请求体的内容由流写入产生. 这个例子是流直接写入Okio的BufferedSink. 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取. OkHttp的底层对流和字节的操作都是基于Okio库, Okio库也是Square开发的另一个IO库, 填补I/O和NIO的空缺, 目的是提供简单便于使用的接口来操作IO.

    public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        RequestBody requestBody = new RequestBody() {
        @Override
        public MediaType contentType() {
            return MEDIA_TYPE_MARKDOWN;
        }
    
        @Override
        public void writeTo(BufferedSink sink) throws IOException {
            sink.writeUtf8("Numbers\n");
            sink.writeUtf8("-------\n");
            for (int i = 2; i <= 997; i++) {
                sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
        }
    
        private String factor(int n) {
            for (int i = 2; i < n; i++) {
                int x = n / i;
                if (x * i == n) return factor(x) + " × " + i;
            }
            return Integer.toString(n);
        }
    };
    
    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();
    
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
        System.out.println(response.body().string());
    }
    

    4. Post方式提交String

    下面是使用HTTP POST提交请求到服务. 这个例子提交了一个markdown文档到web服务, 以HTML方式渲染markdown. 因为整个请求体都在内存中, 因此避免使用此api提交大文档(大于1MB).

    public static final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
    
    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        String postBody = ""
            + "Releases\n"
            + "--------\n"
            + "\n"
            + " * _1.0_ May 6, 2013\n"
            + " * _1.1_ June 15, 2013\n"
            + " * _1.2_ August 11, 2013\n";
    
        Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }
    

    5、Post方式提交分块请求

    MultipartBody.Builder可以构建复杂的请求体, 与HTML文件上传形式兼容. 多块请求体中每块请求都是一个请求体, 可以定义自己的请求头. 这些请求头可以用来描述这块请求, 例如它的Content-Disposition. 如果Content-Length和Content-Type可用的话, 他们会被自动添加到请求头中.

    private static final String IMGUR_CLIENT_ID = "...";
    private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");
    
    private final OkHttpClient client = new OkHttpClient();
    
    public void run() throws Exception {
        // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
        RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();
    
        Request request = new Request.Builder()
            .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
            .url("https://api.imgur.com/3/image")
            .post(requestBody)
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
    }
    

    六、其他用法

    1、提取响应头

    典型的HTTP头是一个Map<String, String> : 每个字段都有一个或没有值. 但是一些头允许多个值。
    当写请求头的时候, 使用header(name, value)可以设置唯一的name、value. 如果已经有值, 旧的将被移除, 然后添加新的. 使用addHeader(name, value)可以添加多值(添加, 不移除已有的).
    当读取响应头时, 使用header(name)返回最后出现的name、value. 通常情况这也是唯一的name、value. 如果没有值, 那么header(name)将返回null. 如果想读取字段对应的所有值, 使用headers(name)`会返回一个list.
    为了获取所有的Header, Headers类支持按index访问.

    private final OkHttpClient client = new OkHttpClient();
     
    public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")
            .addHeader("Accept", "application/json; q=0.5")
            .addHeader("Accept", "application/vnd.github.v3+json")
            .build();
     
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
     
        System.out.println("Server: " + response.header("Server"));
        System.out.println("Date: " + response.header("Date"));
        System.out.println("Vary: " + response.headers("Vary"));
    }
    

    2、使用Gson来解析JSON响应

    Gson是一个在JSON和Java对象之间转换非常方便的api库. 这里我们用Gson来解析Github API的JSON响应.
    注意: ResponseBody.charStream()使用响应头Content-Type指定的字符集来解析响应体. 默认是UTF-8.

    private final OkHttpClient client = new OkHttpClient();
        private final Gson gson = new Gson();
    
        public void run() throws Exception {
        Request request = new Request.Builder()
            .url("https://api.github.com/gists/c2a7c39532239ff261be")
            .build();
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        Gist gist = gson.fromJson(response.body().charStream(), Gist.class);
        for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
          System.out.println(entry.getKey());
          System.out.println(entry.getValue().content);
        }
      }
    
      static class Gist {
        Map<String, GistFile> files;
      }
    
      static class GistFile {
        String content;
      }
    

    3、响应缓存

    OKHTTP如果要设置缓存,首要的条件就是设置一个缓存文件夹,在Android中为了安全起见,一般设置为私密数据空间。通过getExternalCacheDir()获取。
    如然后通过调用OKHttpClient.Builder中的cache()方法。如下面代码所示:

    //缓存文件夹
    File cacheFile = new File(getExternalCacheDir().toString(),"cache");
    //缓存大小为10M
    int cacheSize = 10 * 1024 * 1024;
    //创建缓存对象
    Cache cache = new Cache(cacheFile,cacheSize);
    
    OkHttpClient client = new OkHttpClient.Builder()
            .cache(cache)
            .build();
    

    设置好Cache我们就可以正常访问了。我们可以通过获取到的Response对象拿到它正常的消息和缓存的消息。

    Response的消息有两种类型,CacheResponse和NetworkResponse。CacheResponse代表从缓存取到的消息,NetworkResponse代表直接从服务端返回的消息。
    示例代码如下:

    private void testCache(){
            //缓存文件夹
            File cacheFile = new File(getExternalCacheDir().toString(),"cache");
            //缓存大小为10M
            int cacheSize = 10 * 1024 * 1024;
            //创建缓存对象
            final Cache cache = new Cache(cacheFile,cacheSize);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    OkHttpClient client = new OkHttpClient.Builder()
                            .cache(cache)
                            .build();
                    //官方的一个示例的url
                    String url = "http://publicobject.com/helloworld.txt";
    
                    Request request = new Request.Builder()
                            .url(url)
                            .build();
                    Call call1 = client.newCall(request);
                    Response response1 = null;
                    try {
                        //第一次网络请求
                        response1 = call1.execute();
                        Log.i(TAG, "testCache: response1 :"+response1.body().string());
                        Log.i(TAG, "testCache: response1 cache :"+response1.cacheResponse());
                        Log.i(TAG, "testCache: response1 network :"+response1.networkResponse());
                        response1.body().close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                    Call call12 = client.newCall(request);
    
                    try {
                        //第二次网络请求
                        Response response2 = call12.execute();
                        Log.i(TAG, "testCache: response2 :"+response2.body().string());
                        Log.i(TAG, "testCache: response2 cache :"+response2.cacheResponse());
                        Log.i(TAG, "testCache: response2 network :"+response2.networkResponse());
                        Log.i(TAG, "testCache: response1 equals response2:"+response2.equals(response1));
                        response2.body().close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
        }
    

    我们在上面的代码中,用同一个url地址分别进行了两次网络访问,然后分别用Log打印它们的信息。打印的结果主要说明了一个现象,第一次访问的时候,Response的消息是NetworkResponse消息,此时CacheResponse的值为Null.而第二次访问的时候Response是CahceResponse,而此时NetworkResponse为空。也就说明了上面的示例代码能够进行网络请求的缓存。

    其实控制缓存的消息头往往是服务端返回的信息中添加的如”Cache-Control:max-age=60”。所以,会有两种情况。

    1. 客户端和服务端开发能够很好沟通,按照达成一致的协议,服务端按照规定添加缓存相关的消息头。
    2. 客户端与服务端的开发根本就不是同一家公司,没有办法也不可能要求服务端按照客户端的意愿进行开发。

    第一种办法当然很好,只要服务器在返回消息的时候添加好Cache-Control相关的消息便好。

    第二种情况,就很麻烦,你真的无法左右别人的行为。怎么办呢?好在OKHTTP能够很轻易地处理这种情况。那就是定义一个拦截器,
    人为地添加Response中的消息头,然后再传递给用户,这样用户拿到的Response就有了我们理想当中的消息头Headers,从而达到控制缓存的意图,正所谓移花接木。

    缓存之拦截器

    因为拦截器可以拿到Request和Response,所以可以轻而易举地加工这些东西。在这里我们人为地添加Cache-Control消息头。

    class CacheInterceptor implements Interceptor{
    
            @Override
            public Response intercept(Chain chain) throws IOException {
    
                Response originResponse = chain.proceed(chain.request());
    
                //设置缓存时间为60秒,并移除了pragma消息头,移除它的原因是因为pragma也是控制缓存的一个消息头属性
                return originResponse.newBuilder().removeHeader("pragma")
                        .header("Cache-Control","max-age=60").build();
            }
        }
    

    定义好拦截器中后,我们可以添加到OKHttpClient中了。

    private void testCacheInterceptor(){
            //缓存文件夹
            File cacheFile = new File(getExternalCacheDir().toString(),"cache");
            //缓存大小为10M
            int cacheSize = 10 * 1024 * 1024;
            //创建缓存对象
            final Cache cache = new Cache(cacheFile,cacheSize);
    
            OkHttpClient client = new OkHttpClient.Builder()
                    .addNetworkInterceptor(new CacheInterceptor())
                    .cache(cache)
                    .build();
            .......
    }
    

    代码后面部分有省略。主要通过在OkHttpClient.Builder()中addNetworkInterceptor()中添加。而这样也挺简单的,就几步完成了缓存代码。

    拦截器进行缓存的缺点

    1. 网络访问请求的资源是文本信息,如新闻列表,这类信息经常变动,一天更新好几次,它们用的缓存时间应该就很短。
    2. 网络访问请求的资源是图片或者视频,它们变动很少,或者是长期不变动,那么它们用的缓存时间就应该很长。

    那么,问题来了。
    因为OKHTTP开发建议是同一个APP,用同一个OKHTTPCLIENT对象这是为了只有一个缓存文件访问入口。这个很容易理解,单例模式嘛。但是问题拦截器是在OKHttpClient.Builder当中添加的。如果在拦截器中定义缓存的方法会导致图片的缓存和新闻列表的缓存时间是一样的,这显然是不合理的,真实的情况不应该是图片请求有它的缓存时间,新闻列表请求有它的缓存时间,应该是每一个Request有它的缓存时间。 那么,有解决的方案吗? 有的,okhttp官方有建议的方法。

    okhttp官方文档建议缓存方法

    okhttp中建议用CacheControl这个类来进行缓存策略的制定。
    它内部有两个很重要的静态实例。

    /**强制使用网络请求*/
    public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
    
      /**
       * 强制性使用本地缓存,如果本地缓存不满足条件,则会返回code为504
       */
      public static final CacheControl FORCE_CACHE = new Builder()
          .onlyIfCached()
          .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
          .build();
    

    我们看到FORCE_NETWORK常量用来强制使用网络请求。FORCE_CACHE只取本地的缓存。它们本身都是CacheControl对象,由内部的Buidler对象构造。下面我们来看看CacheControl.Builder

    CacheControl.Builder

    它有如下方法:

    • noCache()//不使用缓存,用网络请求
    • noStore()//不使用缓存,也不存储缓存
    • onlyIfCached()//只使用缓存
    • noTransform()//禁止转码
    • maxAge(10, TimeUnit.MILLISECONDS)//设置超时时间为10ms。
    • maxStale(10, TimeUnit.SECONDS)//超时之外的超时时间为10s
    • minFresh(10, TimeUnit.SECONDS)//超时时间为当前时间加上10秒钟。

    知道了CacheControl的相关信息,那么它怎么使用呢?不同于拦截器设置缓存,CacheControl是针对Request的,所以它可以针对每个请求设置不同的缓存策略。比如图片和新闻列表。下面代码展示如何用CacheControl设置一个60秒的超时时间。

    private void testCacheControl(){
            //缓存文件夹
            File cacheFile = new File(getExternalCacheDir().toString(),"cache");
            //缓存大小为10M
            int cacheSize = 10 * 1024 * 1024;
            //创建缓存对象
            final Cache cache = new Cache(cacheFile,cacheSize);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    OkHttpClient client = new OkHttpClient.Builder()
                            .cache(cache)
                            .build();
                    //设置缓存时间为60秒
                    CacheControl cacheControl = new CacheControl.Builder()
                            .maxAge(60, TimeUnit.SECONDS)
                            .build();
                    Request request = new Request.Builder()
                            .url("http://blog.csdn.net/briblue")
                            .cacheControl(cacheControl)
                            .build();
                    try {
                        Response response = client.newCall(request).execute();
                        response.body().close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
        }
    

    强制使用缓存

    前面有讲CacheControl.FORCE_CACHE这个常量。

    public static final CacheControl FORCE_CACHE = new Builder()
          .onlyIfCached()
          .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS)
    

    它内部其实就是调用onlyIfCached()和maxStale方法。
    它的使用方法为

    Request request = new Request.Builder()
                .url("http://blog.csdn.net/briblue")
                .cacheControl(Cache.FORCE_CACHE)
                .build();
    

    但是如前面后提到的,如果缓存不符合条件会返回504.这个时候我们要根据情况再进行编码,如缓存不行就再进行一次网络请求。

    Response forceCacheResponse = client.newCall(request).execute();
         if (forceCacheResponse.code() != 504) {
           // 资源已经缓存了,可以直接使用
         } else {
           // 资源没有缓存,或者是缓存不符合条件了。
         } 
    

    不使用缓存

    前面也有讲CacheControl.FORCE_NETWORK这个常量。

    public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();
    

    它的内部其实是调用noCache()方法,也就是不缓存的意思。
    它的使用方法为

    Request request = new Request.Builder()
                .url("http://blog.csdn.net/briblue")
                .cacheControl(Cache.FORCE_NETWORK)
                .build();
    

    还有一种情况将maxAge设置为0,也不会取缓存,直接走网络。

    Request request = new Request.Builder()
                .url("http://blog.csdn.net/briblue")
                .cacheControl(new CacheControl.Builder()
                .maxAge(0, TimeUnit.SECONDS))
                .build();
    

    4、取消一个Call

    使用Call.cancel()可以立即停止掉一个正在执行的call. 如果一个线程正在写请求或者读响应, 将会引发IOException. 当call没有必要的时候, 使用这个api可以节约网络资源. 例如当用户离开一个应用时, 不管同步还是异步的call都可以取消.
    你可以通过tags来同时取消多个请求. 当你构建一请求时, 使用RequestBuilder.tag(tag)来分配一个标签, 之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call.

     private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
     private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        final long startNanos = System.nanoTime();
        final Call call = client.newCall(request);
    
        // Schedule a job to cancel the call in 1 second.
        executor.schedule(new Runnable() {
          @Override public void run() {
            System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
            call.cancel();
            System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
          }
        }, 1, TimeUnit.SECONDS);
    
        try {
          System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
          Response response = call.execute();
          System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, response);
        } catch (IOException e) {
          System.out.printf("%.2f Call failed as expected: %s%n",
              (System.nanoTime() - startNanos) / 1e9f, e);
        }
      }
    

    5、超时

    没有响应时使用超时结束call. 没有响应的原因可能是客户点链接问题、服务器可用性问题或者这之间的其他东西. OkHttp支持连接超时, 读取超时和写入超时.

      private final OkHttpClient client;
    
      public ConfigureTimeouts() throws Exception {
        client = new OkHttpClient.Builder()
            .connectTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
            .build();
    
        Response response = client.newCall(request).execute();
        System.out.println("Response completed: " + response);
      }
    

    6、每个call的配置

    使用OkHttpClient, 所有的HTTP Client配置包括代理设置、超时设置、缓存设置. 当你需要为单个call改变配置的时候, 调用OkHttpClient.newBuilder(). 这个api将会返回一个builder, 这个builder和原始的client共享相同的连接池, 分发器和配置.
    下面的例子中,我们让一个请求是500ms的超时、另一个是3000ms的超时。

      private final OkHttpClient client = new OkHttpClient();
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
            .build();
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(500, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 1 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 1 failed: " + e);
        }
    
        try {
          // Copy to customize OkHttp for this request.
          OkHttpClient copy = client.newBuilder()
              .readTimeout(3000, TimeUnit.MILLISECONDS)
              .build();
    
          Response response = copy.newCall(request).execute();
          System.out.println("Response 2 succeeded: " + response);
        } catch (IOException e) {
          System.out.println("Response 2 failed: " + e);
        }
      }
    

    七、处理验证

    这部分和HTTP AUTH有关.

    1、HTTP AUTH

    使用HTTP AUTH需要在server端配置http auth信息, 其过程如下:

    • 客户端发送http请求(没有Authorization header)
    • 服务器发现配置了http auth, 于是检查request里面有没有"Authorization"的http header
      如果有, 则判断Authorization里面的内容是否在用户列表里面, Authorization header的典型数据为"Authorization: Basic jdhaHY0=", 其中Basic表示基础认证, jdhaHY0=是base64编码的"user:passwd"字符串. 如果没有,或者用户密码不对,则返回http code 401页面给客户端.
    • 标准的http浏览器在收到401页面之后, 应该弹出一个对话框让用户输入帐号密码; 并在用户点确认的时候再次发出请求, 这次请求里面将带上Authorization header.
    • 服务器端认证通过,并返回页面
    • 浏览器显示页面
      一次典型的访问场景是:

    2、OkHttp认证

    OkHttp会自动重试未验证的请求. 当响应是401 Not Authorized时,Authenticator会被要求提供证书. Authenticator的实现中需要建立一个新的包含证书的请求. 如果没有证书可用, 返回null来跳过尝试.
    使用Response.challenges()来获得任何authentication challenges的 schemes 和 realms. 当完成一个Basic challenge, 使用Credentials.basic(username, password)来解码请求头.

      private final OkHttpClient client;
    
      public Authenticate() {
        client = new OkHttpClient.Builder()
            .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response response) throws IOException {
                System.out.println("Authenticating for response: " + response);
                System.out.println("Challenges: " + response.challenges());
                String credential = Credentials.basic("jesse", "password1");
                return response.request().newBuilder()
                    .header("Authorization", credential)
                    .build();
              }
            })
            .build();
      }
    
      public void run() throws Exception {
        Request request = new Request.Builder()
            .url("http://publicobject.com/secrets/hellosecret.txt")
            .build();
    
        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    
        System.out.println(response.body().string());
      }
    

    当认证无法工作时, 为了避免多次重试, 你可以返回空来放弃认证. 例如, 当exact credentials已经尝试过, 你可能会直接想跳过认证, 可以这样做:

      if (credential.equals(response.request().header("Authorization"))) {
        return null; // If we already failed with these credentials, don't retry.
       }
    

    当重试次数超过定义的次数, 你若想跳过认证, 可以这样做:

      if (responseCount(response) >= 3) {
        return null; // If we've failed 3 times, give up.
      }
      
      private int responseCount(Response response) {
        int result = 1;
        while ((response = response.priorResponse()) != null) {
          result++;
        }
        return result;
      }
    

    参考文献

    展开全文
  • 对称加密和非对称加密原理

    千次阅读 2019-02-22 19:14:47
    对称加密和不对称加密原理 **私钥加密(对称加密 symmetric cryptography):**私钥加密算法使用单个私钥来加密和解密数据。由于具有密钥的任意一方都可以使用该密钥解密数据,因此必须保护密钥不被未经授权的代理...

    对称加密和非对称加密原理

    私钥加密(对称加密 symmetric cryptography):私钥加密算法使用单个私钥来加密和解密数据。由于具有密钥的任意一方都可以使用该密钥解密数据,因此必须保护密钥不被未经授权的代理得到。私钥加密又称为对称加密 ,因为同一密钥既用于加密又用于解密。私钥加密算法非常快(与公钥算法相比),特别适用于对较大的数据流执行加密转换。Well-known secret key cryptographic algorithms include the Data Encryption Standard (DES), triple-strength DES (3DES), Rivest Cipher 2 (RC2), and Rivest Cipher 4 (RC4).

    通常,私钥算法(称为块密码)用于一次加密一个数据块。块密码(如 RC2、DES、TripleDES 和 Rijndael)通过加密将 n 字节的输入块转换为加密字节的输出块。如果要加密或解密字节序列,必须逐块进行。由于 n 很小(对于 RC2、DES 和 TripleDES,n = 8 字节;n = 16 [默认值];n = 24;对于 Rijndael,n = 32),因此必须对大于 n 的数据值一次加密一个块。
    基类库中提供的块密码类使用称作密码块链 (CBC) 的链模式,它使用一个密钥和一个初始化向量 (IV) 对数据执行加密转换。对于给定的私钥k,一个不使用初始化向量的简单块密码将把相同的明文输入块加密为同样的密文输出块。如果在明文流中有重复的块,那么在密文流中将存在重复的块。如果未经授权的用户知道有关明文块的结构的任何信息,就可以使用这些信息解密已知的密文块并有可能发现您的密钥。若要克服这个问题,可将上一个块中的信息混合到加密下一个块的过程中。这样,两个相同的明文块的输出就会不同。由于该技术使用上一个块加密下一个块,因此使用了一个 IV 来加密数据的第一个块。使用该系统,未经授权的用户有可能知道的公共消息标头将无法用于对密钥进行反向工程。

    可以危及用此类型密码加密的数据的一个方法是,对每个可能的密钥执行穷举搜索。根据用于执行加密的密钥大小,即使使用最快的计算机执行这种搜索,也极其耗时,因此难以实施。使用较大的密钥大小将使解密更加困难。虽然从理论上说加密不会使对手无法检索加密的数据,但这确实极大增加了这样做的成本。如果执行彻底搜索来检索只在几天内有意义的数据需要花费三个月的时间,那么穷举搜索的方法是不实用的。
    私钥加密的缺点是它假定双方已就密钥和 IV 达成协议,并且互相传达了密钥和 IV 的值。并且,密钥必须对未经授权的用户保密。由于存在这些问题,私钥加密通常与公钥加密一起使用,来秘密地传达密钥和 IV 的值。

    最早、最著名的保密密钥或对称密钥加密算法DES(Data Encryption Standard)/DESede是由IBM公司在70年代发展起来的,并经政府的加密标准筛选后,于1976年11月被美国政府采用,DES随后被美国国家标准局和美国国家标准协会(American National Standard Institute,ANSI)承认。DES使用56位密钥对64位的数据块进行加密,并对64位的数据块进行16轮编码。与每轮编码时,一个48位的"每轮"密钥值由56位的完整密钥得出来。

    .NET 提供以下实现类以提供对称的密钥加密算法:
    • DESCryptoServiceProvider

    • RC2CryptoServiceProvider

    • RijndaelManaged

    • TripleDESCryptoServiceProvider

    公钥加密(不对称加密, RSA, DSA, DH asymmetric cryptography ):公钥加密使用一个必须对未经授权的用户保密的私钥和一个可以对任何人公开的公钥。公钥和私钥都在数学上相关联;用公钥加密的数据只能用私钥解密,而用私钥签名的数据只能用公钥验证。公钥可以提供给任何人;公钥用于对要发送到私钥持有者的数据进行加密。两个密钥对于通信会话都是唯一的。公钥加密算法也称为不对称算法,原因是需要用一个密钥加密数据而需要用另一个密钥来解密数据。对称算法的根本原理就是单向函数,f(a)=b,但是用b很难得到a。
    公钥加密算法使用固定的缓冲区大小,而私钥加密算法使用长度可变的缓冲区。公钥算法无法像私钥算法那样将数据链接起来成为流,原因是它只可以加密少量数据。因此,不对称操作不使用与对称操作相同的流模型。

    双方(小红和小明)可以按照下列方式使用公钥加密。首先,小红生成一个公钥/私钥对。如果小明想要给小红发送一条加密的消息,他将向她索要她的公钥。小红通过不安全的网络将她的公钥发送给小明,小明接着使用该密钥加密消息。(如果小明在不安全的信道如公共网络上收到小红的密钥,则小明必须同小红验证他具有她的公钥的正确副本。)小明将加密的消息发送给小红,而小红使用她的私钥解密该消息。
    但是,在传输小红的公钥期间,未经授权的代理可能截获该密钥。而且,同一代理可能截获来自小明的加密消息。但是,该代理无法用公钥解密该消息。该消息只能用小红的私钥解密,而该私钥没有被传输。小红不使用她的私钥加密给小明的答复消息,原因是任何具有公钥的人都可以解密该消息。如果小红想要将消息发送回小明,她将向小明索要他的公钥并使用该公钥加密她的消息。然后,小明使用与他相关联的私钥来解密该消息。

    在一个实际方案中,小红和小明使用公钥(不对称)加密来传输私(对称)钥,而对他们的会话的其余部分使用私钥加密(由于对称加密快捷,用于实际的数据加密,而利用不对称加密的方式解决对称加密中私钥传递的不安全性,此为对称和不对称加密结合的加密方式)。
    公钥加密具有更大的密钥空间(或密钥的可能值范围),因此不大容易受到对每个可能密钥都进行尝试的穷举攻击。由于不必保护公钥,因此它易于分发。公钥算法可用于创建数字签名以验证数据发送方的身份。但是,公钥算法非常慢(与私钥算法相比),不适合用来加密大量数据。公钥算法仅对传输很少量的数据有用。公钥加密通常用于加密一个私钥算法将要使用的密钥和 IV。传输密钥和 IV 后,会话的其余部分将使用私钥加密。

    RSA系统是诸多此类算法中最著名、最多使用的一种。RSA公开密钥密码系统是由R.Rivest、A.Shamir和L.Adleman俊教授于1977年提出的。RSA的取名就是来自于这三位发明者的姓的第一个字母RSA(Rivest-Shamir-Adleman)算法是基于大数不可能被质因数分解假设的公钥体系。简单地说就是找两个很大的质数。一个对外公开的为“公钥”(Prblic key) ,另一个不告诉任何人,称为"私钥”(Private key)。这两个密钥是互补的,也就是说用公钥加密的密文可以用私钥解密,反过来也一样。

    公钥的传输:要启动安全通讯,通信两端必须首先得到相同的共享密钥(主密钥),但共享密钥不能通过网络相互发送,因为这种做法极易泄密。Diffie-Hellman算法是用于密钥交换的最早最安全的算法之一。

    RSA算法:RSA算法是基于大数难于分解的原理。不但可以用于认证,也可以用于密钥传输,例子可以参考RSAUtil.java文件。那么用户A和B如何利用RSA算法来传输密钥呢?

    1:A产生一个密钥K,用B的公钥加密K,然后将得到的密文发送给B。

    2:B用自己的私钥解密收到的密钥,就可以得到密钥。

    DH算法:DH算法的出现就是用来进行密钥传输的。DH算法是基于离散对数实现的。DH算法的基本工作原理是:通信双方公开或半公开交换一些准备用来生成密钥的"材料数据",在彼此交换过密钥生成"材料"后,两端可以各自生成出完全一样的共享密钥。在任何时候,双方都绝不交换真正的密钥。通信双方交换的密钥生成"材料",长度不等,"材料"长度越长,所生成的密钥强度也就越高,密钥破译就越困难。除进行密钥交换外,IPSec还使用DH算法生成所有其他加密密钥。

    在通信前,用户A和B双方约定2个大整数n和g,其中1<g<n,这两个整数可以公开

    1. A随机产生一个大整数a,然后计算Ka=ga mod n(a需要保密)

    2. B随机产生一个大整数b,然后计算Kb=gb mod n(b需要保密)

    3. A把Ka发送给B,B把Kb发送给A

    4. A计算K=Kba mod n

    5. B计算K=Kab mod n

    由于Kba mod n= (gb mod n)a mod n= (ga mod n)b mod n,因此可以保证双方得到的K是相同的,K即是共享的密钥。可以参考JCE文档中的DH 3 party的例子。

    实际的一个用DH算法传递DES私钥的JAVA例子参看“JAVA上加密算法的实现用例”一文中的末一个例子,过程如下:假设A和B通信(JCE中只支持DH算法作为传递私钥的算法)

    A利用KeyPairGenerator类生成一个钥对类KeyPair并可通过generatePublic方法产生公钥PublicKey(puA)和getPrivate方法私钥PrivateKey(prA)。A把puA发给B。B用类X509EncodedKeySpec解码,然后利用KeyFactory类生成puA,转换成DHPublicKey类利用其getParams方法取得参数类DHParameterSpec,B再利用此参数类,通过KeyPairGenerator类的initialize方法生成KeyPairGenerator实例从而用generateKeyPair方法生成B的KeyPair。进而B生成puB和prB,B把puB发给A。A利用puB和prA作为参数,分别调用KeyAgreement类的init和doPhase方法初始化,然后用generateSecret方法生成各自的DES的密钥SecretKey,此密钥是相同的,即可用Cipher类进行加解密了。

    .NET 通过抽象基类 (System.Security.Crytography.AsymmetricAlgorithm) 提供下列非对称(公钥/私钥)加密算法:
    • DSACryptoServiceProvider

    • RSACryptoServiceProvider

    数字签名
    公钥算法还可用于构成数字签名。数字签名验证发送方的身份(如果您信任发送方的公钥)并帮助保护数据的完整性。

    为了使用公钥加密对消息进行数字签名,小红首先将哈希算法应用于该消息以创建消息摘要。该消息摘要是数据的紧凑且唯一的表示形式。然后,小红用她的私钥加密该消息摘要以创建她的个人签名。在收到消息和签名时,小明使用小红的公钥解密签名以恢复消息摘要,并使用与小红所使用的相同的哈希算法来散列消息。如果小明计算的消息摘要与从小红那里收到的消息摘要完全一致,小明就可以确定该消息来自私钥的持有人,并且数据未被修改过。如果小明相信小红是私钥的持有人,则他知道该消息来自小红。

    请注意,由于发送方的公钥为大家所周知,并且它通常包含在数字签名格式中,因此任何人都可以验证签名。此方法不保守消息的机密;若要使消息保密,还必须对消息进行加密。

    .NET Framework 提供以下实现数字签名算法的类:

    DSACryptoServiceProvider
    RSACryptoServiceProvider

    CA证书 Public Key Certificates(参考这里)

    A public key certificate provides a safe way for an entity to pass on its public key to be used in asymmetric cryptography. The public key certificate avoids the following situation: if Charlie creates his own public key and private key, he can claim that he is Alice and send his public key to Bob. Bob will be able to communicate with Charlie, but Bob will think that he is sending his data to Alice.

    A public key certificate can be thought of as the digital equivalent of a passport. It is issued by a trusted organization and provides identification for the bearer. A trusted organization that issues public key certificates is known as a certificate authority (CA). The CA can be likened to a notary public. To obtain a certificate from a CA, one must provide proof of identity. Once the CA is confident that the applicant represents the organization it says it represents, the CA signs the certificate attesting to the validity of the information contained within the certificate.

    A public key certificate contains several fields, including:

    Issuer - The issuer is the CA that issued the certificate. If a user trusts the CA that issues a certificate, and if the certificate is valid, the user can trust the certificate.

    Period of validity - A certificate has an expiration date, and this date is one piece of information that should be checked when verifying the validity of a certificate.

    Subject - The subject field includes information about the entity that the certificate represents.

    Subject’s public key - The primary piece of information that the certificate provides is the subject’s public key. All the other fields are provided to ensure the validity of this key.

    Signature - The certificate is digitally signed by the CA that issued the certificate. The signature is created using the CA’s private key and ensures the validity of the certificate. Because only the certificate is signed, not the data sent in the SSL transaction, SSL does not provide for non-repudiation.
    If Bob only accepts Alice’s public key as valid when she sends it in a public key certificate, Bob will not be fooled into sending secret information to Charlie when Charlie masquerades as Alice.

    Multiple certificates may be linked in a certificate chain. When a certificate chain is used, the first certificate is always that of the sender. The next is the certificate of the entity that issued the sender’s certificate. If there are more certificates in the chain, each is that of the authority that issued the previous certificate. The final certificate in the chain is the certificate for a root CA. A root CA is a public certificate authority that is widely trusted. Information for several root CAs is typically stored in the client’s Internet browser. This information includes the CA’s public key. Well-known CAs include VeriSign, Entrust, and GTE CyberTrust.

    哈希值
    哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希计算都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的。

    消息身份验证代码 (MAC) 哈希函数通常与数字签名一起用于对数据进行签名,而消息检测代码 (MDC) 哈希函数则用于数据完整性。

    双方(小红和小明)可按下面的方式使用哈希函数来确保数据的完整性。如果小红对小明编写一条消息并创建该消息的哈希,则小明可以在稍后散列该消息并将他的哈希与原始哈希进行比较。如果两个哈希值相同,则该消息没有被更改;如果值不相同,则该消息在小红编写它之后已被更改。为了使此系统发挥作用,小红必须对除小明外的所有人保密原始的哈希值。

    When sending encrypted data, SSL typically uses a cryptographic hash function to ensure data integrity. The hash function prevents Charlie from tampering with data that Alice sends to Bob.

    A cryptographic hash function is similar to a checksum. The main difference is that while a checksum is designed to detect accidental alterations in data, a cryptographic hash function is designed to detect deliberate alterations. When data is processed by a cryptographic hash function, a small string of bits, known as a hash, is generated. The slightest change to the message typically makes a large change in the resulting hash. A cryptographic hash function does not require a cryptographic key. Two hash functions often used with SSL are Message Digest 5 (MD5) and Secure Hash Algorithm (SHA). SHA was proposed by the US National Institute of Science and Technology (NIST).

    Message Authentication Code: A message authentication code (MAC) is similar to a cryptographic hash, except that it is based on a secret key. When secret key information is included with the data that is processed by a cryptographic hash function, the resulting hash is known as an HMAC.
    If Alice wants to be sure that Charlie does not tamper with her message to Bob, she can calculate an HMAC for her message and append the HMAC to her original message. She can then encrypt the message plus the HMAC using a secret key she shares with Bob. When Bob decrypts the message and calculates the HMAC, he will be able to tell if the message was modified in transit. With SSL, an HMAC is used with the transmission of secure data.

    .NET Framework 提供以下实现数字签名算法的类:

    HMACSHA1
    MACTripleDES
    MD5CryptoServiceProvider
    SHA1Managed
    SHA256Managed
    SHA384Managed
    SHA512Managed

    摘要函数(MD2、MD4和MD5,还有SHA1等算法(产生一个20字节的二进制数组))
    摘要是一种防止改动的方法,其中用到的函数叫摘要函数。这些函数的输入可以是任意大小的消息,而输出是一个固定长度的摘要。摘要有这样一个性质,如果改变了输入消息中的任何东西,甚至只有一位,输出的摘要将会发生不可预测的改变,也就是说输入消息的每一位对输出摘要都有影响。总之,摘要算法从给定的文本块中产生一个数字签名(fingerprint或message digest),数字签名可以用于防止有人从一个签名上获取文本信息或改变文本信息内容。摘要算法的数字签名原理在很多加密算法中都被使用,如S/KEY和PGP(pretty good privacy)。

    现在流行的摘要函数有MD4和MD5。MD2摘要算法的设计是出于下面的考虑:利用32位RISC结构来最大其吞吐量,而不需要大量的替换表(substitution table)。MD4算法将消息的给予对长度作为输入,产生一个128位的"指纹"或"消息化"。要产生两个具有相同消息化的文字块或者产生任何具有预先给定"指纹"的消息,都被认为在计算上是不可能的。MD5摘要算法是个数据认证标准。MD5的设计思想是要找出速度更快但更不安全的MD4中潜在的不安全,MD5的设计者通过使MD5在计算上慢下来,以及对这些计算做了一些基础性的改动来解决这个问题。MD5在RFC1321中给出文档描述,是MD4算法的一个扩展。美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5是为代表。HMAC-MD5算法(消息摘要5)基于RFC1321。MD5对MD4做了改进,计算速度比MD4稍慢,但安全性能得到了进一步改善。MD5在计算中使用了64个32位常数,最终生成一个128位的完整性检查和。

    HMAC-SHA算法:安全Hash算法定义在NIST FIPS 180-1,其算法以MD5为原型。SHA在计算中使用了79个32位常数,最终产生一个160位完整性检查和。SHA检查和长度比MD5更长,因此安全性也更高。

    随机数生成
    随机数生成是许多加密操作不可分割的组成部分。例如,加密密钥需要尽可能地随机,以便使生成的密钥很难再现。加密随机数生成器必须生成无法以计算方法推算出(低于 p < .05 的概率)的输出;即,任何推算下一个输出位的方法不得比随机猜测具有更高的成功概率。.NET Framework 中的类使用随机数生成器生成加密密钥。

    在商务领域的应用

    许多人都知道NETSCAPE公司是Internet商业中领先技术的提供者,该公司提供了一种基于RSA和保密密钥的应用于因特网的技术,被称为安全插座层(Secure Sockets Layer,SSL)。

    也许很多人知道Socket,它是一个编程界面,并不提供任何安全措施,而SSL不但提供编程界面,而且向上提供一种安全的服务,SSL3.0现在已经应用到了服务器和浏览器上,SSL2.0则只能应用于服务器端。

    SSL3.0用一种电子证书(electric certificate)来实行身份进行验证后,双方就可以用保密密钥进行安全的会话了。它同时使用“对称”和“非对称”加密方法,在客户与电子商务的服务器进行沟通的过程中,客户会产生一个Session Key,然后客户用服务器端的公钥将Session Key进行加密,再传给服务器端,在双方都知道Session Key后,传输的数据都是以Session Key进行加密与解密的,但服务器端发给用户的公钥必需先向有关发证机关申请,以得到公证。

    基于SSL3.0提供的安全保障,用户就可以自由订购商品并且给出信用卡号了,也可以在网上和合作伙伴交流商业信息并且让供应商把订单和收货单从网上发过来,这样可以节省大量的纸张,为公司节省大量的电话、传真费用。在过去,电子信息交换(Electric Data Interchange,EDI)、信息交易(information transaction)和金融交易(financial transaction)都是在专用网络上完成的,使用专用网的费用大大高于互联网。正是这样巨大的诱惑,才使人们开始发展因特网上的电子商务,但不要忘记数据加密。

    SSL(Secure Sockets Layer) or TSL (Transport Layer Security)

    Secure Sockets Layer (SSL) is the most widely used protocol for implementing cryptography on the Web. SSL uses a combination of cryptographic processes to provide secure communication over a network. This section provides an introduction to SSL and the cryptographic processes it uses.

    SSL provides a secure enhancement to the standard TCP/IP sockets protocol used for Internet communications. As shown in the “TCP/IP Protocol Stack With SSL” figure below, the secure sockets layer is added between the transport layer and the application layer in the standard TCP/IP protocol stack. The application most commonly used with SSL is Hypertext Transfer Protocol (HTTP), the protocol for Internet Web pages.

    SSL was developed by Netscape in 1994, and with input from the Internet community, has evolved to become a standard. It is now under the control of the international standards organization, the Internet Engineering Task Force (IETF). The IETF has renamed SSL to Transport Layer Security (TLS), and released the first specification, version 1.0, in January 1999. TLS 1.0 is a modest upgrade to the most recent version of SSL, version 3.0. The differences between SSL 3.0 and TLS 1.0 are minor.

    One of the reasons SSL is effective is that it uses several different cryptographic processes. SSL uses public key cryptography to provide authentication, and secret key cryptography and digital signatures to provide for privacy and data integrity. Before you can understand SSL, it is helpful to understand these cryptographic processes.

    常用算法比较

    数据加密标准(DES)是一个古老的对称密钥加密算法,目前已经不再使用。它不是一个很安全的算法。

    三重DES(Triple-DES)仍然是很安全的,但是也只是在别无他法的情况下的一个较好的选择。显然高级加密标准(AES)是一个更好的加密算法,NIST用AES代替Triple-DES作为他们的标准(下面有更详细的讨论)。其他较好的算法包括另外两个AES的变种算法Twofish和Serpent-也称为CAST-128,它是效率和安全的完美结合。这几个算法不仅比DES更安全,而且也比DES的速度更快。为什么要使用一些又慢又不安全的算法呢

    SHA1是一个哈希函数,而不是一个加密函数。作为一个哈希函数,SHA1还是相当优秀的,但是还需要几年的发展才能用作加密算法。如果你正在设计一个新系统,那么谨记你可能会在若干年后用SHA1代替目前的算法。我再重复一遍:只是可能。

    RSA是一个公开密钥加密算法。RSA的密钥长度一般为2048-4096位。如果你现在的系统使用的是1024位的公开密钥,也没有必要担心,但是你可以加长密钥长度来达到更好的加密效果。

    高级加密标准(AES)是一个用来代替数据加密标准(DES)的算法。目前使用的一般为128,196和256位密钥,这三种密钥都是相当安全的。而且美国政府也是这样认为的。他们批准将128位密钥的AES算法用于一般数据加密,196位和256位密钥的AES算法用于秘密数据和绝密数据的加密。

    DESX是DES的一个改进版本。DESX的原理是利用一个随机的二进制数与加密前的数据以及解密后的数据异或。虽然也有人批评这种算法,但是与DES相比DESX确实更安全,不过DESX在许多情况下并不适用。我曾经处理过一个硬件支持DES的系统,由于有些环节不能容忍三重DES的慢速,我们在这些地方使用了DESX来代替DES。然而,这是一个非常特殊的情况。如果你需要使用DESX,理由显而易见(可能和我不得不使用DESX的原因类似)。但我建议你使用AES或者上面我提到的一些算法。

    RC4是一种常用于SSL连接的数据流加密算法。它已经出现很多年了,而且有很多已知和可能的缺陷,因此在一些新的工程中不要使用它。如果你目前正在使用它而且可以轻易的卸载它,那么情况也不是很坏。不过,我怀疑如果你现在正在使用它,你不可能轻易的卸载它。如果不能将它从系统中轻易的卸载,那么你还是考虑今后怎样升级它,但是不要感到很惊慌。我不会拒绝在一个使用RC4算法来加密SSL连接的网站购买东西,但是如果我现在要新建一个系统,那么我会考虑使用其他的算法,例如:AES。

    对于下面两个算法MD5-RSA和SHA1-DSA,他们是用于数字签名的。但是不要使用MD5,因为它有很多缺陷。很多年前大家就知道MD5中存在漏洞,不过直到今年夏天才破解出来。我们可以将SHA1和RSA或DSA配合在一起使用,目前DSA的密钥位数高达1024位,这个密钥位数已经足够长了,因此不需要担心安全问题。然而,如果NIST实现了更长的密钥位数当然更好。

    X.509证书是一个数据结构,常用于规定比特和字节的顺序,它本身不是一个密码系统。它通常包含一个RSA密钥,也可能包含一个DSA密钥。但是X.509证书内部以及证书本身并不是加密技术。对称加密和不对称加密原理
    私钥加密(对称加密 symmetric cryptography):私钥加密算法使用单个私钥来加密和解密数据。由于具有密钥的任意一方都可以使用该密钥解密数据,因此必须保护密钥不被未经授权的代理得到。私钥加密又称为对称加密,因为同一密钥既用于加密又用于解密。私钥加密算法非常快(与公钥算法相比),特别适用于对较大的数据流执行加密转换。Well-known secret key cryptographic algorithms include the Data Encryption Standard (DES), triple-strength DES (3DES), Rivest Cipher 2 (RC2), and Rivest Cipher 4 (RC4).

    通常,私钥算法(称为块密码)用于一次加密一个数据块。块密码(如 RC2、DES、TripleDES 和 Rijndael)通过加密将 n 字节的输入块转换为加密字节的输出块。如果要加密或解密字节序列,必须逐块进行。由于 n 很小(对于 RC2、DES 和 TripleDES,n = 8 字节;n = 16 [默认值];n = 24;对于 Rijndael,n = 32),因此必须对大于 n 的数据值一次加密一个块。
    基类库中提供的块密码类使用称作密码块链 (CBC) 的链模式,它使用一个密钥和一个初始化向量 (IV) 对数据执行加密转换。对于给定的私钥k,一个不使用初始化向量的简单块密码将把相同的明文输入块加密为同样的密文输出块。如果在明文流中有重复的块,那么在密文流中将存在重复的块。如果未经授权的用户知道有关明文块的结构的任何信息,就可以使用这些信息解密已知的密文块并有可能发现您的密钥。若要克服这个问题,可将上一个块中的信息混合到加密下一个块的过程中。这样,两个相同的明文块的输出就会不同。由于该技术使用上一个块加密下一个块,因此使用了一个 IV 来加密数据的第一个块。使用该系统,未经授权的用户有可能知道的公共消息标头将无法用于对密钥进行反向工程。

    可以危及用此类型密码加密的数据的一个方法是,对每个可能的密钥执行穷举搜索。根据用于执行加密的密钥大小,即使使用最快的计算机执行这种搜索,也极其耗时,因此难以实施。使用较大的密钥大小将使解密更加困难。虽然从理论上说加密不会使对手无法检索加密的数据,但这确实极大增加了这样做的成本。如果执行彻底搜索来检索只在几天内有意义的数据需要花费三个月的时间,那么穷举搜索的方法是不实用的。
    私钥加密的缺点是它假定双方已就密钥和 IV 达成协议,并且互相传达了密钥和 IV 的值。并且,密钥必须对未经授权的用户保密。由于存在这些问题,私钥加密通常与公钥加密一起使用,来秘密地传达密钥和 IV 的值。

    最早、最著名的保密密钥或对称密钥加密算法DES(Data Encryption Standard)/DESede是由IBM公司在70年代发展起来的,并经政府的加密标准筛选后,于1976年11月被美国政府采用,DES随后被美国国家标准局和美国国家标准协会(American National Standard Institute,ANSI)承认。DES使用56位密钥对64位的数据块进行加密,并对64位的数据块进行16轮编码。与每轮编码时,一个48位的"每轮"密钥值由56位的完整密钥得出来。

    .NET 提供以下实现类以提供对称的密钥加密算法:
    • DESCryptoServiceProvider

    • RC2CryptoServiceProvider

    • RijndaelManaged

    • TripleDESCryptoServiceProvider

    公钥加密(不对称加密, RSA, DSA, DH asymmetric cryptography ):公钥加密使用一个必须对未经授权的用户保密的私钥和一个可以对任何人公开的公钥。公钥和私钥都在数学上相关联;用公钥加密的数据只能用私钥解密,而用私钥签名的数据只能用公钥验证。公钥可以提供给任何人;公钥用于对要发送到私钥持有者的数据进行加密。两个密钥对于通信会话都是唯一的。公钥加密算法也称为不对称算法,原因是需要用一个密钥加密数据而需要用另一个密钥来解密数据。对称算法的根本原理就是单向函数,f(a)=b,但是用b很难得到a。
    公钥加密算法使用固定的缓冲区大小,而私钥加密算法使用长度可变的缓冲区。公钥算法无法像私钥算法那样将数据链接起来成为流,原因是它只可以加密少量数据。因此,不对称操作不使用与对称操作相同的流模型。

    双方(小红和小明)可以按照下列方式使用公钥加密。首先,小红生成一个公钥/私钥对。如果小明想要给小红发送一条加密的消息,他将向她索要她的公钥。小红通过不安全的网络将她的公钥发送给小明,小明接着使用该密钥加密消息。(如果小明在不安全的信道如公共网络上收到小红的密钥,则小明必须同小红验证他具有她的公钥的正确副本。)小明将加密的消息发送给小红,而小红使用她的私钥解密该消息。
    但是,在传输小红的公钥期间,未经授权的代理可能截获该密钥。而且,同一代理可能截获来自小明的加密消息。但是,该代理无法用公钥解密该消息。该消息只能用小红的私钥解密,而该私钥没有被传输。小红不使用她的私钥加密给小明的答复消息,原因是任何具有公钥的人都可以解密该消息。如果小红想要将消息发送回小明,她将向小明索要他的公钥并使用该公钥加密她的消息。然后,小明使用与他相关联的私钥来解密该消息。

    在一个实际方案中,小红和小明使用公钥(不对称)加密来传输私(对称)钥,而对他们的会话的其余部分使用私钥加密(由于对称加密快捷,用于实际的数据加密,而利用不对称加密的方式解决对称加密中私钥传递的不安全性,此为对称和不对称加密结合的加密方式)。
    公钥加密具有更大的密钥空间(或密钥的可能值范围),因此不大容易受到对每个可能密钥都进行尝试的穷举攻击。由于不必保护公钥,因此它易于分发。公钥算法可用于创建数字签名以验证数据发送方的身份。但是,公钥算法非常慢(与私钥算法相比),不适合用来加密大量数据。公钥算法仅对传输很少量的数据有用。公钥加密通常用于加密一个私钥算法将要使用的密钥和 IV。传输密钥和 IV 后,会话的其余部分将使用私钥加密。

    RSA系统是诸多此类算法中最著名、最多使用的一种。RSA公开密钥密码系统是由R.Rivest、A.Shamir和L.Adleman俊教授于1977年提出的。RSA的取名就是来自于这三位发明者的姓的第一个字母RSA(Rivest-Shamir-Adleman)算法是基于大数不可能被质因数分解假设的公钥体系。简单地说就是找两个很大的质数。一个对外公开的为“公钥”(Prblic key) ,另一个不告诉任何人,称为"私钥”(Private key)。这两个密钥是互补的,也就是说用公钥加密的密文可以用私钥解密,反过来也一样。

    公钥的传输:要启动安全通讯,通信两端必须首先得到相同的共享密钥(主密钥),但共享密钥不能通过网络相互发送,因为这种做法极易泄密。Diffie-Hellman算法是用于密钥交换的最早最安全的算法之一。

    RSA算法:RSA算法是基于大数难于分解的原理。不但可以用于认证,也可以用于密钥传输,例子可以参考RSAUtil.java文件。那么用户A和B如何利用RSA算法来传输密钥呢?

    1:A产生一个密钥K,用B的公钥加密K,然后将得到的密文发送给B。

    2:B用自己的私钥解密收到的密钥,就可以得到密钥。

    DH算法:DH算法的出现就是用来进行密钥传输的。DH算法是基于离散对数实现的。DH算法的基本工作原理是:通信双方公开或半公开交换一些准备用来生成密钥的"材料数据",在彼此交换过密钥生成"材料"后,两端可以各自生成出完全一样的共享密钥。在任何时候,双方都绝不交换真正的密钥。通信双方交换的密钥生成"材料",长度不等,"材料"长度越长,所生成的密钥强度也就越高,密钥破译就越困难。除进行密钥交换外,IPSec还使用DH算法生成所有其他加密密钥。

    在通信前,用户A和B双方约定2个大整数n和g,其中1<g<n,这两个整数可以公开

    1. A随机产生一个大整数a,然后计算Ka=ga mod n(a需要保密)

    2. B随机产生一个大整数b,然后计算Kb=gb mod n(b需要保密)

    3. A把Ka发送给B,B把Kb发送给A

    4. A计算K=Kba mod n

    5. B计算K=Kab mod n

    由于Kba mod n= (gb mod n)a mod n= (ga mod n)b mod n,因此可以保证双方得到的K是相同的,K即是共享的密钥。可以参考JCE文档中的DH 3 party的例子。

    实际的一个用DH算法传递DES私钥的JAVA例子参看“JAVA上加密算法的实现用例”一文中的末一个例子,过程如下:假设A和B通信(JCE中只支持DH算法作为传递私钥的算法)

    A利用KeyPairGenerator类生成一个钥对类KeyPair并可通过generatePublic方法产生公钥PublicKey(puA)和getPrivate方法私钥PrivateKey(prA)。A把puA发给B。B用类X509EncodedKeySpec解码,然后利用KeyFactory类生成puA,转换成DHPublicKey类利用其getParams方法取得参数类DHParameterSpec,B再利用此参数类,通过KeyPairGenerator类的initialize方法生成KeyPairGenerator实例从而用generateKeyPair方法生成B的KeyPair。进而B生成puB和prB,B把puB发给A。A利用puB和prA作为参数,分别调用KeyAgreement类的init和doPhase方法初始化,然后用generateSecret方法生成各自的DES的密钥SecretKey,此密钥是相同的,即可用Cipher类进行加解密了。

    .NET 通过抽象基类 (System.Security.Crytography.AsymmetricAlgorithm) 提供下列非对称(公钥/私钥)加密算法:
    • DSACryptoServiceProvider

    • RSACryptoServiceProvider

    数字签名
    公钥算法还可用于构成数字签名。数字签名验证发送方的身份(如果您信任发送方的公钥)并帮助保护数据的完整性。

    为了使用公钥加密对消息进行数字签名,小红首先将哈希算法应用于该消息以创建消息摘要。该消息摘要是数据的紧凑且唯一的表示形式。然后,小红用她的私钥加密该消息摘要以创建她的个人签名。在收到消息和签名时,小明使用小红的公钥解密签名以恢复消息摘要,并使用与小红所使用的相同的哈希算法来散列消息。如果小明计算的消息摘要与从小红那里收到的消息摘要完全一致,小明就可以确定该消息来自私钥的持有人,并且数据未被修改过。如果小明相信小红是私钥的持有人,则他知道该消息来自小红。

    请注意,由于发送方的公钥为大家所周知,并且它通常包含在数字签名格式中,因此任何人都可以验证签名。此方法不保守消息的机密;若要使消息保密,还必须对消息进行加密。

    .NET Framework 提供以下实现数字签名算法的类:

    DSACryptoServiceProvider
    RSACryptoServiceProvider

    CA证书 Public Key Certificates(参考这里)

    A public key certificate provides a safe way for an entity to pass on its public key to be used in asymmetric cryptography. The public key certificate avoids the following situation: if Charlie creates his own public key and private key, he can claim that he is Alice and send his public key to Bob. Bob will be able to communicate with Charlie, but Bob will think that he is sending his data to Alice.

    A public key certificate can be thought of as the digital equivalent of a passport. It is issued by a trusted organization and provides identification for the bearer. A trusted organization that issues public key certificates is known as a certificate authority (CA). The CA can be likened to a notary public. To obtain a certificate from a CA, one must provide proof of identity. Once the CA is confident that the applicant represents the organization it says it represents, the CA signs the certificate attesting to the validity of the information contained within the certificate.

    A public key certificate contains several fields, including:

    Issuer - The issuer is the CA that issued the certificate. If a user trusts the CA that issues a certificate, and if the certificate is valid, the user can trust the certificate.

    Period of validity - A certificate has an expiration date, and this date is one piece of information that should be checked when verifying the validity of a certificate.

    Subject - The subject field includes information about the entity that the certificate represents.

    Subject’s public key - The primary piece of information that the certificate provides is the subject’s public key. All the other fields are provided to ensure the validity of this key.

    Signature - The certificate is digitally signed by the CA that issued the certificate. The signature is created using the CA’s private key and ensures the validity of the certificate. Because only the certificate is signed, not the data sent in the SSL transaction, SSL does not provide for non-repudiation.
    If Bob only accepts Alice’s public key as valid when she sends it in a public key certificate, Bob will not be fooled into sending secret information to Charlie when Charlie masquerades as Alice.

    Multiple certificates may be linked in a certificate chain. When a certificate chain is used, the first certificate is always that of the sender. The next is the certificate of the entity that issued the sender’s certificate. If there are more certificates in the chain, each is that of the authority that issued the previous certificate. The final certificate in the chain is the certificate for a root CA. A root CA is a public certificate authority that is widely trusted. Information for several root CAs is typically stored in the client’s Internet browser. This information includes the CA’s public key. Well-known CAs include VeriSign, Entrust, and GTE CyberTrust.

    哈希值
    哈希算法将任意长度的二进制值映射为固定长度的较小二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希计算都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的。

    消息身份验证代码 (MAC) 哈希函数通常与数字签名一起用于对数据进行签名,而消息检测代码 (MDC) 哈希函数则用于数据完整性。

    双方(小红和小明)可按下面的方式使用哈希函数来确保数据的完整性。如果小红对小明编写一条消息并创建该消息的哈希,则小明可以在稍后散列该消息并将他的哈希与原始哈希进行比较。如果两个哈希值相同,则该消息没有被更改;如果值不相同,则该消息在小红编写它之后已被更改。为了使此系统发挥作用,小红必须对除小明外的所有人保密原始的哈希值。

    When sending encrypted data, SSL typically uses a cryptographic hash function to ensure data integrity. The hash function prevents Charlie from tampering with data that Alice sends to Bob.

    A cryptographic hash function is similar to a checksum. The main difference is that while a checksum is designed to detect accidental alterations in data, a cryptographic hash function is designed to detect deliberate alterations. When data is processed by a cryptographic hash function, a small string of bits, known as a hash, is generated. The slightest change to the message typically makes a large change in the resulting hash. A cryptographic hash function does not require a cryptographic key. Two hash functions often used with SSL are Message Digest 5 (MD5) and Secure Hash Algorithm (SHA). SHA was proposed by the US National Institute of Science and Technology (NIST).

    Message Authentication Code: A message authentication code (MAC) is similar to a cryptographic hash, except that it is based on a secret key. When secret key information is included with the data that is processed by a cryptographic hash function, the resulting hash is known as an HMAC.
    If Alice wants to be sure that Charlie does not tamper with her message to Bob, she can calculate an HMAC for her message and append the HMAC to her original message. She can then encrypt the message plus the HMAC using a secret key she shares with Bob. When Bob decrypts the message and calculates the HMAC, he will be able to tell if the message was modified in transit. With SSL, an HMAC is used with the transmission of secure data.

    .NET Framework 提供以下实现数字签名算法的类:

    HMACSHA1
    MACTripleDES
    MD5CryptoServiceProvider
    SHA1Managed
    SHA256Managed
    SHA384Managed
    SHA512Managed

    摘要函数(MD2、MD4和MD5,还有SHA1等算法(产生一个20字节的二进制数组))
    摘要是一种防止改动的方法,其中用到的函数叫摘要函数。这些函数的输入可以是任意大小的消息,而输出是一个固定长度的摘要。摘要有这样一个性质,如果改变了输入消息中的任何东西,甚至只有一位,输出的摘要将会发生不可预测的改变,也就是说输入消息的每一位对输出摘要都有影响。总之,摘要算法从给定的文本块中产生一个数字签名(fingerprint或message digest),数字签名可以用于防止有人从一个签名上获取文本信息或改变文本信息内容。摘要算法的数字签名原理在很多加密算法中都被使用,如S/KEY和PGP(pretty good privacy)。

    现在流行的摘要函数有MD4和MD5。MD2摘要算法的设计是出于下面的考虑:利用32位RISC结构来最大其吞吐量,而不需要大量的替换表(substitution table)。MD4算法将消息的给予对长度作为输入,产生一个128位的"指纹"或"消息化"。要产生两个具有相同消息化的文字块或者产生任何具有预先给定"指纹"的消息,都被认为在计算上是不可能的。MD5摘要算法是个数据认证标准。MD5的设计思想是要找出速度更快但更不安全的MD4中潜在的不安全,MD5的设计者通过使MD5在计算上慢下来,以及对这些计算做了一些基础性的改动来解决这个问题。MD5在RFC1321中给出文档描述,是MD4算法的一个扩展。美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5是为代表。HMAC-MD5算法(消息摘要5)基于RFC1321。MD5对MD4做了改进,计算速度比MD4稍慢,但安全性能得到了进一步改善。MD5在计算中使用了64个32位常数,最终生成一个128位的完整性检查和。

    HMAC-SHA算法:安全Hash算法定义在NIST FIPS 180-1,其算法以MD5为原型。SHA在计算中使用了79个32位常数,最终产生一个160位完整性检查和。SHA检查和长度比MD5更长,因此安全性也更高。

    随机数生成
    随机数生成是许多加密操作不可分割的组成部分。例如,加密密钥需要尽可能地随机,以便使生成的密钥很难再现。加密随机数生成器必须生成无法以计算方法推算出(低于 p < .05 的概率)的输出;即,任何推算下一个输出位的方法不得比随机猜测具有更高的成功概率。.NET Framework 中的类使用随机数生成器生成加密密钥。

    在商务领域的应用

    许多人都知道NETSCAPE公司是Internet商业中领先技术的提供者,该公司提供了一种基于RSA和保密密钥的应用于因特网的技术,被称为安全插座层(Secure Sockets Layer,SSL)。

    也许很多人知道Socket,它是一个编程界面,并不提供任何安全措施,而SSL不但提供编程界面,而且向上提供一种安全的服务,SSL3.0现在已经应用到了服务器和浏览器上,SSL2.0则只能应用于服务器端。

    SSL3.0用一种电子证书(electric certificate)来实行身份进行验证后,双方就可以用保密密钥进行安全的会话了。它同时使用“对称”和“非对称”加密方法,在客户与电子商务的服务器进行沟通的过程中,客户会产生一个Session Key,然后客户用服务器端的公钥将Session Key进行加密,再传给服务器端,在双方都知道Session Key后,传输的数据都是以Session Key进行加密与解密的,但服务器端发给用户的公钥必需先向有关发证机关申请,以得到公证。

    基于SSL3.0提供的安全保障,用户就可以自由订购商品并且给出信用卡号了,也可以在网上和合作伙伴交流商业信息并且让供应商把订单和收货单从网上发过来,这样可以节省大量的纸张,为公司节省大量的电话、传真费用。在过去,电子信息交换(Electric Data Interchange,EDI)、信息交易(information transaction)和金融交易(financial transaction)都是在专用网络上完成的,使用专用网的费用大大高于互联网。正是这样巨大的诱惑,才使人们开始发展因特网上的电子商务,但不要忘记数据加密。

    SSL(Secure Sockets Layer) or TSL (Transport Layer Security)

    Secure Sockets Layer (SSL) is the most widely used protocol for implementing cryptography on the Web. SSL uses a combination of cryptographic processes to provide secure communication over a network. This section provides an introduction to SSL and the cryptographic processes it uses.

    SSL provides a secure enhancement to the standard TCP/IP sockets protocol used for Internet communications. As shown in the “TCP/IP Protocol Stack With SSL” figure below, the secure sockets layer is added between the transport layer and the application layer in the standard TCP/IP protocol stack. The application most commonly used with SSL is Hypertext Transfer Protocol (HTTP), the protocol for Internet Web pages.

    SSL was developed by Netscape in 1994, and with input from the Internet community, has evolved to become a standard. It is now under the control of the international standards organization, the Internet Engineering Task Force (IETF). The IETF has renamed SSL to Transport Layer Security (TLS), and released the first specification, version 1.0, in January 1999. TLS 1.0 is a modest upgrade to the most recent version of SSL, version 3.0. The differences between SSL 3.0 and TLS 1.0 are minor.

    One of the reasons SSL is effective is that it uses several different cryptographic processes. SSL uses public key cryptography to provide authentication, and secret key cryptography and digital signatures to provide for privacy and data integrity. Before you can understand SSL, it is helpful to understand these cryptographic processes.

    常用算法比较

    数据加密标准(DES)是一个古老的对称密钥加密算法,目前已经不再使用。它不是一个很安全的算法。

    三重DES(Triple-DES)仍然是很安全的,但是也只是在别无他法的情况下的一个较好的选择。显然高级加密标准(AES)是一个更好的加密算法,NIST用AES代替Triple-DES作为他们的标准(下面有更详细的讨论)。其他较好的算法包括另外两个AES的变种算法Twofish和Serpent-也称为CAST-128,它是效率和安全的完美结合。这几个算法不仅比DES更安全,而且也比DES的速度更快。为什么要使用一些又慢又不安全的算法呢

    SHA1是一个哈希函数,而不是一个加密函数。作为一个哈希函数,SHA1还是相当优秀的,但是还需要几年的发展才能用作加密算法。如果你正在设计一个新系统,那么谨记你可能会在若干年后用SHA1代替目前的算法。我再重复一遍:只是可能。

    RSA是一个公开密钥加密算法。RSA的密钥长度一般为2048-4096位。如果你现在的系统使用的是1024位的公开密钥,也没有必要担心,但是你可以加长密钥长度来达到更好的加密效果。

    高级加密标准(AES)是一个用来代替数据加密标准(DES)的算法。目前使用的一般为128,196和256位密钥,这三种密钥都是相当安全的。而且美国政府也是这样认为的。他们批准将128位密钥的AES算法用于一般数据加密,196位和256位密钥的AES算法用于秘密数据和绝密数据的加密。

    DESX是DES的一个改进版本。DESX的原理是利用一个随机的二进制数与加密前的数据以及解密后的数据异或。虽然也有人批评这种算法,但是与DES相比DESX确实更安全,不过DESX在许多情况下并不适用。我曾经处理过一个硬件支持DES的系统,由于有些环节不能容忍三重DES的慢速,我们在这些地方使用了DESX来代替DES。然而,这是一个非常特殊的情况。如果你需要使用DESX,理由显而易见(可能和我不得不使用DESX的原因类似)。但我建议你使用AES或者上面我提到的一些算法。

    RC4是一种常用于SSL连接的数据流加密算法。它已经出现很多年了,而且有很多已知和可能的缺陷,因此在一些新的工程中不要使用它。如果你目前正在使用它而且可以轻易的卸载它,那么情况也不是很坏。不过,我怀疑如果你现在正在使用它,你不可能轻易的卸载它。如果不能将它从系统中轻易的卸载,那么你还是考虑今后怎样升级它,但是不要感到很惊慌。我不会拒绝在一个使用RC4算法来加密SSL连接的网站购买东西,但是如果我现在要新建一个系统,那么我会考虑使用其他的算法,例如:AES。

    对于下面两个算法MD5-RSA和SHA1-DSA,他们是用于数字签名的。但是不要使用MD5,因为它有很多缺陷。很多年前大家就知道MD5中存在漏洞,不过直到今年夏天才破解出来。我们可以将SHA1和RSA或DSA配合在一起使用,目前DSA的密钥位数高达1024位,这个密钥位数已经足够长了,因此不需要担心安全问题。然而,如果NIST实现了更长的密钥位数当然更好。

    X.509证书是一个数据结构,常用于规定比特和字节的顺序,它本身不是一个密码系统。它通常包含一个RSA密钥,也可能包含一个DSA密钥。但是X.509证书内部以及证书本身并不是加密技术。

    转自:https://www.cnblogs.com/lvdongjie/p/4241107.html

    展开全文
  • 网络安全原理加密算法

    千次阅读 2017-09-19 23:17:47
    一、重要的数据在互联网中进行传输的时候必须保证数据的安全性,从以下四个方面来做: 1.保证数据是从真正的源发送的,而不是其他人(源认证) 2.保证数据在传输的过程中没有被篡改过(数据...2.对称算法原理、特征、算

    一、重要的数据在互联网中进行传输的时候必须保证数据的安全性,从以下四个方面来做:

    1.保证数据是从真正的源发送的,而不是其他人(源认证)

    2.保证数据在传输的过程中没有被篡改过(数据的完整性)

    3.保证数据在传输的过程中别人看不懂(数据的私密性)

    4.保证数据的不可否认性(不可否认性)


    1.1.1加密算法

    Tags:

    1.加密算法分类

    2.对称算法<原理、特征、算法>

    3.非对称算法<原理、特征、算法>

     

     

    1.加密算法概述

    用于对用户数据进行加密,常用算法有DES3DESAESRSADH算法。

    根据密钥特征,分为对称非对称算法。

     

    2.对称算法

    1)原理

    使用一把密钥来对信息提供安全的保护。只有一个密钥,即用来加密,也用来解密

    2)特征

    加密速度快:

    数据通信中,运用对称加密可以让数据的传输基本感受不到加解密

     

    紧凑性:

    原文在经过加密后得到的密文,不会比原文大太多

     

    安全性:

    在没有得到密钥的情况下,对称加密不会被很快破解

     

    3)算法

    DESData Encryption Standard数据加密标准

    3DESTriple DES三重数据加密标准算法

    AES: Advanced Encryption Standard高级加密标准

     

    3DES:有三个密匙,用第一个密匙加密,用第二个密匙解密,用第三个密匙加密

     

    缺点:密钥的保管和分发是严重的问题

    收发双方都需要同一个密钥进行加密和解密,密钥的传递没有好的办法


    3.非对称算法

    1)原理

    公钥+私钥,公钥大家都知道,私钥只有本地知道,

    公钥加密私钥解,私钥加密公钥解。加解密双方各有

    一对密钥对。发给谁就用谁的公钥!

    2)特征

    速度慢

    密文不紧凑

    通常只用于数字签名,或加密一些小文件

    3)算法

    RSA

    DH

     

    缺点:

    密文不紧凑:不适用于大量数据的应用

    4、对称+非对称算法

    对称算法和非对称算法是互补的

    安全

    紧凑

    速度快

    密钥管理方便

    支持数字签名

    支持不可否认性

     

    明文------对称算法(密钥A--------密文

    密钥A--------使用接收方的公钥加密------------加密后的密钥A

     

    密文+加密后的密钥A--------------->接收者

     

    加密后的密钥A----------自己的私钥------------密钥A

    密文------------密钥A--------------明文

     

    使用对称加密算法和非对称加密算法的结合,实现了数据的加密和密钥的传输

    基本上,通信的过程中,都是使用对称算法对数据进行加密,使用非对称算法对对称算法的密钥进行加密


    1.1.2 哈希算法

    1、哈希算法(hash)分类

     

    MD5SHA,用于实现数据完整性校验。

     

    2、哈希算法原理

    Hash : 散列 ,散列函数的主要任务是验证数据的完整性。通过散列函数计算得到的结果叫做散列值,这个散列值也常常被称为数据的指纹(Fingerprint)

     

    把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。

     

    特征:

    1、雪崩效应:

    明文有任何的修改,hash全局改变

    2、不可逆向:

    无法根据哈希值得到明文

    3、固定输出:128bit160bit

    10M----MD5---->128bit散列值

    1000M-----MD5----->128bit散列值

    10000G-----SHA----->160bit散列值

    4、唯一性:

    相同的原文通过哈希计算永远得到一样的散列值


    1.1.3数字签名

    1.数字签名组件

    HASH算法

    非对称加密算法

     

    2.数字签名原理

    发送方使用自己的私钥进行加密,接收方使用发送方的公钥进行解密的过程

    (私钥只能由发送方保管,其他人不会有)

    解决了数据在传输过程中的源认证问题



    3.数字签名功能

     

    不可否认性

      用签名者的公钥可以解开HMAC,证明数据是签名者发送的

    数据完整性

      HASH值相同证明明文没有被篡改

    缺点:无法对发送方进行验证(欺骗者可以假装成发送方)


    1.1.5数字证书

    1、数字证书组成

    对等体公钥

    对等体姓名,组织,地址

    证书有效期

    CA的数字签名

     

    注意:数字证书仅仅只是解决“这个公钥的持有者到底是谁”的问题!

     

    2PKI架构

    CA

    RA

    数字证书

    对等体

    分发机制


    1.仅仅只有一个受信任的介绍者(证书颁发机构)

    2.CA签署每一个人的公钥

    3.每个人都拥有CA的公钥



    展开全文
  • 通信加密原理

    千次阅读 2019-08-04 11:02:03
    消息通信时都需要加密,如果不加密,在请求和响应的过程中,如果消息中途被黑客劫持或篡改后果不堪设想。如图所示: 1976年以前,所有的加密方法都是同一种模式:对称加密 1、客户端C选择某一种加密规则K,对...

    一、历史:

            消息通信时都需要加密,如果不加密,在请求和响应的过程中,如果消息中途被黑客劫持或篡改后果不堪设想。如图所示:

            1976年以前,所有的加密方法都是同一种模式:对称加密

                    1、客户端C选择某一种加密规则K,对信息进行加密,然后将加密的信息传递给服务端S;

                    2、服务端S接收到加密的信息后,使用同一种规则K,对加密的信息进行解密。

            由于加密和解密使用同样规则(简称"密钥"),所以被称为"对称加密算法"(Symmetric-key algorithm)。这种加密模式有一个最大弱点:客户端必须把加密规则K告诉服务端,否则无法解密。传递和保存这个密钥K,就成了最头疼的问题,因为密钥K在传输过程中一旦被泄露,就可以被黑客解密所有的信息。

           现实情况更复杂,一般服务端会有多个客户端,如果服务端和这几个客户端都是用的同一个加密规则,也就是说虽然加密了,但所有人都知道是怎么加密的,那加密是没有意义的。所以一般不会只用同一个密钥,而是不同的客户端采用不同的密钥加解密。但这个密钥也只能使用非对称加密算法加密后传递,因为如果使用对称加密算法加密,永远都需要传递加密规则。

            1976年,两位美国计算机学家Whitfield Diffie 和 Martin Hellman,提出了一种崭新构思,可以在不直接传递密钥的情况下,完成解密。这被称为"Diffie-Hellman密钥交换算法"。

            这个算法启发了其他科学家。人们认识到,加密和解密可以使用不同的规则,只要这两种规则之间存在某种对应关系即可,这样就避免了直接传递密钥。这种新的加密模式被称为"非对称加密算法",有如下特点:

            1、密钥是两个,一个是公钥,一个是私钥;

            2、公钥是对任何人开放的,但私钥只有服务端才知道;

            3、公钥加密的信息,不能被公钥解密,只能被私钥解密;

            1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。从那时直到现在,RSA算法一直是最广为使用的"非对称加密算法"。毫不夸张地说,只要有计算机网络的地方,就有RSA算法。

            这种算法非常可靠,密钥越长,它就越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。如果想深入了解RSA算法细节请移步阮一峰大神的博客:http://www.ruanyifeng.com/blog/,本文参考目录有他具体对RSA算法的实现细节的文章地址。

            如果公钥加密的信息只有私钥解得开,公钥虽然谁都可以获取,但私钥不用传递也就不会泄漏,通信就是安全的。这样虽然只保证了单向的客户端传递给服务端的信息被安全加密,但已经足够了。唯一的问题是如何保证服务端传递给客户端的公钥就是服务端真正的公钥而不是被篡改的公钥呢?众所周知,在https中可以通过CA来进行公证,而ssh的公钥和私钥都是服务端自己生成的,没法公证,只能通过客户端自己对公钥进行确认。

    二、对称加密算法

            也叫私钥加密算法,是最快速、最简单的一种加密方式。加密(encryption)与解密(decryption)用的是同样的密钥(secret key),对称加密通常使用相对较小的密钥,一般小于256 bit。因为密钥越大,加密越强,但加密与解密的过程越慢。

            缺点是密钥的管理与分配,如何把密钥发送到需要解密你的消息的人的手里是一个问题。在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。现实中通常的做法是将对称加密的密钥进行非对称加密,然后传送给需要它的人。

            常见的算法有:DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法。

    三、非对称加密算法

            也叫公钥加密算法,虽然非对称加密很安全,但是和对称加密比起来,它非常的慢,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去,唯一要注意的是如何确保公钥没有被篡改。

            常见的算法有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。

     

    参考:http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html

               http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html

    展开全文
  • AES加密原理

    千次阅读 2017-04-05 16:12:35
     美国国家标准技术研究所在2001年发布了高级加密标准(AES)。AES是一个对称分组密码算法,旨在取代DES成为广泛使用的标准。   根据使用的密码长度,AES最常见的有3种方案,用以适应不同的场景要求,分别是AES-...
  • 网络基本原理加密

    千次阅读 2016-03-01 14:45:45
    base64编码加密一种简单的加密方式,本质是将任意的二进制数据编码成字符串,在网络上传输.//终端命令 base64编码 base64 xx.png -o abc.txt 编码文件 echo -n "Man" | base64 编码字符串 base64解码 base64 abc.txt ...
  • 公钥私钥加密原理

    千次阅读 2018-07-26 11:12:00
    加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。加密过程需要用到公钥。 解密:通过解密算法和私钥对密文进行解密,得到明文。解密过程需要用到解密算法和私钥。注意,由公钥加密的内容,只能由...
  • PGP加密原理

    千次阅读 2019-08-27 10:07:50
    PGP是一个基于RSA公匙加密体系的邮件加密软件。可以用它对你的邮件保密以防止非授权者阅读,它还能对你的邮件加上数字签名从而使收信人可以确信邮件是你发来的。它让你可以安全地和你从未见过的人们通讯,事先并不...
  • iOS app加密&探究爱加密iOS app加密原理。做安卓开发的一定都了解有爱加密安卓加密技术以及原理,不知道的我这里重新讲解下:  常见的安卓加密方法有:伪加密、混淆保护、运行时验证、爱加密apk加密四个方法。在...
  • 非对称加密算法--RSA加密原理

    万次阅读 多人点赞 2018-12-24 00:33:58
    密码学是在编码与破译的斗争实践中逐步发展起来的,并随着...在中国历史上最早的加密算法的记载出自于周朝兵书《六韬.龙韬》中的《阴符》和《阴书》。在遥远的西方,在希罗多德(Herodotus)的《历史》中记载了公元前...
  • 加密是Android4.2.x系统发布前的加密方式之一,通过java代码对APK(压缩文件)进行伪加密,其修改原理是修改连续4位字节标记为”P K 01 02”的后第5位字节,奇数表示不加密偶数表示加密。 虽然伪加密可以起到...
  • HTTPS怎么加密_HTTPS加密原理与算法

    千次阅读 2019-08-27 17:35:43
    之前我们使用的http协议是明文传输的,不会进行加密的,这样就很容易泄露用户的隐私安全,因此使用http协议...那么HTTPS是如何加密的,工作原理是什么呢? 咱们先来了解下什么是TLS/SSL? 俗的讲,TLS、SSL其实是...
  • base64加密原理

    千次阅读 2017-08-21 23:29:27
    今天面试被问到以前的项目有没有用到网络传输加密,当时脑袋抽风没想起来直接说了没有。回来后看书看到了base64几个字,顿时醒悟了过来。 Shit!明明最近的项目就用到了base64方法加密的,只是做的比较久远了,...
  • 文件传输加密原理

    千次阅读 2017-11-20 11:36:00
    9.5.2文件传输加密原理 文件传输加密的目的是保护文件不被非法用户打开,防止别人查看文件内容。现在网络还存在许多不安全因素。在发送电子邮件或进行文件传输时,或许有很多人在别处密切监视着,特别是对一些著名...
  • 浅析SSL SSH加密原理

    千次阅读 2018-01-20 00:19:48
    前言 本文浅析了SSL和SSH加密原理,分为加密基本知识和SSL/SSH协议内容两部分。本文不涉及具体非对称加密算法。同时由于资料的零碎,虽然我尽量让文章有结构,但也难免有地方不太完美,
  • ca证书原理以及加密原理

    千次阅读 多人点赞 2018-10-23 10:12:20
    文中首先解释了加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的出现所起的作用。接着对数字证书做一个详细的解释,并讨论一下windows中数字证书的管理,最后...
  • 文件夹加密狗的加密原理与解密

    千次阅读 2009-06-01 20:58:00
    现在说一下“加密原理,此“加密”软件在要加密的文件夹里面建立了一个名字为DirRecycler的文件夹,这个文件夹被设置成系统和隐藏属性了,而且被伪装成了回收站,所以我们在正常情况下看不见,但只要我们用RAR解压...
  • 数据加密原理详解

    千次阅读 2019-11-29 11:05:36
    数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文。它的核心是密码学。 数据加密仍是计算机系统对信息进行保护的一种最可靠的办法。...
  • AES 加密算法的原理详解

    万次阅读 多人点赞 2018-07-23 22:18:49
    本教程摘选自https://blog.csdn.net/qq_28205153/article/details/55798628的原理部分。 AES简介 高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的...
  • RC4加密原理步骤

    千次阅读 2019-03-27 10:10:10
    RC4 1.简介 在密码学中,RC4(来自 Rivest Cipher 4 的缩写)是一种流加密算法,密钥长度可变。...2.原理及步骤 (图片转载地址:https://www.cnblogs.com/block2016/p/5601925.html) 1>先初...
  • 加密算法(一)——加密原理

    千次阅读 2009-10-16 14:55:00
    加密提高终端和网络通讯的物理安全,有三种方法加密传输数据: * 链接加密:在网络节点间加密,在节点间传输加密,传送到节点后解密,不同节点对间用不同密码. * 节点加密:与链接加密类似,不同的只是当数据在节点间传送时,...
  • ssh的加密原理与基于密钥的登录

    千次阅读 2018-03-12 13:34:13
     登录方式主要有两种:1、基于用户密码的登录方式: 加密原理:当服务器知道用户请求登录时,服务器会把自己的公钥发给用户,ssh会将服务器的公钥存放在客户端的~/.ssh/known_hosts文件下,用户会根据服务器给它发...
  • 加密工作原理

    万次阅读 2017-09-21 07:22:39
     随后就有了加密传输的理念及相应的技术了,对原始的明文数据进行加密加密后生成的数据称之为“密文”。密文与明文最大的区别就是打乱了原来明文数据中字符的顺序,甚至生成一堆非字符信息(通常称之为“乱码”
  • base64加密原理解析

    千次阅读 2018-07-23 17:04:52
    开发者对Base64编码肯定很熟悉,是否对它有很清晰的认识就不一定了。实际 上Base64已经简单到不能再简单了,如果对它的理解还是模棱两可实在不应该。...因为有些网络传送渠道并不支持所有的字节,例...
  • https 的加密原理

    千次阅读 2018-06-05 20:58:41
    http协议是明文传输,传输数据过程中...对称加密甲方选择某一种加密规则,对信息进行加密乙方使用同一种规则,对信息进行解密。(3des)特点 :速度快 约定秘钥不安全2.非对称加密1977年,三位数学家Rivest、Shamir 和...
  • 12.1 数据加密原理与实现方法无论是网络传输中使用的安全传输协议,还是安全手段或安全措施,它们都需要使用加密算法和相应的解密算法。不了解与此相关的加密原理和算法,就很难深刻地认识各种安全传输协议,以及...
  • 对称加密和不对称加密原理

    千次阅读 2012-04-05 11:30:23
    私钥加密(对称加密 symmetric cryptography):私钥加密算法使用单个私钥来加密和解密数据。由于具有密钥的任意一方都可以使用该密钥解密数据,因此必须保护密钥不被未经授权的代理得到。私钥加密又称为对称加密,...
  • 网络加密

    千次阅读 2015-06-07 22:38:39
    Base64参考网站:http://zh.wikipedia.org/wiki/Base64简介是网络上使用最广泛的编码系统,能够将任何二进制数据,转换成... Base 64 是所有现代加密算法的基础算法 终端命令# 将 10.jpg 进行 base64 编码,生成 10.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 114,978
精华内容 45,991
关键字:

网络加密原理