数字签名算法(DSA)

数字签名算法(DSA,Digital Signature Algorithm),是一种公开密钥算法,不能用于加密,只能用于数字签名。主要用作为接收者验证数字的完整性和数据发送者的身份,DSA算法的安全性基于解离散对数的困难性。

package main

import (
    "crypto/dsa"
    "crypto/rand"
    "fmt"
)

func main() {
    var params dsa.Parameters

    //生成参数 
    if e := dsa.GenerateParameters(&params, rand.Reader, dsa.L1024N160); e != nil {
        fmt.Println(e)
    }

    //生成私钥 
    var priv dsa.PrivateKey

    priv.Parameters = params
    if e := dsa.GenerateKey(&priv, rand.Reader); e != nil {
        fmt.Println(e)
    }

    //根据私钥生成公钥 
    pub := priv.PublicKey

    //消息 
    message := []byte("hello world")

    //使用私钥进行签名,产生整数对(r,s) 
    r, s, e := dsa.Sign(rand.Reader, &priv, message)
    if e != nil {
        fmt.Println(e)
    }

    //认证 
    fmt.Printf("认证 %q (r:%s,s:%s)\n", message, r, s)
    if dsa.Verify(&pub, message, r, s) {
        fmt.Println("认证正确!")
    } else {
        fmt.Println("认证失败!")
    }
}

公钥加密算法(RSA)

公钥加密算法于1987年首次公开,RSA是提出这个算法的三人姓氏开头字母组成,可用于加密,也可以用于数字签名。RSA的安全性基于大数分解的困难性。

加密算法:

  • 最优非对称加密填充(OAEP,Optimal Asymmetric Encryption Padding),在随机预言模型下,用来处理非对称加密前的明文;

  • 公钥密码学标准(PKCS,The Public-Key Cryptography Standards),是由美国RSA数据安全公司及其合作伙伴制定的一组公钥密码学标准,其中包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。

签名认证:

  • 公钥密码学标准(PKCS);

  • 概率签名方案(PSS,Probabilistic Signature Scheme),与PKCS不同的是,它支持添加盐(Salt)。

package main

import (
    "crypto"
    "crypto/md5"
    "crypto/rand"
    "crypto/rsa"
    "fmt"
)

func main() {
    //生成私钥 
    priv, e := rsa.GenerateKey(rand.Reader, 1024)
    if e != nil {
        fmt.Println(e)
    }

    //根据私钥产生公钥 
    pub := &priv.PublicKey

    //明文 
    plaintext := []byte("Hello world")

    //加密生成密文 
    fmt.Printf("%q\n加密:\n", plaintext)
    ciphertext, e := rsa.EncryptOAEP(md5.New(), rand.Reader, pub, plaintext, nil)
    if e != nil {
        fmt.Println(e)
    }
    fmt.Printf("\t%x\n", ciphertext)

    //解密得到明文 
    fmt.Printf("解密:\n")
    plaintext, e = rsa.DecryptOAEP(md5.New(), rand.Reader, priv, ciphertext, nil)
    if e != nil {
        fmt.Println(e)
    }
    fmt.Printf("\t%q\n", plaintext)

    //消息先进行Hash处理 
    h := md5.New()
    h.Write(plaintext)
    hashed := h.Sum(nil)
    fmt.Printf("%q MD5 Hashed:\n\t%x\n", plaintext, hashed)

    //签名 
    opts := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthAuto, Hash: crypto.MD5}
    sig, e := rsa.SignPSS(rand.Reader, priv, crypto.MD5, hashed, opts)
    if e != nil {
        fmt.Println(e)
    }
    fmt.Printf("签名:\n\t%x\n", sig)

    //认证 
    fmt.Printf("验证结果:")
    if e := rsa.VerifyPSS(pub, crypto.MD5, hashed, sig, opts); e != nil {
        fmt.Println("失败:", e)
    } else {
        fmt.Println("成功.")
    }
}

椭圆曲线加密算法

ECDSA的全名是Elliptic Curve DSA,即椭圆曲线DSA。它是Digital Signature Algorithm (DSA)应用了椭圆曲线加密算法的变种。椭圆曲线算法的原理很复杂,但是具有很好的公开密钥算法特性,通过公钥无法逆向获得私钥。

  1. 签名过程

    假设要签名的消息是一个字符串:“Hello World!”。DSA签名的第一个步骤是对待签名的消息生成一个消息摘要。不同的签名算法使用不同的消息摘要算法。而ECDSA256使用SHA256生成256比特的摘要。
    摘要生成结束后,应用签名算法对摘要进行签名:
    产生一个随机数k
    利用随机数k,计算出两个大数r和s。将r和s拼在一起就构成了对消息摘要的签名。
    这里需要注意的是,因为随机数k的存在,对于同一条消息,使用同一个算法,产生的签名是不一样的。从函数的角度来理解,签名函数对同样的输入会产生不同的输出。因为函数内部会将随机值混入签名的过程。

  2. 验证过程
    关于验证过程,这里不讨论它的算法细节。从宏观上看,消息的接收方从签名中分离出r和s,然后利用公开的密钥信息和s计算出r。如果计算出的r和接收到的r值相同,则表示验证成功。否则,表示验证失败。

package main

import (
    "fmt"
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/sha256"
    "math/big"
)

func main() {
    //明文 
    message := []byte("Hello world")

    key, err := NewSigningKey()
    if err != nil {
        return
    }

    signature, err := Sign(message, key)

    fmt.Printf("签名后:%x\n", signature)
    if err != nil {
        return
    }

    if !Verify(message, signature, &key.PublicKey) {
        fmt.Println("验证失败!")
        return
    }else{
        fmt.Println("验证成功!")
    }
}

func NewSigningKey() (*ecdsa.PrivateKey, error) {
    key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    return key, err
}

// Sign signs arbitrary data using ECDSA.
func Sign(data []byte, privkey *ecdsa.PrivateKey) ([]byte, error) {
    // hash message
    digest := sha256.Sum256(data)

    // sign the hash
    r, s, err := ecdsa.Sign(rand.Reader, privkey, digest[:])
    if err != nil {
        return nil, err
    }

    // encode the signature {R, S}
    // big.Int.Bytes() will need padding in the case of leading zero bytes
    params := privkey.Curve.Params()
    curveOrderByteSize := params.P.BitLen() / 8
    rBytes, sBytes := r.Bytes(), s.Bytes()
    signature := make([]byte, curveOrderByteSize*2)
    copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
    copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)

    return signature, nil
}

// Verify checks a raw ECDSA signature.
// Returns true if it's valid and false if not.
func Verify(data, signature []byte, pubkey *ecdsa.PublicKey) bool {
    // hash message
    digest := sha256.Sum256(data)

    curveOrderByteSize := pubkey.Curve.Params().P.BitLen() / 8

    r, s := new(big.Int), new(big.Int)
    r.SetBytes(signature[:curveOrderByteSize])
    s.SetBytes(signature[curveOrderByteSize:])

    return ecdsa.Verify(pubkey, digest[:], r, s)
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