6. 消息认证码--->保证数据的完整性
go
"消息认证码 --- 消息被正确传送了吗?"
6.1 什么是消息认证码
-
Alice 和 Bob 的故事
像以前一样,我们还是从一个Alice和Bob的故事开始讲起。不过,这一次Alice和Bob分别是两家银行,Alice银行通过网络向Bob银行发送了一条汇款请求,Bob银行收到的请求内容是:
从账户 A − 5374 向账户 B − 6671 汇款 1000 万元 从账户A-5374 向账户B-6671汇款1000万元 从账户A−5374向账户B−6671汇款1000万元
当然,Bob银行所收到的汇款请求内容必须与Alice银行所发送的内容是完全一致的。如果主动攻击者Mallory在中途将Alice银行发送的汇款请求进行了篡改,那么Bob银行就必须要能够识别出这种篡改,否则如果Mallory将收款账户改成了自己的账户,那么1000万元就会被盗走。
话说回来,这条汇款请求到底是不是Alice银行发送的呢?有可能Alice银行根本就没有发送过汇款请求,而是由主动攻击者Mallory伪装成Alice银行发送的。如果汇款请求不是来自Alice银行,那么就绝对不能执行汇款。
现在我们需要关注的问题是汇款请求(消息)的 "完整性" 和 "认证" 这两个性质。
消息的完整性(integrity), 指的是"消息没有被篡改"这一性质,完整性也叫一致性。如果能够确认汇款请求的内容与Alice银行所发出的内容完全一致,就相当于是确认了消息的完整性,也就意味着消息没有被篡改。
消息的认证(authentication)指的是"消息来自正确的发送者"这一性质。如果能够确认汇款请求确实来自Alice银行,就相当于对消息进行了认证,也就意味着消息不是其他人伪装成发送者所发出的。
通过使用本章中要介绍的消息认证码,我们就可以同时识别出篡改和伪装,也就是既可以确认消息的完整性,也可以进行认证。
-
什么是消息认证码
消息认证码(message authentication code)是一种确认完整性并进行认证的技术,取三个单词的首字母,简称为MAC。
消息认证码的输入包括任意长度的消息和一个发送者与接收者之间共享的密钥 ,它可以输出固定长度的数据,这个数据称为MAC值。
根据任意长度的消息输出固定长度的数据,这一点和单向散列函数很类似。但是单向散列函数中计算散列值时不需要密钥,而消息认证码中则需要使用发送者与接收者之间共享的密钥。
要计算MAC必须持有共享密钥,没有共享密钥的人就无法计算MAC值,消息认证码正是利用这一性质来完成认证的。此外,和单向散列函数的散列值一样,哪怕消息中发生1比特的变化,MAC值也会产生变化,消息认证码正是利用这一性质来确认完整性的。
消息认证码有很多种实现方法,大家可以暂且这样理解:消息认证码是一种与密钥相关联的单向散列函数。
**单向散列函数与消息认证码的比较**
6.2 消息认证码的使用步骤
我们还是以Alice银行和Bob银行的故事为例,来讲解一下消息认证码的使用步骤:
- 发送者Alice与接收者Bob事先共享密钥。
- 发送者Alice根据汇款请求消息计算MAC值(使用共享密钥)。
- 发送者Alice将汇款请求消息和MAC值两者发送给接收者Bob。
- 接收者Bob根据接收到的汇款请求消息计算MAC值(使用共享密钥)。
- 接收者Bob将自己计算的MAC值与从Alice处收到的MAC值进行对比。
- 如果两个MAC值一致,则接收者Bob就可以断定汇款请求的确来自Alice(认证成功);如果不一致,则可以断定消息不是来自Alice(认证失败)。
-
思考改进方案?
从哈希函数入手
需要将要发送的数据进行哈希运算, 将哈希值和原始数据一并发送
需要在进行哈希运算的时候引入加密的步骤
- 在alice对数据进行哈希运算的时候引入一个秘钥, 让其参与哈希运算, 生成散列值
- bob对数据校验
- bob收到原始和散列值之后,
- 处理原始数据: 通过秘钥和哈希算法对原始数据生成散列值
- 散列值比较: 生成的散列值 和 接收到的散列值进行比对
- bob收到原始和散列值之后,
- 前提条件:
- 在消息认证码生成的一方和校验的一方, 必须有一个秘钥
- 双方约定好使用同样的哈希函数对数据进行运算
- 流程:
- 发送者:
- 发送原始法消息
- 将原始消息生成消息认证码
- ((原始消息) + 秘钥) * 函数函数 = 散列值(消息认证码)
- 将消息认证码发送给对方
- 接收者:
- 接收原始数据
- 接收消息认证码
- 校验:
- ( 接收的消息 + 秘钥 ) * 哈希函数 = 新的散列值
- 通过新的散列值和接收的散列值进行比较
6.3 HMAC
6.3.1 HMAC介绍
HMAC是一种使用单向散列函数来构造消息认证码的方法(RFC2104),其中HMAC的H就是Hash的意思。
HMAC中所使用的单向散列函数并不仅限于一种,任何高强度的单向散列函数都可以被用于HMAC,如果将来设计出新的单向散列函数,也同样可以使用。
使用SHA-I、MD5、RIPEMD-160所构造的HMAC,分别称为HMAC-SHA-1、HMAC-MD5和HMAC-RlPEMD。
**使用HMAC通过秘钥将消息生成消息认证码的内部实现**:
通过上述流程我们可以看出,最后得到的MAC值,一定是一个和输入的消息以及密钥都相关的长度固定的比特序列。
6.3.2 Go中对HMAC的使用
需要使用的包
go
import "crypto/hmac"
使用的函数
go
func New(h func() hash.Hash, key []byte) hash.Hash
func Equal(mac1, mac2 []byte) bool
- hamc.New 函数
- 参数1: 创建一个新的使用哈希校验算法的hash.Hash接口, 如:
- md5.New()
- sha1.New()
- sha256.New()
- 参数2: 使用的秘钥
- 返回值: 通过该哈希接口添加数据和计算消息认证码
- 添加数据: Write(p []byte) (n int, err error)
- 计算结果: Sum(b []byte) []byte
- hmac.Equal 函数
- 比较两个MAC是否相同
生成消息认证码:
go
// 生成消息认证码
func GenerateHMAC(src, key []byte) []byte {
// 1. 创建一个底层采用sha256算法的 hash.Hash 接口
myHmac := hmac.New(sha256.New, key)
// 2. 添加测试数据
myHmac.Write(src)
// 3. 计算结果
result := myHmac.Sum(nil)
return result
}
重要函数说明
-
创建一个底层采用哈希算法的 hash.Hash 接口
go函数对应的包: "crypto/hmac" func New(h func() hash.Hash, key []byte) hash.Hash - 参数 h: 函数指针, 返回值为hash.Hash, 可以使用哈希算法对应的New方法, 如:---->传入地址 -- md5.New -- sha1.New -- sha256.New -- sha256.New224 -- sha512.New -- sha512.New384 - 参数 key: 和数据进行混合运算使用的秘钥 - 返回值: hash.Hash 接口
验证消息认证码
go
func VerifyHMAC(res, src, key []byte) bool {
// 1. 创建一个底层采用sha256算法的 hash.Hash 接口
myHmac := hmac.New(sha256.New, key)
// 2. 添加测试数据
myHmac.Write(src)
// 3. 计算结果
result := myHmac.Sum(nil)
// 4. 比较结果
return hmac.Equal(res, result)
}
重要函数说明:
-
比较两个MAC是否相同
go函数对应的包: "crypto/hmac" func Equal(mac1, mac2 []byte) bool - 参数 mac1, mac2: 通过哈希算法计算得到的消息认证码 - 返回值: 如果mac1==mac2, 返回 true; 否则, 返回 false
测试代码
go
func HMacTest() {
key := []byte("我是消息认证码秘钥")
src := []byte("我是消息认证码测试数据")
result := GenerateHMAC(src, key)
final := VerifyHMAC(result, src, key)
if final {
fmt.Println("消息认证码认证成功!!!")
} else {
fmt.Println("消息认证码认证失败 ......")
}
}
消息认证码生成和校验代码---示例
go
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
)
func main() {
src := []byte("在消息认证码中,需要发送者和接收者之间共享密钥,而这个密钥不能被主动攻击者Mallory获取。" +
"如果这个密钥落入Mallory手中,则Mallory也可以计算出MAC值,从而就能够自由地进行篡改和伪装攻击," +
"这样一来消息认证码就无法发挥作用了。")
key := []byte("helloworld")
hamc1 := GenerateHamc(src, key)
bl := VerifyHamc(src, key, hamc1)
//fmt.Printf("校验结果: %t\n", bl)
fmt.Println(bl)
}
// GenerateHamc 生成消息认证码的函数
// plainText 明文, key秘钥 生成的结果发送给对方进行校验
func GenerateHamc(plainText, key []byte) string {
//创建哈希接口,需要指定使用的哈希算法和秘钥
myhash := hmac.New(sha1.New, key) //生成hash.Hash
//2 给哈希对象添加数据
myhash.Write(plainText)
//计算散列值
hashText := myhash.Sum(nil)
//格式化为16进制形式 转换之后得到的16进制格式字符串
myStr := hex.EncodeToString(hashText) //网络通信必须做
return myStr
}
// VerifyHamc 验证消息认证码 myStr1计算出的散列值
func VerifyHamc(plainText []byte, key []byte, myStr1 string) bool {
//创建哈希接口,需要指定使用的哈希算法和秘钥
myhash := hmac.New(sha1.New, key) //生成hash.Hash
//2 给哈希对象添加数据
myhash.Write(plainText)
//计算散列值
hashText := myhash.Sum(nil)
//格式化为16进制形式 转换之后得到的16进制格式字符串
myStr := hex.EncodeToString(hashText) //网络通信必须做
//两个散列值进行比较
return hmac.Equal([]byte(myStr), []byte(myStr1))
}
6.3 消息认证码的密钥配送问题--->弊端--->使用非对称加密可以解决
在消息认证码中,需要发送者和接收者之间共享密钥,而这个密钥不能被主动攻击者Mallory获取。如果这个密钥落入Mallory手中,则Mallory也可以计算出MAC值,从而就能够自由地进行篡改和伪装攻击,这样一来消息认证码就无法发挥作用了。
发送者和接收者需要共享密钥,这一点和我们介绍的对称加密很相似。实际上,对称加密的密钥配送问题在消息认证码中也同样会发生。关于秘钥的配送后边章节会介绍如何使用非对称加密的方式进行解决。
6.4 消息认证码--->无法解决的问题
假设发送者Alice要向接收者Bob发送消息,如果使用了消息认证码,接收者Bob就能够断定自己收到的消息与发送者Alice所发出的消息是一致的,这是因为消息中的MAC值只有用Alice和Bob之间共享的密钥才能够计算出来,即便主动攻击者Mallory篡改消息,或者伪装成Alice发送消息,Bob也能够识别出消息的篡改和伪装。
但是,消息认证码也不能解决所有的问题,例如"对第三方证明"和"防止否认",这两个问题就无法通过消息认证码来解决。下面我们来逐一解释一下。
6.4.1 对第三方证明--->没有第三方证明
假设Bob在接收了来自Alice的消息之后,想要向第三方验证者Victor证明这条消息的确是Alice发送的,但是用消息认证码无法进行这样的证明,这是为什么呢?
首先,Victor要校验MAC值,就需要知道Alice和Bob之间共享的密钥。
假设Bob相信Victor, 同意将密钥告诉Victor,即便如此,Victor也无法判断这条消息是由Alice发送的,因为Victor可以认为:"即使MAC值是正确的,发送这条消息的人也不一定是Alice,还有可能是Bob。"
能够计算出正确MAC值的人只有Alice和Bob,在他们两个人之间进行通信时,可以断定是对方计算了MAC值,这是因为共享这个密钥的双方之中,有一方就是自己。然而,对于第三方Victor、Alice或Bob却无法证明是对方计算了MAC值,而不是自己。
使用第7章中将要介绍的数字签名就可以实现对第三方的证明。
6.4.2 防止否认-->不能防止别人反悔
假设Bob收到了包含MAC值的消息,这个MAC值是用Alice和Bob共享的密钥计算出来的,因此Bob能够判断这条消息的确来自Alice。
但是,上面我们讲过,Bob无法向验证者Victor证明这一点,也就是说,发送者Alice可以向Victor声称:"我没有向Bob发送过这条消息。"这样的行为就称为否认(repudiation)。
Alice可以说"这条消息是Bob自己编的吧","说不定Bob的密钥被主动攻击者Mallory给盗取了,我的密钥可是妥善保管着呢" 等。说白了,就是Alice和Bob吵起来了。
即便Bob拿MAC值来举证,Victor也无法判断Alice和Bob谁的主张才是正确的,也就是说,用消息认证码无法防止否认(nonrepudiatlon)。
6.5 总结
消息认证码是对消息进行认证并确认其完整性的技术。通过使用发送者和接收者之间共享的密钥,就可以识别出是否存在伪装和篡改行为。
消息认证码可以使用单向散列函数HMAC, 对称加密也可以实现, 这里不再进行介绍。
消息认证码中,由于发送者和接收者共享相同的密钥,因此会产生无法对第三方证明以及无法防止否认等问题。在下一章中,我们将介绍能够解决这些问题的数字签名。
7. 数字签名--->是对我们发送的消息再次进行加密--->使用私钥进行加密
go
"数字签名 --- 消息到底是谁写的"
本章中我们将学习数字签名的相关知识。数字签名是一种将相当于现实世界中的盖章、签字的功能在计算机世界中进行实现的技术。使用数字签名可以识别篡改和伪装,还可以防止否认。
7.1 从消息认证到数字签名
-
消息认证码的局限性
通过使用第6章中介绍的消息认证码,我们可以识别消息是否被篡改或者发送者身份是否被伪装,也就是可以校验消息的完整性,还可以对消息进行认证。然而,比如在出具借条的场景中却无法使用消息认证码,因为消息认证码无法防止否认。
消息认证码之所以无法防止否认,是因为消息认证码需要在发送者Alice和接收者Bob两者之间共享同一个密钥。正是因为密钥是共享的,所以能够使用消息认证码计算出正确MAC值的并不只有发送者Alice,接收者Bob也可以计算出正确的MAC值。由于Alice和Bob双方都能够计算出正确的MAC值,因此对于第三方来说,我们无法证明这条消息的确是由Alice生成的。
-
通过数字签名解决问题
下面请大家开动一下脑筋。假设发送者Alice和接收者Bob不需要共享一个密钥,也就是说,Alice和Bob各自使用不同的密钥。
我们假设Alice使用的密钥是一个只有Alice自己才知道的私钥。当Alice发送消息时,她用私钥生成一个"签名"。相对地,接收者Bob则使用一个和Alice不同的密钥对签名进行验证。使用Bob的密钥无法根据消息生成签名,但是用Bob的密钥却可以对Alice所计算的签名进行验证,也就是说可以知道这个签名是否是通过Alice的密钥计算出来的。如果真有这么一种方法的话,那么不管是识别篡改、伪装还是防止否认就都可以实现了吧 ?
实际上,这种看似很神奇的技术早就已经问世了,这就是数字签名(digital signat.ure)。
7.2 签名的生成和验证
让我们来稍微整理一下。
在数字签名技术中,出现了下面两种行为:
- 生成消息签名的行为
- 验证消息签名的行为
生成消息签名这一行为是由消息的发送者Alice来完成的,也称为**"对消息签名**"。生成签名就是根据消息内容计算数字签名的值,这个行为意味着 "我认可该消息的内容"。
验证数字签名这一行为一般是由消息的接收者Bob来完成的,但也可以由需要验证消息的第三方来完成,这里的第三方我们暂且将其命名为验证者Victor。验证签名就是检查该消息的签名是否真的属于Alice,验证的结果可以是成功或者失败,成功就意味着这个签名是属于Alice的,失败则意味着这个签名不是属于Alice的。
在数字签名中,生成签名和验证签名这两个行为需要使用各自专用的密钥来完成。
Alice使用"签名密钥"来生成消息的签名,而Bob和Victor则使用"验证密钥"来验证消息的签名。数字签名对签名密钥和验证密钥进行了区分,使用验证密钥是无法生成签名的。这一点非常重要。此外,签名密钥只能由签名的人持有,而验证密钥则是任何需要验证签名的人都可以持有。
刚才讲的这部分内容,是不是觉得似曾相识呢?
没错,这就是我们讲过的非对称加密。公钥密码和上面讲的数字签名的结构非常相似。在非对称加密中,密钥分为加密密钥和解密密钥,用加密密钥无法进行解密。此外,解密密钥只能由需要解密的人持有,而加密密钥则是任何需要加密的人都可以持有。你看,数字签名和非对称加密是不是很像呢?
实际上,数字签名和非对称加密有着非常紧密的联系,简而言之,数字签名就是通过将非对称加密 "反过来用" 而实现的。下面我们来将密钥的使用方式总结成一张表:
私钥 | 公钥 | |
---|---|---|
非对称加密 | 接收者解密时使用 | 发送者加密时使用 |
数字签名 | 签名者生成签名时使用 | 验证者验证签名时使用 |
谁持有秘钥? | 个人持有 | 只要需要,任何人都可以持有 |
7.3 非对称加密和数字签名
下面我们再来详细讲一讲非对称加密与数字签名之间的关系。
要实现数字签名,我们可以使用第4章中介绍的非对称加密。非对称加密包括一个由公钥和私钥组成的密钥对,其中公钥用于加密,私钥用于解密。
数字签名中也同样会使用公钥和私钥组成的密钥对,不过这两个密钥的用法和非对称加密是相反的,即用私钥加密相当于生成签名,而用公钥解密则相当于验证签名。请大家通过比较两张图示来理解一下"反过来用"到底是什么样的情形。
那么为什么加密相当于生成签名,而解密相当于验证签名呢?要理解这个问题,我们需要回想一下非对称加密中讲过的知识,即组成密钥对的两个密钥之间存在严密的数学关系,它们是一对无法拆散的伙伴。
用公钥加密所得到的密文,只能用与该公钥配对的私钥才能解密:同样地,用私钥加密所得到的密文,也只能用与该私钥配对的公钥才能解密。也就是说,如果用某个公钥成功解密了密文,那么就能够证明这段密文是用与该公钥配对的私钥进行加密所得到的。
用私钥进行加密这一行为只能由持有私钥的人完成,正是基于这一事实,我们才可以将用私钥加密的密文作为签名来对待。
由于公钥是对外公开的,因此任何人都能够用公钥进行解密,这就产生了一个很大的好处,即任何人都能够对签名进行验证。
- 签名
- 有原始数据对其进行哈希运算 -> 散列值
- 使用非对称加密的私钥对散列值加密 -> 签名
- 将原始数据和签名一并发送给对方
- 验证
- 接收数据
- 原始数据
- 数字签名
- 数字签名, 需要使用公钥解密, 得到散列值
- 对原始数据进行哈希运算得到新的散列值
7.3 数字签名的方法
下面我们来具体介绍两种生成和验证数字签名的方法。
- 直接对消息签名的方法
- 对消息的散列值签名的方法
直接对消息签名的方法比较容易理解,但实际上并不会使用;对消息的散列值签名的方法稍微复杂一点,但实际中我们一般都使用这种方法。
使用直接对消息签名的方法,需要对整个消息进行加密,非常耗时,这是因为非对称加密算法本来就非常慢。那么,我们能不能生成一条很短的数据来代替消息本身呢?这就是单向散列函数。
于是我们不必再对整个消息进行加密(即对消息签名),而是只要先用单向散列函数求出消息的散列值,然后再将散列值进行加密(对散列值签名)就可以了。无论消息有多长,散列值永远都是这么短,因此对其进行加密(签名)是非常轻松的。
(1)Alice用单向散列函数计算消息的散列值。
(2)Alice用自己的私钥对散列值进行加密。
用私钥加密散列值所得到的密文就是Alice对这条散列值的签名,由于只有Alice才持有自己的私钥因此, 除了Alice以外,其他人是无法生成相同的签名(密文)的。
(3)Alice将消息和签名发送给Bob。
(4)Bob用Alice的公钥对收到的签名进行解密。
如果收到的签名确实是用Alice的私钥进行加密而得到的密文(签名),那么用Alice的公钥应该能够正确 解密,解密的结果应该等于消息的散列值。如果收到的签名不是用Alice的私钥进行加密而得到的密文, 那么就无法用Alice的公钥正确解密(解密后得到的数据看起来是随机的)。
(5)Bob将签名解密后得到的散列值与Alice直接发送的消息的散列值进行对比。
如果两者一致,则签名验证成功;如果两者不一致,则签名验证失败。
我们将数字签名中生成签名和验证签名的过程整理成一张时间流程图 。
**Alice对消息的散列值签名, Bob验证签名**
**Alice对消息的散列值签名, Bob验证签名(按时间顺序)**
7.4 通过RSA实现数字签名
前边章节已经介绍过了如何通过自己编写的go代码生成非对称加密算法RSA的公钥和私钥文件, 假设公钥文件的文件名为 public.pem ,私钥文件对应的文件名为 private.pem。
-
使用rsa生成密钥对
- 生成密钥对
- 序列化
- 保存到磁盘文件
-
使用私钥进行数字签名
-
打开磁盘的私钥文件
-
将私钥文件中的内容读出
-
使用pem对数据解码, 得到了pem.Block结构体变量
-
x509将数据解析成私钥结构体 -> 得到了私钥
-
创建一个哈希对象 -> md5/sha1
-
给哈希对象添加数据
-
计算哈希值
-
使用rsa中的函数对散列值签名
gofunc SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) (s []byte, err error) 参数1: rand.Reader 参数2: 非对称加密的私钥 参数3: 使用的哈希算法 crypto.sha1 crypto.md5 参数4: 数据计算之后得到的散列值 返回值: - s: 得到的签名数据 - err: 错误信息
-
-
使用公钥进行签名认证
-
打开公钥文件, 将文件内容读出 - []byte
-
使用pem解码 -> 得到pem.Block结构体变量
-
使用x509对pem.Block中的Bytes变量中的数据进行解析 -> 得到一接口
-
进行类型断言 -> 得到了公钥结构体
-
对原始消息进行哈希运算(和签名使用的哈希算法一致) -> 散列值
- 创建哈希接口
- 添加数据
- 哈希运算
-
签名认证 - rsa中的函数------>签名认证的比较,是在内部做的
gofunc VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error) 参数1: 公钥 参数2: 哈希算法 -> 与签名使用的哈希算法一致 参数3: 将原始数据进行哈希原始得到的散列值 参数4: 签名的字符串 返回值: - nil -> 验证成功 - !=nil -> 失败
-
7.4.1 生成数字签名
go
func SignatureRSA() ([]byte, error) {
// 1. 从秘钥文件中读生成的秘钥内容
fp, err := os.Open("private.pem")
if err != nil {
return nil, errors.New("打开私钥文件 - private.pem 失败!!!")
}
// 2. 读文件内容
fileInfo, _ := fp.Stat()
all := make([]byte, fileInfo.Size())
_, err = fp.Read(all)
if err != nil {
return nil, errors.New("读文件内容失败!!!")
}
fmt.Println("文件内容: ", string(all))
// 3. 关闭文件
defer fp.Close()
// 4. 将数据解析成pem格式的数据块
block, _ := pem.Decode(all)
// 5. 解析pem数据块, 得到私钥
priv_Key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, errors.New("解析私钥失败!!!")
}
// 待签名数据
myData := []byte("渡远荆门外,来从楚国游。山随平野尽,江入大荒流。月下飞天境,云生结海楼。仍怜故乡水,万里送行舟。")
// 6. 将数据通过哈希函数生成信息摘要
myHash := sha256.New()
myHash.Write(myData)
result := myHash.Sum(nil)
// 7. 生成签名
mySignature, err := rsa.SignPKCS1v15(rand.Reader, priv_Key, crypto.SHA256, result)
if err != nil {
return nil, errors.New("生成签名失败!!!")
}
return mySignature, nil
}
7.4.2 验证数字签名
go
func VerifyRSA(src []byte) (error){
// 1. 从秘钥文件中读生成的秘钥内容
fp, err := os.Open("public.pem")
if err != nil {
return errors.New("打开公钥文件 - public.pem 失败!!!")
}
// 2. 读文件内容
fileInfo, _ := fp.Stat()
all := make([]byte, fileInfo.Size())
num, err := fp.Read(all)
if err != nil {
return errors.New("读文件内容失败!!!")
}
fmt.Println("文件大小: ", num)
// 3. 关闭文件
defer fp.Close()
// 4. 将公钥数据解析为pem格式的数据块
block, _ := pem.Decode(all)
// 5. 将公钥从pem数据块中提取出来
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return errors.New("解析公钥失败!!!")
}
// 6. 公钥接口转换为公钥对象
pubKey := pubInterface.(*rsa.PublicKey)
// 待认证数据
myData := []byte("渡远荆门外,来从楚国游。山随平野尽,江入大荒流。月下飞天境,云生结海楼。仍怜故乡水,万里送行舟。")
// 7. 将数据通过哈希函数生成信息摘要
myHash := sha256.New()
myHash.Write(myData)
result := myHash.Sum(nil)
// 7. 数据认证
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, result, src)
if err != nil {
return err
}
fmt.Println("数字签名验证成功, 恭喜o(* ̄︶ ̄*)o恭喜")
return nil
}
RSA生成密钥对--->案例
go
// GenerateRsaKey 生成rsa的密钥对,并且保存到磁盘文件中
func GenerateRsaKey(keySize int) {
//使用rsa中的GenerateKey方法生成私钥
//第一个参数是随机数,第二个参数====秘钥的位数--->1024个bit的倍数
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
fmt.Println("GenerateKey", err)
return
}
// 通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
derText := x509.MarshalPKCS1PrivateKey(privateKey)
// 将公钥字符串设置到pem格式块中 --- 要组织一个pem Block块
block := pem.Block{
Type: "rsa private key", //这个地方写个字符串就行
//Headers: //可选
Bytes: derText, // 内容解码后的数据,一般是DER编码的ASN.1结构
}
// pem编码---写入到磁盘中
file, err := os.Create("private.pem")
if err != nil {
panic(err)
}
err = pem.Encode(file, &block)
if err != nil {
panic(err)
//return
} //把Bytes格式化
file.Close()
//=====================================公钥===============
//1 从私钥中取出公钥
publicKey := privateKey.PublicKey
//使用x509标准序列化==>转换为字符串
derstream, err := x509.MarshalPKIXPublicKey(&publicKey) //编码之后的字符串 在swich里面是指针的判断
if err != nil {
panic(err)
}
//将得到的数据放到pem Block中
block1 := pem.Block{
Type: "rsa public key",
Bytes: derstream, //通过x509转换的字符串放到字节中去
}
//pem编码
file1, err := os.Create("public.pem")
if err != nil {
return
}
err = pem.Encode(file1, &block1)
if err != nil {
panic(err)
}
file1.Close()
}
rsa签名和签名认证的案例
go
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
)
func main() {
src := []byte("在消息认证码中,需要发送者和接收者之间共享密钥,而这个密钥不能被主动攻击者Mallory获取。如果这个密钥落入Mallory手中,则Mallory也可以计算出MAC值,从而就能够自由地进行篡改和伪装攻击,这样一来消息认证码就无法发挥作用了。")
sigText := SignatureRSA(src, "private.pem")
bl := VerifyRSA(src, sigText, "public.pem")
fmt.Println(bl)
}
// SignatureRSA RSA签名----->使用私密秘钥
// fileName文件名为私钥 原始数据 返回签名字符串
func SignatureRSA(plainText []byte, fileName string) []byte {
//打开磁盘的私钥文件
file, err := os.Open(fileName)
if err != nil {
panic(err)
}
//将私钥文件中的内容读出
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
file.Close()
//使用pem对数据解码, 得到了pem.Block结构体变量
block, _ := pem.Decode(buf) //解码
//x509将数据解析成私钥结构体 -> 得到了私钥
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
//创建一个哈希对象 -> md5/sha1 选择sha512
myhash := sha512.New()
//给哈希对象添加数据
myhash.Write(plainText)
//计算哈希值
hashText := myhash.Sum(nil)
//使用rsa中的函数对散列值签名
//参数1: rand.Reader
//参数2: 非对称加密的私钥
//参数3: 使用的哈希算法
// crypto.sha1
// crypto.md5
//参数4: 数据计算之后得到的散列值
//返回值:
// - s: 得到的签名数据
// - err: 错误信息
signPKCS1v15, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA512, hashText)
if err != nil {
panic(err)
}
return signPKCS1v15
}
// VerifyRSA RSA签名认证--->公钥
// plainText 明文,pubFileName 公钥fileName 签名字符串
func VerifyRSA(plainText, signPKCS1v15 []byte, pubFileName string) bool {
//1. 打开公钥文件, 将文件内容读出 - []byte
file, err := os.Open(pubFileName)
if err != nil {
panic(err)
}
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
file.Close()
//2. 使用pem解码 -> 得到pem.Block结构体变量
block, _ := pem.Decode(buf) //解码
//fmt.Println(block.Headers)
//fmt.Println(block.Type)
//fmt.Println(block.Bytes)
//3. 使用x509对pem.Block中的Bytes变量中的数据进行解析 -> 得到一接口
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
//4. 进行类型断言 -> 得到了公钥结构体
publicKey := pubInterface.(*rsa.PublicKey)
//5. 对原始消息进行哈希运算(和签名使用的哈希算法一致) -> 散列值
// 1. 创建哈希接口
hashText := sha512.Sum512(plainText) //返回的是[Size]byte 定长的数组
// 2. 添加数据
// 3. 哈希运算
//6. 签名认证 - rsa中的函数------>签名认证的比较,是在内部做的
//参数1: 公钥
//参数2: 哈希算法 -> 与签名使用的哈希算法一致
//参数3: 将原始数据进行哈希原始得到的散列值--->切片
//参数4: 签名的字符串
//返回值:
// - nil -> 验证成功
// - !=nil -> 失败
//hashText[:]这样就可以转换为切片
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA512, hashText[:], signPKCS1v15)
if err == nil {
return true
}
return false
}
7.5 使用椭圆曲线实现数字签名---->效率比rsa要高很多---数字越大对应的公钥和私钥越长,加密等级越高
go语言没有提供做非对称加解密的包
椭圆曲线在go中对应的包: import "crypto/elliptic"
使用椭圆曲线在go中进行数字签名: import "crypto/ecdsa"
美国FIPS186-2标准, 推荐使用5个素域上的椭圆曲线, 这5个素数模分别是:
P~192~ = 2^192^ - 2^64^ - 1
P~224~ = 2^224^ - 2^96^ + 1
P~256~ = 2^256^ - 2^224^ + 2^192^ - 2^96^ -1
P~384~ = 2^384^ - 2^128^ - 2^96^ + 2^32^ -1
P~512~ = 2^512^ - 1
-
秘钥对称的生成, 并保存到磁盘
-
使用ecdsa生成密钥对
gofunc GenerateKey(c elliptic.Curve, rand io.Reader) (priv *PrivateKey, err error)
-
将私钥写入磁盘
-
使用x509进行序列化
gofunc MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)
-
将得到的切片字符串放入pem.Block结构体中
block := pem.Block{
Type : "描述...",
Bytes : MarshalECPrivateKey返回值中的切片字符串,
}
-
使用pem编码
pem.Encode();
-
-
将公钥写入磁盘
-
从私钥中得到公钥
-
使用x509进行序列化
gofunc MarshalPKIXPublicKey(pub interface{}) ([]byte, error)
-
将得到的切片字符串放入pem.Block结构体中
block := pem.Block{
Type : "描述...",
Bytes : MarshalECPrivateKey返回值中的切片字符串,
}
-
使用pem编码
pem.Encode();
-
-
生成密钥对--案例
go
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"os"
)
func main() {
GenerateEccKey()
}
// GenerateEccKey 生成椭圆曲线的密钥对
func GenerateEccKey() {
//使用ecdsa生成密钥对--->私钥
//type PrivateKey struct {
// PublicKey
// D *big.Int
//}
privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
panic(err)
}
//将私钥写入磁盘
//使用x509进行序列化
derText, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
panic(err)
}
//将得到的切片字符串放入pem.Block结构体中
block := pem.Block{
Type: "ecdsa private key",
Bytes: derText,
}
//使用pem编码
file, err := os.Create("eccPrivate.pem")
if err != nil {
panic(err)
}
// 参数1: 可进行写操作的IO对象, 此处需要指定一个文件指针
pem.Encode(file, &block) //编码 写入到磁盘文件
file.Close()
//将公钥写入磁盘
//从私钥中得到公钥
publicKey := privateKey.PublicKey
//使用x509进行序列化
derText, err = x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
panic(err)
}
//将得到的切片字符串放入pem.Block结构体中
block = pem.Block{
Type: "ecdsa public key",
Bytes: derText,
}
//使用pem编码
file, err = os.Create("eccPublic.pem")
if err != nil {
panic(err)
}
pem.Encode(file, &block)
file.Close()
}
-
使用私钥进行数字签名
-
打开私钥文件, 将内容读出来 ->[]byte
-
使用pem进行数据解码 -> pem.Decode()
-
使用x509, 对私钥进行还原
gofunc ParseECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error)
-
对原始数据进行哈希运算 -> 散列值
-
进行数字签名
gofunc Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)--->r s得到的是两个点 -->比较的是曲线的两个点 - 得到的r和s不能直接使用, 因为这是指针--->指向本机的两个内存地址 应该将这两块内存中的数据进行序列化 -> []byte func (z *Int) MarshalText() (text []byte, err error)
-
使用私钥进行签名--案例
go
// EccSignature ecc签名---私钥
// privName:私钥 plainText:原始数据
func EccSignature(plainText []byte, privName string) (rText, sText []byte) {
//> 1. 打开私钥文件, 将内容读出来 ->[]byte
file, err := os.Open(privName)
if err != nil {
return
}
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
file.Close()
//> 2. 使用pem进行数据解码 -> pem.Decode()
block, _ := pem.Decode(buf)
//> 3. 使用x509, 对私钥进行还原
privateKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
panic(err)
}
//> 4. 对原始数据进行哈希运算 -> 散列值
hashText := sha1.Sum(plainText)
//> 5. 进行数字签名
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hashText[:])
if err != nil {
panic(err)
}
// 6. 对r, s内存中的数据进行格式化 -> []byte
rText, err = r.MarshalText()
if err != nil {
panic(err)
}
sText, err = s.MarshalText()
if err != nil {
panic(err)
}
return
}
-
使用公钥验证数字签名
-
打开公钥文件, 将里边的内容读出 -> []byte
-
pem解码 -> pem.Decode()
-
使用x509对公钥还原
gofunc ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error)
-
将接口 -> 公钥
-
对原始数据进行哈希运算 -> 得到散列值
-
签名的认证 - > ecdsa
gofunc Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool - 参数1: 公钥 - 参数2: 原始数据生成的散列值 - 参数3,4: 通过签名得到的连个点 func (z *Int) UnmarshalText(text []byte) error
-
使用公钥验证--案例
go
// EccVerify Ecc签名认证
// plainText:原始数据,rText, sText :需要这两个序列化,然后需要一个公钥的文件
func EccVerify(plainText, rText, sText []byte, pubFile string) bool {
//> 1. 打开公钥文件, 将里边的内容读出 -> []byte
file, err := os.Open(pubFile)
if err != nil {
panic(err)
}
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
file.Close()
//> 2. pem解码 -> pem.Decode()
block, _ := pem.Decode(buf)
//> 3. 使用x509对公钥还原
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
//> 4. 将接口 -> 公钥
publicKey := pubInterface.(*ecdsa.PublicKey)
//> 5. 对原始数据进行哈希运算 -> 得到散列值
hashText := sha1.Sum(plainText)
//> 6. 签名的认证 - > ecdsa (问题,api的设计为什么在这个地方要传地址,直接传值比较不是更好吗?)
var r, s big.Int
// 将rText, sText -> int数据
r.UnmarshalText(rText)
s.UnmarshalText(sText)
bl := ecdsa.Verify(publicKey, hashText[:], &r, &s)
return bl
}
整体源码
go
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"os"
)
func main() {
//GenerateEccKey()
src := []byte("使用x509对pem.Block中的Bytes变量中的数据进行解析 -> 得到一接口")
rText, sText := EccSignature(src, "eccPrivate.pem")
bl := EccVerify(src, rText, sText, "eccPublic.pem")
fmt.Println(bl)
}
// GenerateEccKey 生成椭圆曲线的密钥对
func GenerateEccKey() {
//使用ecdsa生成密钥对--->私钥
//type PrivateKey struct {
// PublicKey
// D *big.Int
//}
privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
panic(err)
}
//将私钥写入磁盘
//使用x509进行序列化
derText, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
panic(err)
}
//将得到的切片字符串放入pem.Block结构体中
block := pem.Block{
Type: "ecdsa private key",
Bytes: derText,
}
//使用pem编码
file, err := os.Create("eccPrivate.pem")
if err != nil {
panic(err)
}
// 参数1: 可进行写操作的IO对象, 此处需要指定一个文件指针
pem.Encode(file, &block) //编码 写入到磁盘文件
file.Close()
//将公钥写入磁盘
//从私钥中得到公钥
publicKey := privateKey.PublicKey
//使用x509进行序列化
derText, err = x509.MarshalPKIXPublicKey(&publicKey)
if err != nil {
panic(err)
}
//将得到的切片字符串放入pem.Block结构体中
block = pem.Block{
Type: "ecdsa public key",
Bytes: derText,
}
//使用pem编码
file, err = os.Create("eccPublic.pem")
if err != nil {
panic(err)
}
pem.Encode(file, &block)
file.Close()
}
// EccSignature ecc签名---私钥
// privName:私钥 plainText:原始数据
func EccSignature(plainText []byte, privName string) (rText, sText []byte) {
//> 1. 打开私钥文件, 将内容读出来 ->[]byte
file, err := os.Open(privName)
if err != nil {
panic(err)
}
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
file.Close()
//> 2. 使用pem进行数据解码 -> pem.Decode()
block, _ := pem.Decode(buf)
//> 3. 使用x509, 对私钥进行还原
privateKey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
panic(err)
}
//> 4. 对原始数据进行哈希运算 -> 散列值
hashText := sha1.Sum(plainText)
//> 5. 进行数字签名
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hashText[:])
if err != nil {
panic(err)
}
// 6. 对r, s内存中的数据进行格式化 -> []byte
rText, err = r.MarshalText()
if err != nil {
panic(err)
}
sText, err = s.MarshalText()
if err != nil {
panic(err)
}
return
}
// EccVerify Ecc签名认证
// plainText:原始数据,rText, sText :需要这两个序列化,然后需要一个公钥的文件
func EccVerify(plainText, rText, sText []byte, pubFile string) bool {
//> 1. 打开公钥文件, 将里边的内容读出 -> []byte
file, err := os.Open(pubFile)
if err != nil {
panic(err)
}
info, err := file.Stat()
if err != nil {
panic(err)
}
buf := make([]byte, info.Size())
file.Read(buf)
file.Close()
//> 2. pem解码 -> pem.Decode()
block, _ := pem.Decode(buf)
//> 3. 使用x509对公钥还原
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
//> 4. 将接口 -> 公钥
publicKey := pubInterface.(*ecdsa.PublicKey)
//> 5. 对原始数据进行哈希运算 -> 得到散列值
hashText := sha1.Sum(plainText)
//> 6. 签名的认证 - > ecdsa (问题,api的设计为什么在这个地方要传地址,直接传值比较不是更好吗?)
var r, s big.Int
// 将rText, sText -> int数据
r.UnmarshalText(rText)
s.UnmarshalText(sText)
bl := ecdsa.Verify(publicKey, hashText[:], &r, &s)
return bl
}
7.6 数字签名无法解决的问题
用数字签名既可以识别出篡改和伪装,还可以防止否认。也就是说,我们同时实现了确认消息的完整性、进行认证以及否认防止。现代社会中的计算机通信从这一技术中获益匪浅。
然而,**要正确使用数字签名,有一个大前提,那是用于验证签名的公钥必须属于真正的发送者。**即便数字签名算法再强大,如果你得到的公钥是伪造的,那么数字签名也会完全失效。
现在我们发现自己陷人了一个死循环一一一数字签名是用来识别消息篡改、伪装以及否认的,但是为此我们又必须从没有被伪装的发送者得到没有被篡改的公钥才行。
为了能够确认自己得到的公钥是否合法,我们需要使用证书。所谓证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。
当然,这样的方法只是把问题转移了而已。为了对证书上施加的数字签名进行验证,我们必定需要另一个公钥,那么如何才能构筑一个可信的数字签名链条呢?又由谁来颁发可信的证书呢?到这一步,我们就已经踏人了社会学的领域。我们需要让公钥以及数字签名技术成为一种社会性的基础设施,即公钥基础设施(Public Key Intrastructure),简称PKIO关于证书和PKI我们将在第8章中介绍。
8. 证书
go
"证书 -- 为公钥加上数字签名"
要开车得先考驾照.驾照上面记有本人的照片、姓名、出生日期等个人信息.以及有效期、准驾车辆的类型等信息,并由公安局在上面盖章。我们只要看到驾照,就可以知道公安局认定此人具有驾驶车辆的资格。
公钥证书(Public-Key Certificate,PKC)其实和驾照很相似,里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority、Certifying Authority, CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人。公钥证书也简称为证书(certificate)。
可能很多人都没听说过认证机构,认证机构就是能够认定 "公钥确实属于此人",并能够生成数字签名的个人或者组织。认证机构中有国际性组织和政府所设立的组织,也有通过提供认证服务来盈利的一般企业,此外个人也可以成立认证机构。
8.1 证书的应用场景
下面我们来通过证书的代表性应用场景来理解证书的作用。
下图展示了Alice向Bob发送密文的场景,在生成密文时所使用的Bob的公钥是通过认证机构获取的。
认证机构必须是可信的,对于"可信的第三方",下图中会使用Trent这个名字,这个词是从trust(信任)一词演变而来的。
下面让我们对照着上图来看一看这些步骤具体都做了些什么。
Bob生成密钥对
要使用公钥密码进行通信,首先需要生成密钥对。Bob生成了一对公钥和私钥,并将私钥自行妥善保管。在这里,密钥对是由Bob自己生成的,也可以由认证机构代为生成。
Bob在认证机构Trent注册自己的公钥
在这里Bob则将公钥发送给了认证机构Trent,这是因为Bob需要请认证机构Trent对他的公钥加上数字签名(也就是生成证书)。
Trent收到Bob的公钥后,会确认所收到的公钥是否为Bob本人所有(参见专栏:身份确认和认证业务准则)
专栏:身份确认和认证业务准则
认证机构确认"本人"身份的方法和认证机构的认证业务准则(CertificatePractice Statement, CPS,的内容有关。如果认证机构提供的是测试用的服务,那么可能完全不会进行任何身份确认。如果是政府部门运營的认证机构,可能就需要根据法律规定来进行身份确认。如果是企业面向内部设立的认证机构,那就可能会给部门负责人打电话直接确认。
例如,VeriSign的认证业务准则中将身份确认分为Class1 ~ 3共三个等级
- Class1:通过向邮箱发送件来确认本人身份
- Class2:通过第三方数据库来确认本人身份
- Class3:通过当面认证和身份证明来确认本人身份
等级越高,身份确认越严格。
认证机构Trent用自己的私钥对Bob的公钥施加数字签名并生成证书
Trent对Bob的公钥加上数字签名。为了生成数字签名,需要Trent自身的私钥,因此Trent需要事先生成好密钥对。
Alice得到带有认证机构Trent的数字签名的Bob的公钥(证书)
现在Alice需要向Bob发送密文,因此她从Trent处获取证书。证书中包含了Bob的公钥。
Alice使用认证机构Trent的公钥验证数字签名,确认Bob的公钥的合法性
Alice使用认证机构Trent的公钥对证书中的数字签名进行验证。如果验证成功,就相当于确认了证书中所包含的公钥的确是属于Bob的。到这里,Alice就得到了合法的Bob的公钥。
Alice用Bob的公钥加密消息并发送给Bob
Alice用Bob的公钥加密要发送的消息,并将消息发送给Bob。
Bob用自己的私钥解密密文得到Alice的消息
Bob收到Alice发送的密文,然后用自己的私钥解密,这样就能够看到Alice的消息了。
上面就是利用认证机构Trent进行公钥密码通信的流程。其中1、2、3这几个步骤仅在注册新公钥时才会进行,并不是每次通信都需要。此外,步骤 4 仅在Alice第一次用公钥密码向Bob发送消息时才需要进行,只要Alice将Bob的公钥保存在电脑中,在以后的通信中就可以直接使用了。
- Bob生成密钥对
- 可以将bob看成百度, 提供是web服务器
- 生成一个密钥对
- 公钥 -> 分发-->不自己去分发,找第三方认证机构去分发
- 私钥 -> 百度留着
- Bob在认证机构Trent注册自己的公钥
- 百度找了一大家都信赖的机构, 来证明这个公钥是百度的
- 认证机构会生成一个证书, 写明了公钥属于百度
- 认证机构也有一个非对称加密的密钥对
- 认证机构使用自己的私钥对百度的公钥进行签名, 生成了证书
- 认证机构将证书发送给百度
- 认证机构Trent用自己的私钥对Bob的公钥施加数字签名并生成证书
- Alice得到带有认证机构Trent的数字签名的Bob的公钥(证书)
- alice可以看做一个客户 -> 浏览器
- 客户端访问的百度 -> 得到了百度的证书
- 证书中有百度的公钥
- 客户端需要使用认证机构的公钥对证书进行验证
- 客户端怎么会有认证机构的公钥
- window会预装, 或者用户自己安装
- Alice使用认证机构Trent的公钥验证数字签名,确认Bob的公钥的合法性
- 使用认证机构的公钥解除百度证书中签名的数据
- 百度的公钥
- 百度的域名
- 百度证书的有效期
- Alice用Bob的公钥加密消息并发送给Bob
- 非对称加密
- 使用公钥加密 -> 对称加密秘钥分发
- Bob用自己的私钥解密密文得到Alice的消息
- 服务器使用私钥解密 -> 得到对称加密的秘钥
8.2 证书标准规范X.509
证书是由认证机构颁发的,使用者需要对证书进行验证,因此如果证书的格式千奇百怪那就不方便了。于是,人们制定了证书的标准规范,其中使用最广泛的是由ITU(International TelecommumcationUnion,国际电信联盟)和ISO(IntemationalOrganizationforStandardization, 国际标准化组织)制定的X.509规范。很多应用程序都支持x.509并将其作为证书生成和交换的标准规范。
X.509是一种非常通用的证书格式。所有的证书都符合ITU-T X.509国际标准,因此(理论上)为一种应用创建的证书可以用于任何其他符合X.509标准的应用。X.509证书的结构是用ASN1(Abstract Syntax Notation One)进行描述数据结构,并使用ASN.1语法进行编码。
在一份证书中,必须证明公钥及其所有者的姓名是一致的。对X.509证书来说,认证者总是CA或由CA指定的人,一份X.509证书是一些标准字段的集合,这些字段包含有关用户或设备及其相应公钥的信息。X.509标准定义了证书中应该包含哪些信息,并描述了这些信息是如何编码的(即数据格式)
一般来说,一个数字证书内容可能包括基本数据(版本、序列号) 、所签名对象信息( 签名算法类型、签发者信息、有效期、被签发人、签发的公开密钥)、CA的数字签名,等等。
8.2.1 证书规范
前使用最广泛的标准为ITU和ISO联合制定的X.509的 v3版本规范 (RFC5280), 其中定义了如下证书信息域:
版本号(Version Number):规范的版本号,目前为版本3,值为0x2;
序列号(Serial Number):由CA维护的为它所发的每个证书分配的一的列号,用来追踪和撤销证书。只要拥有签发者信息和序列号,就可以唯一标识一个证书,最大不能过20个字节;
签名算法(Signature Algorithm):数字签名所采用的算法,如:
- sha256-with-RSA-Encryption -->哈希256--非对称加密
- ccdsa-with-SHA2S6;
前面是加密算法后面是哈希算法颁发者(Issuer):发证书单位的标识信息,如 " C=CN,ST=Beijing, L=Beijing, O=org.example.com,CN=ca.org。example.com ";
有效期(Validity): 证书的有效期很,包括起止时间。
主体(Subject) : 证书拥有者的标识信息(Distinguished Name),如:" C=CN,ST=Beijing, L=Beijing, CN=person.org.example.com";
主体的公钥信息(SubJect Public Key Info):所保护的公钥相关的信息:
- 公钥算法 (Public Key Algorithm)公钥采用的算法;
- 主体公钥(Subject Unique Identifier):公钥的内容。
颁发者唯一号(Issuer Unique Identifier):代表颁发者的唯一信息,仅2、3版本支持,可选;
主体唯一号(Subject Unique Identifier):代表拥有证书实体的唯一信息,仅2,3版本支持,可选:
扩展(Extensions,可选): 可选的一些扩展。中可能包括:
- Subject Key Identifier:实体的秘钥标识符,区分实体的多对秘钥;
- Basic Constraints:一指明是否属于CA;
- Authority Key Identifier:证书颁发者的公钥标识符;
- CRL Distribution Points: 撤销文件的颁发地址;
- Key Usage:证书的用途或功能信息。
此外,证书的颁发者还需要对证书内容利用自己的私钥添加签名, 以防止别人对证书的内容进行篡改。
8.2.2 证书格式
X.509规范中一般推荐使用PEM(Privacy Enhanced Mail)格式来存储证书相关的文件。证书文件的文件名后缀一般为 .crt 或 .cer 。对应私钥文件的文件名后缀一般为 .key。证书请求文件的文件名后綴为 .csr 。有时候也统一用pem作为文件名后缀。
PEM格式采用文本方式进行存储。一般包括首尾标记和内容块,内容块采用Base64进行编码。
编码格式总结:
- X.509 DER(Distinguished Encoding Rules)编码,后缀为:.der .cer .crt
- X.509 BASE64编码(PEM格式),后缀为:.pem .cer .crt
例如,一个PEM格式(base64编码)的示例证书文件内容如下所示:
X.509是一种非常通用的证书格式。所有的证书都符合ITU-T X.509国际标准,因此(理论上)为一种应用创建的证书可以用于任何其他符合X.509标准的应用。X.509证书的结构是用ASN1(Abstract Syntax Notation One)进行描述数据结构,并使用ASN.1语法进行编码。X.509规范中一般推荐使用PEM(Privacy Enhanced Mail)格式来存储证书相关的文件。
- 证书文件的文件名后缀一般为 .crt 或 .cer
- 对应私钥文件的文件名后缀一般为 .key
- 证书请求文件的文件名后綴为 .csr ------>生成证书的中间文件
- 有时候也统一用pem作为文件名后缀。
go
-----BEGIN CERTIFICATE-----
MIIDyjCCArKgAwIBAgIQdZfkKrISoINLporOrZLXPTANBgkqhkiG9w0BAQsFADBn
MSswKQYDVQQLDCJDcmVhdGVkIGJ5IGh0dHA6Ly93d3cuZmlkZGxlcjIuY29tMRUw
EwYDVQQKDAxET19OT1RfVFJVU1QxITAfBgNVBAMMGERPX05PVF9UUlVTVF9GaWRk
bGVyUm9vdDAeFw0xNzA0MTExNjQ4MzhaFw0yMzA0MTExNjQ4MzhaMFoxKzApBgNV
BAsMIkNyZWF0ZWQgYnkgaHR0cDovL3d3dy5maWRkbGVyMi5jb20xFTATBgNVBAoM
DERPX05PVF9UUlVTVDEUMBIGA1UEAwwLKi5iYWlkdS5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDX0AM198jxwRoKgwWsd9oj5vI0and9v9SB9Chl
gZEu6G9ZA0C7BucsBzJ2bl0Mf6qq0Iee1DfeydfEKyTmBKTafgb2DoQE3OHZjy0B
QTJrsOdf5s636W5gJp4f7CUYYA/3e1nxr/+AuG44Idlsi17TWodVKjsQhjzH+bK6
8ukQZyel1SgBeQOivzxXe0rhXzrocoeKZFmUxLkUpm+/mX1syDTdaCmQ6LT4KYYi
soKe4f+r2tLbUzPKxtk2F1v3ZLOjiRdzCOA27e5n88zdAFrCmMB4teG/azCSAH3g
Yb6vaAGaOnKyDLGunW51sSesWBpHceJnMfrhwxCjiv707JZtAgMBAAGjfzB9MA4G
A1UdDwEB/wQEAwIEsDATBgNVHSUEDDAKBggrBgEFBQcDATAWBgNVHREEDzANggsq
LmJhaWR1LmNvbTAfBgNVHSMEGDAWgBQ9UIffUQSuwWGOm+o74JffZJNadjAdBgNV
HQ4EFgQUQh8IksZqcMVmKrIibTHLbAgLRGgwDQYJKoZIhvcNAQELBQADggEBAC5Y
JndwXpm0W+9SUlQhAUSE9LZh+DzcSmlCWtBk+SKBwmAegbfNSf6CgCh0VY6iIhbn
GlszqgAOAqVMxAEDlR/YJTOlAUXFw8KICsWdvE01xtHqhk1tCK154Otci60Wu+tz
1t8999GPbJskecbRDGRDSA/gQGZJuL0rnmIuz3macSVn6tH7NwdoNeN68Uj3Qyt5
orYv1IFm8t55224ga8ac1y90hK4R5HcvN71aIjMKrikgynK0E+g45QypHRIe/z0S
/1W/6rqTgfN6OWc0c15hPeJbTtkntB5Fqd0sfsnKkW6jPsKQ+z/+vZ5XqzdlFupQ
29F14ei8ZHl9aLIHP5s=
-----END CERTIFICATE-----
证书中的解析出来的内容:
go
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
10:e6:fc:62:b7:41:8a:d5:00:5e:45:b6
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=BE, O=GlobalSign nv-sa, CN=GlobalSign Organization Validation CA-SHA256-G2
Validity
Not Before: Nov 21 08:00:00 2016 GMT
Not After : Nov 22 07:59:59 2017 GMT
Subject: C=US, ST=California, L=San Francisco, O=Wikimedia Foundation, Inc., CN=*.wikipedia.org
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:c9:22:69:31:8a:d6:6c:ea:da:c3:7f:2c:ac:a5:
af:c0:02:ea:81:cb:65:b9:fd:0c:6d:46:5b:c9:1e:
ed:b2:ac:2a:1b:4a:ec:80:7b:e7:1a:51:e0:df:f7:
c7:4a:20:7b:91:4b:20:07:21:ce:cf:68:65:8c:c6:
9d:3b:ef:d5:c1
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Agreement
Authority Information Access:
CA Issuers - URI:http://secure.globalsign.com/cacert/gsorganizationvalsha2g2r1.crt
OCSP - URI:http://ocsp2.globalsign.com/gsorganizationvalsha2g2
X509v3 Certificate Policies:
Policy: 1.3.6.1.4.1.4146.1.20
CPS: https://www.globalsign.com/repository/
Policy: 2.23.140.1.2.2
X509v3 Basic Constraints:
CA:FALSE
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl.globalsign.com/gs/gsorganizationvalsha2g2.crl
X509v3 Subject Alternative Name:
DNS:*.wikipedia.org, DNS:*.m.mediawiki.org, DNS:*.m.wikibooks.org, DNS:*.m.wikidata.org, DNS:*.m.wikimedia.org, DNS:*.m.wikimediafoundation.org, DNS:*.m.wikinews.org, DNS:*.m.wikipedia.org, DNS:*.m.wikiquote.org, DNS:*.m.wikisource.org, DNS:*.m.wikiversity.org, DNS:*.m.wikivoyage.org, DNS:*.m.wiktionary.org, DNS:*.mediawiki.org, DNS:*.planet.wikimedia.org, DNS:*.wikibooks.org, DNS:*.wikidata.org, DNS:*.wikimedia.org, DNS:*.wikimediafoundation.org, DNS:*.wikinews.org, DNS:*.wikiquote.org, DNS:*.wikisource.org, DNS:*.wikiversity.org, DNS:*.wikivoyage.org, DNS:*.wiktionary.org, DNS:*.wmfusercontent.org, DNS:*.zero.wikipedia.org, DNS:mediawiki.org, DNS:w.wiki, DNS:wikibooks.org, DNS:wikidata.org, DNS:wikimedia.org, DNS:wikimediafoundation.org, DNS:wikinews.org, DNS:wikiquote.org, DNS:wikisource.org, DNS:wikiversity.org, DNS:wikivoyage.org, DNS:wiktionary.org, DNS:wmfusercontent.org, DNS:wikipedia.org
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Subject Key Identifier:
28:2A:26:2A:57:8B:3B:CE:B4:D6:AB:54:EF:D7:38:21:2C:49:5C:36
X509v3 Authority Key Identifier:
keyid:96:DE:61:F1:BD:1C:16:29:53:1C:C0:CC:7D:3B:83:00:40:E6:1A:7C
Signature Algorithm: sha256WithRSAEncryption
8b:c3:ed:d1:9d:39:6f:af:40:72:bd:1e:18:5e:30:54:23:35:
...
8.2.3 CA证书
证书是用来证明某某东西确实是某某东西的东西(是不是像绕口令?)。通俗地说,证书就好比上文里面的公章。通过公章,可以证明对应的证件的真实性。
理论上,人人都可以找个证书工具,自己做一个证书。那如何防止坏人自己制作证书出来骗人捏?请看后续 CA 的介绍。
CA是Certificate Authority的缩写,也叫"证书授权中心"。
它是负责管理和签发证书的第三方机构, 好比一个可信任的中介公司。一般来说,CA必须是所有行业和所有公众都信任的、认可的。因此它必须具有足够的权威性。就好比A、B两公司都必须信任C公司,才会找 C 公司作为公章的中介。
-
CA证书
CA 证书,顾名思义,就是CA颁发的证书。
前面已经说了,人人都可以找工具制作证书。但是你一个小破孩制作出来的证书是没啥用处的。因为你不是权威的CA机关,你自己搞的证书不具有权威性。
比如,某个坏人自己刻了一个公章,盖到介绍信上。但是别人一看,不是受信任的中介公司的公章,就不予理睬。坏蛋的阴谋就不能得逞啦。
2. 证书的颁发机构 -> CA- 发布根证书
- 中间证书--->根证书授权了
- 个人
-
证书的信任链 -> 证书签发机构的信任链
A是一个可信赖证书签发机构, A信任B, B就有资格去签发证书
从等级上A比B高一级
-
证书的获取和身份的认证
客户端与服务端需要经过一个握手的过程才能完成身份认证,建立一个安全的连接。握手的过程如下:
-
客户端访问服务器(比如:https://www.12306.cn),发送ssl版本、客户端支持的加密算法等消息。
-
服务器向客户端发送ssl版本、加密算法、证书(证书出现了)等消息。
-
客户端收到消息后,判断证书是否可信, 若可信,则继续通信,发送消息:
客户端生成一个随机数,从证书中获取服务器端的公钥,对随机数加密;
随后信息都将使用双方协定的加密方法和密钥发送, 客户端握手结束。
-
服务器端对数据解密得到随机数, 使用协商好的加密算法和秘钥进行通信
-
-
客户端如何验证CA证书是可信任的?
-
查看证书的方式:
Internet选项 -> 内容 -> 证书, 打开证书窗口查看已经安装的证书
只要电脑上安装了该证书, 就说明该证书是受信任的。使用https协议访问时,服务器发送证书向浏览器时,首先查找该证书是否已在信任列表中,然后对证书进行校验,校验成功,那么就证明证书是可信的。
下图中
受信任的根证书颁发机构
下的证书都是根证书。证书验证的机制是只要根证书是受信任的,那么它的子证书都是可信的。比如说,我们使用https协议访问了需要百度证书的网站,即使我们不安装百度证书,那么网站也不会提示证书不安全,因为,生成百度证书的根证书
Globalsign Root CA - R1
证书,在受信任的证书列表中。如果一个证书的根证书是不可信的,那么这个证书肯定也是不可信任的。由以上可知,根证书在证书验证中极其重要,而且,根证书是无条件信任的,只要我们将根证书安装上,就说明我们对根证书是信任的。比如我们安装12306的根证书,是出于我们对国家的信任,对网站的信任,我们才放心安装这个根证书。对于一些不安全的网站的证书,一定要慎重安装。
另外需要知道的是,【
受信任的根证书颁发机构
】中的证书是windows预先安装的一些证书,都是国际上很有权威的证书机构,他们证书的生成都有很严格的流程,因此他们的证书被认为是安全,就像我们相信银行是安全,所以把钱存入到银行。
-
-
证书信任链
证书直接是可以有信任关系的, 通过一个证书可以证明另一个证书也是真实可信的. 实际上,证书之间的信任关系,是可以嵌套的。比如,C 信任 A1,A1 信任 A2,A2 信任 A3...这个叫做证书的信任链。只要你信任链上的头一个证书,那后续的证书,都是可以信任滴。
假设 C 证书信任 A 和 B;然后 A 信任 A1 和 A2;B 信任 B1 和 B2。则它们之间,构成如下的一个树形关系(一个倒立的树)。
处于最顶上的树根位置的那个证书,就是"根证书"。除了根证书,其它证书都要依靠上一级的证书,来证明自己。那谁来证明"根证书"可靠捏?实际上,根证书自己证明自己是可靠滴(或者换句话说,根证书是不需要被证明滴)。
聪明的同学此刻应该意识到了:根证书是整个证书体系安全的根本。所以,如果某个证书体系中,根证书出了问题(不再可信了),那么所有被根证书所信任的其它证书,也就不再可信了。
-
有哪些CA机构?
世界上较早的数字认证中心是美国的verisign
威瑞信
公司,在windows的证书窗口中可以看到好多verisign公司生成的证书, 美国的DigiCert另外还有加拿大的ENTRUST公司,也是很著名的证书机构。
中国的安全认证体系分为金融CA和非金融CA。
- 在金融CA方面,根证书由中国人民银行管理,
- 非金融CA方面,由中国电信负责。
- 行业性CA
- 中国金融认证中心
- 中国电信认证中心
- 区域性CA, 区域性CA主要是以政府为背景,以企业机制运行
- 广东CA中心
- 上海CA中心
- 行业性CA
-
证书有啥用
-
验证网站是否可信(针对HTTPS)
通常,我们如果访问某些敏感的网页(比如用户登录的页面),其协议都会使用 HTTPS 而不是 HTTP。因为 HTTP 协议是明文的,一旦有坏人在偷窥你的网络通讯,他/她就可以看到网络通讯的内容(比如你的密码、银行帐号、等);而 HTTPS 是加密的协议,可以保证你的传输过程中,坏蛋无法偷窥。
但是,千万不要以为,HTTPS 协议有了加密,就可高枕无忧了。俺再举一个例子来说明,光有加密是不够滴。假设有一个坏人,搞了一个假的网银的站点,然后诱骗你上这个站点。假设你又比较单纯,一不留神,就把你的帐号,口令都输入进去了。那这个坏蛋的阴谋就得逞鸟。
为了防止坏人这么干,HTTPS 协议除了有加密的机制,还有一套证书的机制。通过证书来确保,某个站点确实就是某个站点。
有了证书之后,当你的浏览器在访问某个 HTTPS 网站时,会验证该站点上的 CA 证书(类似于验证介绍信的公章)。如果浏览器发现该证书没有问题(证书被某个根证书信任、证书上绑定的域名和该网站的域名一致、证书没有过期),那么页面就直接打开;否则的话,浏览器会给出一个警告,告诉你该网站的证书存在某某问题,是否继续访问该站点?下面给出 IE 和 Firefox 的抓图:
-
大多数知名的网站,如果用了 HTTPS 协议,其证书都是可信的(也就不会出现上述警告)。所以,今后你如果上某个知名网站,发现浏览器跳出上述警告,你就要小心啦!
验证某文件是否可信(是否被篡改)
证书除了可以用来验证某个网站,还可以用来验证某个文件是否被篡改。具体是通过证书来制作文件的数字签名。制作数字签名的过程太专业,咱就不说了。后面专门告诉大家如何验证文件的数字签名。考虑到大多数人用 Windows 系统,俺就拿 Windows 的例子来说事儿。
比如,俺手头有一个 Google Chrome的安装文件(带有数字签名)。当俺查看该文件的属性,会看到如下的界面。眼神好的同学,会注意到到上面有个"数字签名"的标签页。如果没有出现这个标签页,就说明该文件没有附带数字签名。
一般来说,签名列表中,有且仅有一个签名。选中它,点"详细信息"按钮。跳出如下界面:
通常这个界面会显示一行字:"该数字签名正常 "(图中红圈标出)。如果有这行字,就说明该文件从出厂到你手里,中途没有被篡改过(是原装滴、是纯洁滴)。如果该文件被篡改过了(比如,感染了病毒、被注入木马),那么对话框会出现一个警告提示"该数字签名无效"
不论签名是否正常,你都可以点"查看证书"按钮。这时候,会跳出证书的对话框。如下:
从后一个界面,可以看到刚才说的证书信任链。图中的信任链有3层:
- 第1层是根证书(verisign)。
- 第2层是 symantec 专门用来签名的证书。
- 第3层是 Google自己的证书。
目前大多数知名的公司(或组织机构),其发布的可执行文件(比如软件安装包、驱动程序、安全补丁),都带有数字签名。你可以自己去看一下。
建议大伙儿在安装软件之前,都先看看是否有数字签名?如果有,就按照上述步骤验证一把。一旦数字签名是坏的,那可千万别装。
8.3 公钥基础设施(PKI)---部署把私钥和证书放到对应的服务器上
仅制定证书的规范还不足以支持公钥的实际运用,我们还需要很多其他的规范,例如证书应该由谁来颁发,如何颁发,私钥泄露时应该如何作废证书,计算机之间的数据交换应采用怎样的格式等。这一节我们将介绍能够使公钥的运用更加有效的公钥基础设施。
8.3.1 什么是公钥基础设施
公钥基础设施(Public-Key infrastructure)是为了能够更有效地运用公钥而制定的一系列规范和规格的总称。公钥基础设施一般根据其英语缩写而简称为PKI。
PKI只是一个总称,而并非指某一个单独的规范或规格。例如,RSA公司所制定的PKCS(Public-Key Cryptography Standards,公钥密码标准)系列规范也是PKI的一种,而互联网规格RFC(Requestfor Comments)中也有很多与PKI相关的文档。此外,X.509这样的规范也是PKI的一种。在开发PKI程序时所使用的由各个公司编写的API(Application Programming Interface, 应用程序编程接口)和规格设计书也可以算是PKI的相关规格。
因此,根据具体所采用的规格,PKI也会有很多变种,这也是很多人难以整体理解PKI的原因之一。
为了帮助大家整体理解PKI,我们来简单总结一下PKI的基本组成要素(用户、认证机构、仓库)以及认证机构所负责的工作。
8.3.2 PKI的组成要素
PKI的组成要素主要有以下三个:
- 用户 --- 使用PKI的人
- 认证机构 --- 颁发证书的人
- 仓库 --- 保存证书的数据库
- PKI组成的要素
- 用户
- 申请证书的人 -> web服务器端
- 申请证书
- 生成密钥对 , 或者委托ca生成
- 将公钥发送给CA
- ca使用自己的私钥对得到公钥签名
- 将证书发送给用户
- 发送证书
- 当客户端访问服务器的时候发送证书给客户端
- 注销证书
- 当发现私钥泄露之后
- 申请证书
- 使用证书的人 -> 客户端
- 接收证书
- 验证对方的身份信息
- 申请证书的人 -> web服务器端
- CA认证机构
- 可以生产密钥对(可选)-->如果你不生成它就帮助你生成
- 对公钥签名
- 吊销证书
- 仓库
- 存储证书 -> 公钥
- 用户
用户
用户就是像Alice、Bob这样使用PKI的人。用户包括两种:一种是希望使用PKI注册自己的公钥的人,另一种是希望使用已注册的公钥的人。我们来具体看一下这两种用户所要进行的操作。
-
注册公钥的用户所进行的操作
- 生成密钥对(也可以由认证机构生成)
- 在认证机构注册公钥
- 向认证机构申请证书
- 根据需要申请作废已注册的公钥
- 解密接收到的密文
- 对消息进行数字签名
-
使用已注册公钥的用户所进行的操作
- 将消息加密后发送给接收者
- 验证数字签名
c++/* ==================== 小知识点 ==================== 浏览器如何验证SSL证书 1. 在IE浏览器的菜单中点击"工具 /Internet选项",选择"内容"标签,点击"证书"按钮,然后就可以看到IE 浏览器已经信任了许多"中级证书颁发机构"和"受信任的根证书颁发机 构。当我们在访问该网站时,浏览器 就会自动下载该网站的SSL证书,并对证书的安全性进行检查。 2. 由于证书是分等级的,网站拥有者可能从根证书颁发机构领到证书,也可能从根证书的下一级(如某个国家 的认证中心,或者是某个省发出的证书)领到证书。假设我们正在访问某个使用 了 SSL技术的网站,IE浏 览器就会收到了一个SSL证书,如果这个证书是由根证书颁发机构签发的,IE浏览器就会按照下面的步骤来 检查:浏览器使用内 置的根证书中的公钥来对收到的证书进行认证,如果一致,就表示该安全证书是由可信 任的颁证机构签发的,这个网站就是安全可靠的;如果该SSL证书不是根服 务器签发的,浏览器就会自动检 查上一级的发证机构,直到找到相应的根证书颁发机构,如果该根证书颁发机构是可信的,这个网站的SSL证 书也是可信的。 */
认证机构(CA)
认证机构(Certification Authority,CA)是对证书进行管理的人。上面的图中我们给它起了一个名字叫作Trent。认证机构具体所进行的操作如下:
-
生成密钥对 (也可以由用户生成)
生成密钥对有两种方式:一种是由PKI用户自行生成,一种是由认证机构来生成。在认证机构生成用户密钥对的情况下,认证机构需要将私钥发送给用户,这时就需要使用PKCS#12(Personal Information Exchange Syntax Standard)等规范。
-
在注册公钥时对本人身份进行认证, 生成并颁发证书
在用户自行生成密钥对的情况下,用户会请求认证机构来生成证书。申请证书时所使用的规范是由PKCS#10(Certification Request Syntax Standard)定义的。
认证机构根据其认证业务准则(Certification Practice Statement,CPS)对用户的身份进行认证,并生成证书。在生成证书时,需要使用认证机构的私钥来进行数字签名。生成的证书格式是由PKCS#6 (Extended-Certificate Syntax Standard)和 X.509定义的。
-
作废证书
当用户的私钥丢失、被盗时,认证机构需要对证书进行作废(revoke)。此外,即便私钥安然无恙,有时候也需要作废证书,例如用户从公司离职导致其失去私钥的使用权限,或者是名称变更导致和证书中记载的内容不一致等情况。
纸质证书只要撕毁就可以作废了,但这里的证书是数字信息,即便从仓库中删除也无法作废,因为用户会保存证书的副本,但认证机构又不能人侵用户的电脑将副本删除。
要作废证书,认证机构需要制作一张证书==作废清单(Certificate Revocation List),简称为CRL==。
CRL是认证机构宣布作废的证书一览表,具体来说,是一张已作废的证书序列号的清单,并由认证机构加上数字签名。证书序列号是认证机构在颁发证书时所赋予的编号,在证书中都会记载。
PKI用户需要从认证机构获取最新的CRL,并查询自己要用于验证签名(或者是用于加密)的公钥证书是否已经作废这个步骤是非常重要的。
假设我们手上有Bob的证书,该证书有合法的认证机构签名,而且也在有效期内,但仅凭这些还不能说明该证书一定是有效的,还需要查询认证机构最新的CRL,并确认该证书是否有效。一般来说,这个检查不是由用户自身来完成的,而是应该由处理该证书的软件来完成,但有很多软件并没有及时更能CRL。
认证机构的工作中,公钥注册和本人身份认证这一部分可以由注册机构(Registration Authority,RA) 来分担。这样一来,认证机构就可以将精力集中到颁发证书上,从而减轻了认证机构的负担。不过,引入注册机构也有弊端,比如说认证机构需要对注册机构本身进行认证,而且随着组成要素的增加,沟通过程也会变得复杂,容易遭受攻击的点也会增。
仓库
仓库(repository)是一个保存证书的数据库,PKI用户在需要的时候可以从中获取证书.它的作用有点像打电话时用的电话本。在本章开头的例子中,尽管没特别提到,但Alice获取Bob的证书时,就可以使用仓库。仓库也叫作证书目录。
8.3.3 各种各样的PKI
公钥基础设施(PKI)这个名字总会引起一些误解,比如说"面向公众的权威认证机构只有一个",或者"全世界的公钥最终都是由一个根CA来认证的",其实这些都是不正确的。认证机构只要对公钥进行数字签名就可以了,因此任何人都可以成为认证机构,实际上世界上已经有无数个认证机构了。
国家、地方政府、医院、图书馆等公共组织和团体可以成立认证机构来实现PKI,公司也可以出于业务需要在内部实现PKI,甚至你和你的朋友也可以以实验为目的来构建PKI。
在公司内部使用的情况下,认证机构的层级可以像上一节中一样和公司的组织层级一一对应,也可以不一一对应。例如,如果公司在东京、大阪、北海道和九州都成立了分公司,也可以采取各个分公司之间相互认证的结构。在认证机构的运营方面,可以购买用于构建PKI的软件产品由自己公司运营,也可以使用VeriSign等外部认证服务。具体要采取怎样的方式,取决于目的和规模,并没有一定之规。
9. SSL/TLS
go
"SSL/TLS --- 为了更安全的通信"
本章中我们将学习SSL/TLS的相关知识。
SSL/TLS是世界上应用最广泛的密码通信方法。比如说,当在网上商城中输人信用卡号时,我们的Web浏览器就会使用SSL/TLS进行密码通信。使用SSL/TLS可以对通信对象进行认证,还可以确保通信内容的机密性。
SSL/TLS中综合运用了之前所学习的对称密码、消息认证码、公钥密码、数字签名、伪随机数生成器等密码技术,大家可以在阅读本章内容的同时对这些技术进行复习。严格来说,SSL(Secure Socket Layer)与TLS(Transport Layer Security)是不同的,TLS相当于是SSL的后续版本。不过,本章中所介绍的内容,大多是SSL和TLS两者兼备的,因此除具体介绍通信协议的部分以外,都统一写作SSL/TLS。
9.1 客户端与服务器
Bob书店是Alice经常光顾的一家网店,因为在Bob书店她可以搜索到新出版的图书,还可以通过信用卡快速完成支付,购买的书还能快递到家,真的很方便。
有一天,Alice 读了一本关于网络信息安全的书,书上说"互联网上传输的数据都是可以被窃听的"。Alice感到非常担心,自己在购买新书的时候输人的信用卡号会不会被窃听呢?
Alice看到Bob书店的网站下面写着一行字:"在以https://开头的网页中输人的信息将通过SSL/TLS发送以确保安全"。
的确,输人信用卡号的网页的URL是以 https:// 开头的,而不是一般的 http://。此外.在浏览这个网页时,Alice的web浏览器上还会显示一个小锁头的图标,看上去好像挺安全的。
但Alice心想,就算写着"通过SSL/TLS发送"我也不放心啊,到底在我的Web浏览器和Bob书店的网站之间都发生了哪些事呢?
本章将要介绍的技术一一SSL/TLS就可以解答Alice的疑问。当进行SSL/TLS通信时,Web浏览器上就会显示一个小锁头的图标。
**Alice的Web浏览器(客户端)和Bob书店的网站(服务器)进行HTTP通信**
Alice和Bob书店之间的通信,实际上是Alice所使用的Web浏览器和Bob书店的Web服务器之间的通信。Web浏览器是Alice的计算机上运行的一个程序,而web服务器则是在Bob书店的计算机上运行的一个程序,它们都遵循一种叫作HTTP(Hyper Text Transfer Protocol, 超文本传输协议)的协议(protocol)来进行通信。其中,Web浏览器称为HTTP客户端,Web服务器称为HTTP服务器。
当Alice点击网页上的链接或者输人URL时,Web浏览器就会通过网络向Web服务器发送一个 "我要浏览这个网页",的请求(request)。
Web服务器则将请求的网页内容发送给Web浏览器,以便对请求作出响应(response)。服务器和客户端之间所进行的处理就是请求和响应的往复。HTTP可以认为是在HTTP客户端与HTTP服务器之间进行请求和响应的规范。
Alice向Bob书店发送信用卡号也是使用HTTP来完成的(下图)。Alice输人信用卡号之后按下提交按钮,这时客户端(Web浏览器)就会将信用卡号作为HTTP请求发送给服务器。服务器则会将"生成订单"的网页作为HTTP响应返回给客户端。
不过,如果直接发送请求的话,信用卡号就很可能被窃听。下一节我们将探讨针对这种风险的对策。
**不使用SSL/TLS发送信用卡号的情形**
9.2 用SSL/TLS承载HTTP
什么是SSL,什么是TLS呢?官话说SSL是安全套接层(secure sockets layer),TLS是SSL的继任者,叫传输层安全(transport layer security)。说白点,就是在明文的上层和TCP层之间加上一层加密,这样就保证上层信息传输的安全。如HTTP协议是明文传输,加上SSL层之后,就有了雅称HTTPS。它存在的唯一目的就是保证上层通讯安全的一套机制。
当Web浏览器发送信用卡号时,信用卡号的数据会作为客户端请求发送给服务器。如果通信内容被窃听者Eve所窃取,Eve就会得到信用卡号。
于是,我们可以用SSL(Secure Socket Layer)或者TLS(Transport Layer Security)作为对通信进行加密的协议,然后在此之上承載HTTP(下图)。通过将两种协议进行叠加,我们就可以对HTTP的通信(请求和响应)进行加密,从而防止窃听。通过SSL/TLS进行通信时,URL不是以http://开头,而是以https://开头。
以上就是SSL/TLS的简单介绍。
在大致了解了SSL/TLS之后,我们来整理一下SSL/TLS到底负责哪些工作。我们想要实现的是,通过本地的浏览器访问网络上的web服务器,并进行安全的通信。用上边的例子来说就是,Alice希望通过web浏览器向Bob书店发送信用卡号。在这里,我们有几个必须要解决的问题。
- Alice的信用卡号和地址在发送到Bob书店的过程中不能被窃听。
- Alice的信用卡号和地址在发送到Bob书店的过程中不能被篡改。
- 确认通信对方的Web服务器是真正的Bob书店。
在这里,(1)是机密性的问题;(2)是完整性的问题;而(3)则是认证的问题。
要确保机密性,可以使用对称加密。由于对称加密算法的密钥不能被攻击者预测,因此我们使用伪随机数生成器来生成密钥。若要将对称加密的密钥发送给通信对象,可以使用非对称加密算法完成密钥交换。要识别篡改,对数据进行认证,可以使用消息认证码。消息认证码是使用单向散列函数来实现的。要对通信对象进行认证,可以使用对公钥加上数字签名所生成的证书。
好,工具已经找齐了,下面只要用一个"框架"(framework)将这些工具组合起来就可以了。SSL/TIS协议其实就扮演了这样一种框架的角色。
SSL/TLS也可以保护其他的协议
刚才我们提到用SSL/TLS承载HTTP通信,这是因为HTTP是一种很常用的协议。其实SSL/TLS上面不仅可以承载HTTP,还可以承载其他很多协议。例如,发送邮件时使用的SMTP(Simple Mail Transfer Protocol, 简单邮件传输协议)和接收邮件时使用的POP3(Post Office Protocol,邮局协议)都可以用SSL/TLS进行承载。在这样的情况下,SSL/TLS就可以对收发的邮件进行保护。
用SSL/TLS承载HTTP、SMTP和POP3的结构如下图所示。一般的电子邮件软件都可以完成发送和接收邮件这两种操作,其实是同时扮演了SMTP客户端和POP3客户端这两种角色。
- SSL :(Secure Socket Layer,安全套接字层),为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取。当前版本为3.0。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。
SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层: SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。- TLS :(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。
TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1,它是写入了 RFC 的。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。SSL/TLS协议提供的服务主要有:
- 认证用户和服务器,确保数据发送到正确的客户机和服务器;
- 加密数据以防止数据中途被窃取;
- 维护数据的完整性,确保数据在传输过程中不被改变。
9.3 https
-
描述的是客户端和服务器刚建立连接之后做的事情
第一次
- 客户端连接服务器
- 客户端使用的ssl版本, 客户端支持的加密算法
- 服务器
- 先将自己支持ssl版本和客户端的支持的版本比较
- 支持的不一样, 连接断开
- 支持的一样, 继续
- 根据得到的客户端支持 的加密算法, 找一个服务器端也同样支持算法, 发送给客户端
- 需要发送服务器的证书给客户端
- 先将自己支持ssl版本和客户端的支持的版本比较
第二次:
客户端:
- 接收服务器的证书
- 校验证书的信息
- 校验证书的签发机构
- 证书的有效期
- 证书中支持 的域名和访问的域名是否一致
- 校验有问题, 浏览器会给提示
- 客户端连接服务器
3. https -> 单向认证
- 服务器要准备的
- 生成密钥对
- 将公钥发送给ca, 由ca签发证书
- 将ca签发的证书和非对称加密的私钥部署到当前的web服务器
- 通信流程
- 客户端连接服务器, 通过一个域名
- 域名和IP地址的关系
- 域名要绑定IP地址
- 一个域名只能绑定一个IP地址
- IP地址需要被域名绑定
- 一个IP地址可以被多个域名绑定
- 域名要绑定IP地址
- 客户端访问的域名会别解析成IP地址, 通过IP地址访问web服务器
- 域名和IP地址的关系
- 服务器收到了客户端的请求
- 服务器将CA签发的证书发送给浏览器(客户端)
- 客户端拿到了服务器的公钥证书
- 读这个公钥 证书
- 验证域名
- 有效期
- ca签发机构
- 服务器的公钥
- 读这个公钥 证书
- 客户会生成一个随机数 (作为对称加密的秘钥来使用的)
- 使用服务器的公钥就这个随机数进行加密
- 将这个加密之后 秘钥发送给服务器
- 服务器对收到的密文解密
- 使用服务器的私钥解密, 得到对称加密的秘钥
- 数据的传输
- 使用对称加密的方式对数据进行加密
- 客户端连接服务器, 通过一个域名
9.3.1 http和https
HTTP协议:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL/TLS层,HTTPS的安全基础是SSL/TLS,因此加密的详细内容就需要SSL/TLS。
HTTPS协议的主要作用可以分为两种:
- 建立一个信息安全通道,来保证数据传输的安全;
- 确认网站的真实性。
HTTPS和HTTP的区别主要如下:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、
身份认证的网络协议,比http协议安全。
9.3.2 https优缺点
-
https的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:
- 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
- HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
- HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
- 谷歌曾在2014年8月份调整搜索引擎算法,并称 "比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高"。
-
https的缺点
虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:
- HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;
- HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
- SSL/TLS证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
- SSL/TLS证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
- HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
4. 自签名证书
-
使用openssl生成自签名证书
-
创建一个目录如Mytest, 进入该目录, 在该目录下打开命令行窗口
-
启动openssl
shellopenssl # 执行该命令即可
-
使用openssl工具生成一个RSA私钥, 注意:生成私钥,需要提供一个至少4位的密码。
shellgenrsa -des3 -out server.key 2048 - des3: 使用3des对私钥进行加密
-
生成CSR(证书签名请求)
shellreq -new -key server.key -out server.csr
-
删除私钥中的密码, 第一步给私钥文件设置密码是必须要做的, 如果不想要可以删掉
shellrsa -in server.key -out server.key -out 参数后的文件名可以随意起
-
生成自签名证书
shellx509 -req -days 365 -in server.csr -signkey server.key -out server.crt
-
参考资料
PKCS15个标准
PKCS 全称是 Public-Key Cryptography Standards ,是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准。
可以到官网上看看 What is PKCS
PKCS 目前共发布过 15 个标准:
- PKCS#1:RSA加密标准。PKCS#1定义了RSA公钥函数的基本格式标准,特别是数字签名。它定义了数字签名如何计算,包括待签名数据和签名本身的格式;它也定义了PSA公/私钥的语法。
- PKCS#2:涉及了RSA的消息摘要加密,这已被并入PKCS#1中。
- PKCS#3:Diffie-Hellman密钥协议标准。PKCS#3描述了一种实现Diffie- Hellman密钥协议的方法。
- PKCS#4:最初是规定RSA密钥语法的,现已经被包含进PKCS#1中。
- PKCS#5:基于口令的加密标准。PKCS#5描述了使用由口令生成的密钥来加密8位位组串并产生一个加密的8位位组串的方法。PKCS#5可以用于加密私钥,以便于密钥的安全传输(这在PKCS#8中描述)。
- PKCS#6:扩展证书语法标准。PKCS#6定义了提供附加实体信息的X.509证书属性扩展的语法(当PKCS#6第一次发布时,X.509还不支持扩展。这些扩展因此被包括在X.509中)。
- PKCS#7:密码消息语法标准。PKCS#7为使用密码算法的数据规定了通用语法,比如数字签名和数字信封。PKCS#7提供了许多格式选项,包括未加密或签名的格式化消息、已封装(加密)消息、已签名消息和既经过签名又经过加密的消息。
- PKCS#8:私钥信息语法标准。PKCS#8定义了私钥信息语法和加密私钥语法,其中私钥加密使用了PKCS#5标准。
- PKCS#9:可选属性类型。PKCS#9定义了PKCS#6扩展证书、PKCS#7数字签名消息、PKCS#8私钥信息和PKCS#10证书签名请求中要用到的可选属性类型。已定义的证书属性包括E-mail地址、无格式姓名、内容类型、消息摘要、签名时间、签名副本(counter signature)、质询口令字和扩展证书属性。
- PKCS#10:证书请求语法标准。PKCS#10定义了证书请求的语法。证书请求包含了一个唯一识别名、公钥和可选的一组属性,它们一起被请求证书的实体签名(证书管理协议中的PKIX证书请求消息就是一个PKCS#10)。
- PKCS#11:密码令牌接口标准。PKCS#11或"Cryptoki"为拥有密码信息(如加密密钥和证书)和执行密码学函数的单用户设备定义了一个应用程序接口(API)。智能卡就是实现Cryptoki的典型设备。注意:Cryptoki定义了密码函数接口,但并未指明设备具体如何实现这些函数。而且Cryptoki只说明了密码接口,并未定义对设备来说可能有用的其他接口,如访问设备的文件系统接口。
- PKCS#12:个人信息交换语法标准。PKCS#12定义了个人身份信息(包括私钥、证书、各种秘密和扩展字段)的格式。PKCS#12有助于传输证书及对应的私钥,于是用户可以在不同设备间移动他们的个人身份信息。
- PDCS#13:椭圆曲线密码标准。PKCS#13标准当前正在完善之中。它包括椭圆曲线参数的生成和验证、密钥生成和验证、数字签名和公钥加密,还有密钥协定,以及参数、密钥和方案标识的ASN.1语法。
- PKCS#14:伪随机数产生标准。PKCS#14标准当前正在完善之中。为什么随机数生成也需要建立自己的标准呢?PKI中用到的许多基本的密码学函数,如密钥生成和Diffie-Hellman共享密钥协商,都需要使用随机数。然而,如果"随机数"不是随机的,而是取自一个可预测的取值集合,那么密码学函数就不再是绝对安全了,因为它的取值被限于一个缩小了的值域中。因此,安全伪随机数的生成对于PKI的安全极为关键。
- **PKCS#15:密码令牌信息语法标准。**PKCS#15通过定义令牌上存储的密码对象的通用格式来增进密码令牌的互操作性。在实现PKCS#15的设备上存储的数据对于使用该设备的所有应用程序来说都是一样的,尽管实际上在内部实现时可能所用的格式不同。PKCS#15的实现扮演了翻译家的角色,它在卡的内部格式与应用程序支持的数据格式间进行转换。