数字签名(go语言实践)

"数字签名 --- 消息到底是谁写的"

数字签名是一种将相当于现实世界中的盖章、签字的功能在计算机世
界中进行实现的技术。使用数字签名可以识别篡改和伪装,还可以防止否认。

1. 从消息认证到数字签名

1.1. 消息认证码的局限性

我们可以识别消息是否被篡改或者发送者身份是否被伪装,也就是可 以校验消息的完整性,还可以对消息进行认证。然而,比如在出具借条的场景中却无法使用消息认证码, 因为消息认证码无法防止否认。

消息认证码之所以无法防止否认,是因为消息认证码需要在发送者Alice和接收者Bob两者之间共享同一个 密钥。正是因为密钥是共享的,所以能够使用消息认证码计算出正确MAC值的并不只有发送者Alice,接收 者Bob也可以计算出正确的MAC值。由于Alice和Bob双方都能够计算出正确的MAC值,因此对于第三方来 说,我们无法证明这条消息的确是由Alice生成的。

1.2. 通过数字签名解决问题

下面请大家开动一下脑筋。假设发送者Alice和接收者Bob不需要共享一个密钥,也就是说,Alice和Bob各自使用不同的密钥。

我们假设Alice使用的密钥是一个只有Alice自己才知道的私钥。当Alice发送消息时,她用私钥生成一个“签 名"。相对地,接收者Bob则使用一个和Alice不同的密钥对签名进行验证。使用Bob的密钥无法根据消息生 成签名,但是用Bob的密钥却可以对Alice所计算的签名进行验证,也就是说可以知道这个签名是否是通过 Alice的密钥计算出来的。如果真有这么一种方法的话,那么不管是识别篡改、伪装还是防止否认就都可以 实现了吧 ?

2. 签名的生成和验证

在数字签名技术中,出现了下面两种行为:

  • 生成消息签名的行为
  • 验证消息签名的行为

生成消息签名 这一行为是由消息的发送者Alice来完成的,也称为“对消息签名”。生成签名就是根据消息内容计 算数字签名的值,这个行为意味着 “我认可该消息的内容"。

验证数字签名 这一行为一般是由消息的接收者Bob来完成的,但也可以由需要验证消息的第三方来完成,这里 的第三方我们暂且将其命名为验证者Victor。验证签名就是检查该消息的签名是否真的属于Alice,验证的结果可 以是成功或者失败,成功就意味着这个签名是属于Alice的,失败则意味着这个签名不是属于Alice的。

在数字签名中,生成签名和验证签名这两个行为需要使用各自专用的密钥来完成。

数字签名就是通过将非对称加密 “反过来用” 而实现的。下面我们来将密钥的使用方式总结成一张表:

私钥 公钥
非对称加密 接收者解密时使用 发送者加密时使用
数字签名 签名者生成签名时使用 验证者验证签名时使用
谁持有秘钥? 个人持有 只要需要,任何人都可以持有

3. 非对称加密和数字签名

要实现数字签名,我们可以使用之前介绍的非对称加密。非对称加密包括一个由公钥和私钥组成的密钥对, 其中公钥用于加密,私钥用于解密。

数字签名中也同样会使用公钥和私钥组成的密钥对,不过这两个密钥的用法和非对称加密是相反的,即用私钥 加密相当于生成签名,而用公钥解密则相当于验证签名。

image.png

用公钥加密所得到的密文,只能用与该公钥配对的私钥才能解密:同样地,用私钥加密所得到的密文,也只能
用与该私钥配对的公钥才能解密。也就是说,如果用某个公钥成功解密了密文,那么就能够证明这段密文是用与该公钥配对的私钥进行加密所得到的。

用私钥进行加密这一行为只能由持有私钥的人完成,正是基于这一事实,我们才可以将用私钥加密的密文作为签名来对待。

由于公钥是对外公开的,因此任何人都能够用公钥进行解密,这就产生了一个很大的好处,即任何人都能够对签名进行验证。

Alice对消息的散列值签名, Bob验证签名.png

4. 通过RSA实现数字签名

