- 数字签名 — 消息到底是谁写的
- 数字签名是一种将相当于现实世界中的盖章、签字的功能在计算机世界中进行实现的技术。使用数字签名可以识别篡改和伪装,还可以防止否认。
- 我们假设Alice使用的密钥是一个只有Alice自己才知道的私钥。当Alice发送消息时,她用私钥生成一个“签名"。相对地,接收者Bob则使用一个和Alice不同的密钥对签名进行验证。使用Bob的密钥无法根据消息生成签名,但是用Bob的密钥却可以对Alice所计算的签名进行验证,也就是说可以知道这个签名是否是通过Alice的密钥计算出来的。如果真有这么一种方法的话,那么不管是识别篡改、伪装还是防止否认就都可以实现了吧 ?
- 实际上,这种看似很神奇的技术早就已经问世了,这就是数字签名(digital signat.ure)。
1.签名的生成和验证
- 生成消息签名这一行为是由消息的发送者Alice来完成的,也称为“对消息签名”。生成签名就是根据消息内容计算数字签名的值,这个行为意味着 “我认可该消息的内容"。
- 验证数字签名这一行为一般是由消息的接收者Bob来完成的,但也可以由需要验证消息的第三方来完成,这里的第三方我们暂且将其命名为验证者Victor。验证签名就是检查该消息的签名是否真的属于Alice,验证的结果可以是成功或者失败,成功就意味着这个签名是属于Alice的,失败则意味着这个签名不是属于Alice的。
- 在数字签名中,生成签名和验证签名这两个行为需要使用各自专用的密钥来完成。
- Alice使用“签名密钥"来生成消息的签名,而Bob和Victor则使用“验证密钥"来验证消息的签名。数字签名对签名密钥和验证密钥进行了区分,使用验证密钥是无法生成签名的。这一点非常重要。此外,签名密钥只能由签名的人持有,而验证密钥则是任何需要验证签名的人都可以持有。
- 刚才讲的这部分内容,是不是觉得似曾相识呢?
- 没错,这就是我们讲过的非对称加密。公钥密码和上面讲的数字签名的结构非常相似。在非对称加密中,密钥分为加密密钥和解密密钥,用加密密钥无法进行解密。此外,解密密钥只能由需要解密的人持有,而加密密钥则是任何需要加密的人都可以持有。你看,数字签名和非对称加密是不是很像呢?
- 实际上,数字签名和非对称加密有着非常紧密的联系,简而言之,数字签名就是通过将非对称加密 “反过来用” 而实现的。下面我们来将密钥的使用方式总结成一张表:
/ |
私钥 |
公钥 |
非对称加密 |
接收者解密时使用 |
发送者加密时使用 |
数字签名 |
签名者生成签名时使用 |
验证者验证签名时使用 |
谁持有秘钥? |
个人持有 |
只要需要,任何人都可以持有 |
2.数字签名的方法
- 直接对消息签名的方法比较容易理解,但实际上并不会使用;对消息的散列值签名的方法稍微复杂一点,但实际中我们一般都使用这种方法。
- 使用直接对消息签名的方法,需要对整个消息进行加密,非常耗时,这是因为非对称加密算法本来就非常慢。那么,我们能不能生成一条很短的数据来代替消息本身呢?这就是单向散列函数。
- 于是我们不必再对整个消息进行加密(即对消息签名),而是只要先用单向散列函数求出消息的散列值,然后再将散列值进行加密(对散列值签名)就可以了。无论消息有多长,散列值永远都是这么短,因此对其进行加密(签名)是非常轻松的。
- (1)Alice用单向散列函数计算消息的散列值。
- (2)Alice用自己的私钥对散列值进行加密。
- 用私钥加密散列值所得到的密文就是Alice对这条散列值的签名,由于只有Alice才持有自己的私钥因此,除了Alice以外,其他人是无法生成相同的签名(密文)的。
- (3)Alice将消息和签名发送给Bob。
- (4)Bob用Alice的公钥对收到的签名进行解密。
- 如果收到的签名确实是用Alice的私钥进行加密而得到的密文(签名),那么用Alice的公钥应该能够正确解密,解密的结果应该等于消息的散列值。如果收到的签名不是用Alice的私钥进行加密而得到的密文,那么就无法用Alice的公钥正确解密(解密后得到的数据看起来是随机的)。
- (5)Bob将签名解密后得到的散列值与Alice直接发送的消息的散列值进行对比。
- 如果两者一致,则签名验证成功;如果两者不一致,则签名验证失败。
3.通过RSA实现数字签名
package main
import (
"errors"
"os"
"fmt"
"encoding/pem"
"crypto/x509"
"crypto/sha256"
"crypto/rsa"
"crypto/rand"
"crypto"
"encoding/base64"
)
//生成数字签名 - 私钥
func SignatureRSA(str []byte) ([]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("解析私钥失败!!!")
}
// 6. 将数据通过哈希函数生成信息摘要
myHash := sha256.New()
myHash.Write(str)
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
}
//验证数字签名 - 公钥
func VerifyRSA(str []byte, sign []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())
_, 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)
// 7. 将数据通过哈希函数生成信息摘要
myHash := sha256.New()
myHash.Write(str)
result := myHash.Sum(nil)
// 7. 数据认证
err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA256, result, sign)
if err != nil {
return err
}
fmt.Println("数字签名验证成功, 恭喜o(* ̄︶ ̄*)o恭喜")
return nil
}
func main() {
str := []byte("渡远荆门外,来从楚国游。山随平野尽,江入大荒流。月下飞天境,云生结海楼。仍怜故乡水,万里送行舟。")
sign, _ := SignatureRSA(str)
fmt.Println("sign bytes=", sign)
fmt.Println("sign base64=", base64.StdEncoding.EncodeToString(sign))
VerifyRSA(str, sign)
}
sign bytes= [23 15 231 82 213 133 167 52 123 117 219 116 244 109 26 133 148 160 27 112 37 207 90 196 102 88 88 4 40 189 41 1 216 204 184 114 168 140 79 59 239 18 124 198 15 189 16 87 165 244 246 2 173 52 252 157 232 7 230 189 83 213 199 18 166 229 196 142 8 169 231 168 202 24 102 196 59 163 121 200 247 194 158 254 113 150 171 66 180 126 103 63 224 71 149 130 209 12 218 35 172 186 241 242 243 172 229 138 156 156 121 56 189 104 247 239 98 179 203 40 206 197 13 64 235 52 125 175 46 134 27 125 20 146 58 202 177 246 71 249 110 16 42 181 63 27 10 207 96 75 38 0 121 219 71 138 160 123 174 254 108 18 255 36 113 127 98 67 201 106 100 209 152 99 210 143 53 190 51 47 17 130 81 16 152 227 183 90 187 38 88 33 213 128 23 194 208 50 191 100 241 5 89 122 73 108 7 35 156 88 110 83 46 163 46 106 88 35 115 244 187 32 110 33 74 117 205 83 153 28 246 22 1 226 206 80 81 217 142 191 251 145 4 89 10 24 21 52 250 20 215 250 108 171 181 33 101 0 126 140 223 36 2 203 145 193 62 59 158 35 136 46 163 148 141 246 84 245 76 222 255 64 168 158 152 1 133 33 199 215 163 64 200 80 184 144 186 169 65 81 131 49 81 178 223 58 147 140 200 96 228 107 25 150 252 234 24 128 223 178 149 26 204 244 214 17 232 47 64 24 182 7 179 182 197 45 113 53 85 220 239 100 62 58 59 210 196 118 75 94 12 237 194 24 59 236 192 172 91 127 236 69 0 22 102 165 26 69 84 125 229 117 121 155 75 159 140 67 142 118 218 48 114 87 198 36 119 213 171 231 213 99 156 57 244 121 12 221 53 41 208 123 55 24 207 60 35 4 133 47 99 178 246 230 234 208 29 83 131 211 141 184 41 67 189 0 235 39 66 44 242 117 3 11 96 143 55 199 249 53 98 48 36 92 248 107 98 246 16 244 173 106 169 206 143 68 141 24 70 202 118 51 110 107 77 108 13 254 141 178 203 72 103 144 8 95 67 203 79 28 65 26 144 210 131 92 83 26 201 236 108 156 8 203 163 196 135 139 204 179 193 161 223 177 143 166 141 253 126 1 98 58]
sign base64= Fw/nUtWFpzR7ddt09G0ahZSgG3Alz1rEZlhYBCi9KQHYzLhyqIxPO+8SfMYPvRBXpfT2Aq00/J3oB+a9U9XHEqblxI4IqeeoyhhmxDujecj3wp7+cZarQrR+Zz/gR5WC0QzaI6y68fLzrOWKnJx5OL1o9+9is8sozsUNQOs0fa8uhht9FJI6yrH2R/luECq1PxsKz2BLJgB520eKoHuu/mwS/yRxf2JDyWpk0Zhj0o81vjMvEYJREJjjt1q7Jlgh1YAXwtAyv2TxBVl6SWwHI5xYblMuoy5qWCNz9LsgbiFKdc1TmRz2FgHizlBR2Y6/+5EEWQoYFTT6FNf6bKu1IWUAfozfJALLkcE+O54jiC6jlI32VPVM3v9AqJ6YAYUhx9ejQMhQuJC6qUFRgzFRst86k4zIYORrGZb86hiA37KVGsz01hHoL0AYtgeztsUtcTVV3O9kPjo70sR2S14M7cIYO+zArFt/7EUAFmalGkVUfeV1eZtLn4xDjnbaMHJXxiR31avn1WOcOfR5DN01KdB7NxjPPCMEhS9jsvbm6tAdU4PTjbgpQ70A6ydCLPJ1AwtgjzfH+TViMCRc+Gti9hD0rWqpzo9EjRhGynYzbmtNbA3+jbLLSGeQCF9Dy08cQRqQ0oNcUxrJ7GycCMujxIeLzLPBod+xj6aN/X4BYjo=
数字签名验证成功, 恭喜o(* ̄︶ ̄*)o恭喜
4.使用椭圆曲线实现数字签名
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha1"
"crypto/x509"
"encoding/pem"
"math/big"
"os"
"fmt"
)
// 1. 生成密钥对
func GenerateEccKey() {
//1. 使用ecdsa生成密钥对
privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
panic(err)
}
//2. 将私钥写入磁盘
//- 使用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)
}
pem.Encode(file, &block)
file.Close()
//3. 将公钥写入磁盘
//- 从私钥中得到公钥
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()
}
// ecc签名 - 私钥
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
}
// ecc签名认证
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)
// 将rText, sText -> int数据
var r, s big.Int
r.UnmarshalText(rText)
s.UnmarshalText(sText)
//6. 签名的认证 - > ecdsa (问题,api的设计为什么在这个地方要传地址,直接传值比较不是更好吗?)
bl := ecdsa.Verify(publicKey, hashText[:], &r, &s)
return bl
}
func main() {
GenerateEccKey()
src := []byte("渡远荆门外,来从楚国游。山随平野尽,江入大荒流。月下飞天境,云生结海楼。仍怜故乡水,万里送行舟。")
rText, sText := EccSignature(src, "eccPrivate.pem")
bl := EccVerify(src, rText, sText, "eccPublic.pem")
fmt.Println(string(rText))
fmt.Println(string(sText))
fmt.Println(bl)
}
4748556469811711770356827896122337147679112299307482168408298292285324546621124117868914219721410378588515124505778071499564164536054118686706937431088847779
1739763135715799979289674176414981954582813645703922474358064293942534527402887507898992196394415331796652548282688533310553977580370932546926344142581819587
true
5.数字签名无法解决的问题
- 用数字签名既可以识别出篡改和伪装,还可以防止否认。也就是说,我们同时实现了确认消息的完整性、进行认证以及否认防止。现代社会中的计算机通信从这一技术中获益匪浅。
- 然而,**要正确使用数字签名,有一个大前提,那是用于验证签名的公钥必须属于真正的发送者。**即便数字签名算法再强大,如果你得到的公钥是伪造的,那么数字签名也会完全失效。
- 现在我们发现自己陷人了一个死循环一一一数字签名是用来识别消息篡改、伪装以及否认的,但是为此我们又必须从没有被伪装的发送者得到没有被篡改的公钥才行。
- 为了能够确认自己得到的公钥是否合法,我们需要使用证书。所谓证书,就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。
- 当然,这样的方法只是把问题转移了而已。为了对证书上施加的数字签名进行验证,我们必定需要另一个公钥,那么如何才能构筑一个可信的数字签名链条呢?又由谁来颁发可信的证书呢?到这一步,我们就已经踏入了社会学的领域。我们需要让公钥以及数字签名技术成为一种社会性的基础设施,即公钥基础设施(Public Key Intrastructure),简称PKIO。