前边章节已经介绍过了如何通过自己编写的go代码生成非对称加密算法RSA的公钥和私钥文件, 假设公钥文件的 文件名为 publicKey.pem,私钥文件对应的文件名为 privateKey.pem。

生成签名:

func GetRsaSignature(privatePath string, data []byte, hashType byhash.KHashType) ([]byte, error) {
    // 1. 从秘钥文件中读生成的秘钥内容
    fp, err := os.Open(privatePath)
    if err != nil {
        return nil, err
    }
    defer fp.Close()

    // 2. 读文件内容
    fileInfo, _ := fp.Stat()
    all := make([]byte, fileInfo.Size())
    _, err = fp.Read(all)
    if err != nil {
        return nil, err
    }

    // 3. 将数据解析成pem格式的数据块
    block, _ := pem.Decode(all)

    // 4. 解析pem数据块, 得到私钥
    privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        return nil, err
    }

    // 5. 将数据通过哈希函数生成信息摘要
    result,err := byhash.GetHashFromBytes(data,hashType)
    if err != nil {
        return nil,err
    }
    cryptoHash := func() crypto.Hash{
        switch hashType {
        case byhash.KHashTypeMd5:
            return crypto.MD5
        case byhash.KHashTypeSha1:
            return crypto.SHA1
        case byhash.KHashTypeSha256:
            return crypto.SHA256
        case byhash.KHashTypeSha512:
            return crypto.SHA512
        default:
            return crypto.SHA512
        }
    }()

    // 6. 生成签名
    mySignature, err := rsa.SignPKCS1v15(rand.Reader, privKey, cryptoHash, result)
    if err != nil {
        return nil, err
    }

    return mySignature, nil
}

验证签名:

func VerifyRsaSignature(publicPath string, sig, data []byte, hashType byhash.KHashType) (bool, error) {
    // 1. 从秘钥文件中读生成的秘钥内容

    fp, err := os.Open(publicPath)
    if err!=nil {
        return false, err
    }
    defer fp.Close()

    // 2. 读文件内容
    fileInfo, _ := fp.Stat()
    all := make([]byte, fileInfo.Size())
    _, err = fp.Read(all)
    if err!=nil{
        return false, err
    }

    // 3. 将公钥数据解析为pem格式的数据块
    block, _ := pem.Decode(all)

    // 4. 将公钥从pem数据块中提取出来
    pubKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
    if err != nil {
        return false, err
    }

    // 5. 将数据通过哈希函数生成信息摘要
    result,err := byhash.GetHashFromBytes(data,hashType)
    if err != nil {
        return false,err
    }

    // 6. 数据认证
    cryptoHash := func() crypto.Hash{
        switch hashType {
        case byhash.KHashTypeMd5:
            return crypto.MD5
        case byhash.KHashTypeSha1:
            return crypto.SHA1
        case byhash.KHashTypeSha256:
            return crypto.SHA256
        case byhash.KHashTypeSha512:
            return crypto.SHA512
        default:
            return crypto.SHA512
        }
    }()

    err = rsa.VerifyPKCS1v15(pubKey, cryptoHash, result, sig)
    if err != nil {
        return false, err
    }

    return true, nil

}

测试代码:

func testRsaSig()  {
    res, err := bysign.GetRsaSignature("privateKey.pem",[]byte("二愣子抗日"),byhash.KHashTypeSha512)
    fmt.Println(res, err)
    flag,err :=bysign.VerifyRsaSignature("publicKey.pem",res,[]byte("二愣子抗日"),byhash.KHashTypeSha512)
    fmt.Println(flag, err)
}

5. 通过椭圆曲线实现数字签名

生成椭圆曲线公钥私钥的代码见文章

签名代码:

// 使用椭圆曲线进行签名
func GetEccSignFromHashedData(dataHash []byte,privateKeyPath string)([]byte,[]byte){
    //获取私钥
    privateKey := GetEccPrivateKey(privateKeyPath)

    //使用私钥对任意长度的hash值进行签名,返回签名结果(一对大整数)
    r, s, _ := ecdsa.Sign(rand.Reader, privateKey, dataHash)

    rText, _ := r.MarshalText()
    sText, _ := s.MarshalText()

    return rText,sText
}

func GetEccPrivateKey(path string) *ecdsa.PrivateKey{
    // 从文件中读取数据
    file, _ := os.Open(path)
    fileInfo, _ := file.Stat()
    buf :=make([]byte,fileInfo.Size())
    file.Read(buf)
    defer file.Close()

    // pem解码
    block, _ := pem.Decode(buf)

    // x509解码
    privateKey, _ := x509.ParseECPrivateKey(block.Bytes)

    return privateKey
}

验证代码:

// 椭圆曲线校验签名的方法
func EccVerifyFromHashedData(publicKeyPath string,dataHash,rText,sText []byte)bool{
    // 获取公钥
    publicKey := GetEccPublicKey(publicKeyPath)

    var r,s big.Int
    r.UnmarshalText(rText)
    s.UnmarshalText(sText)

    // 校验签名
    result := ecdsa.Verify(publicKey, dataHash, &r, &s)
    return result
}

func GetEccPublicKey(path string) *ecdsa.PublicKey{
    // 从文件中读取数据
    file, _ := os.Open(path)
    fileInfo, _ := file.Stat()
    buf :=make([]byte,fileInfo.Size())
    file.Read(buf)
    defer file.Close()

    // pem解码
    block, _ := pem.Decode(buf)

    // x509解码
    publicKeyInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)

    return publicKeyInterface.(*ecdsa.PublicKey)
}

测试代码:

func testEccSig()  {
    dataHashed, _:= byhash.GetHashFromBytes([]byte("二愣子抗日"),byhash.KHashTypeSha512)
    //dataHashed, _:= byhash.GetHashFromFilePath("/Users/baoye/Desktop/111.itcast",byhash.KHashTypeSha512)
    r, s := bysign.GetEccSignFromHashedData(dataHashed, "eccPri.pem")
    flag := bysign.EccVerifyFromHashedData("eccPub.pem",dataHashed, r,s)
    fmt.Println(flag)
}

6. 数字签名无法解决的问题

用数字签名既可以识别出篡改和伪装,还可以防止否认。也就是说,我们同时实现了确认消息的完整性、进行认证以及否认防止。现代社会中的计算机通信从这一技术中获益匪浅。

然而, 要正确使用数字签名,有一个大前提,那是用于验证签名的公钥必须属于真正的发送者。 即便数字签名 算法再强大,如果你得到的公钥是伪造的,那么数字签名也会完全失效。

现在我们发现自己陷入了一个死循环一一一数字签名是用来识别消息篡改、伪装以及否认的,但是为此我们又必须从没有被伪装的发送者得到没有被篡改的公钥才行。

为了对证书上施加的数字签名进行验证,我们必定需要另一个公 钥,那么如何才能构筑一个可信的数字签名链条呢?又由谁来颁发可信的证书呢?到这一步,我们就已经踏人 了社会学的领域。我们需要让公钥以及数字签名技术成为一种社会性的基础设施,即公钥基础设施(Public Key Intrastructure),简称PKI

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • 在现代密码体制中有保密和认证两种机制,一般发送者和接收者拥有自己的公钥和密钥,公钥是公开的,密钥不公开。 保密机制...
    F麦子阅读 1,071评论 0 0
  • 原文地址 https://mbinary.coding.me/introduction-to-bitcoin.ht...
    mbinary阅读 5,124评论 0 4
  • 前言 文中首先解释加密解密的一些基础知识和概念,然后通过一个加密通信过程的例子说明了加密算法的作用,以及数字证书的...
    sunny冲哥阅读 2,984评论 0 2
  • 北京欢迎你,为你开天辟地,流动中的魅力,充满着朝气。 ――《北京欢迎你》 ...
    古上心月阅读 144评论 0 0
  • 假期的尾巴上。出去看看花。 光福香雪海,乾隆帝弘历先后六次到过的探梅地,慕名而走。 园内的梅花盆景展吸引了不少游客...
    绵绵雨丝阅读 199评论 0 0