Go-加密学 - 消息认证码/数字签名

Posted xiangjai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go-加密学 - 消息认证码/数字签名相关的知识,希望对你有一定的参考价值。

1. 消息认证码

1.1 消息认证

消息认证码(message authentication code)是一种确认完整性并进行认证的技术,取三个单词的首字母,简称为MAC。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CagaqHDp-1654826662895)(assets/1539176665517.png)]

  • 思考改进方案?

    从哈希函数入手

    需要将要发送的数据进行哈希运算, 将哈希值和原始数据一并发送

    需要在进行哈希运算的时候引入加密的步骤

    • 在alice对数据进行哈希运算的时候引入一个秘钥, 让其参与哈希运算, 生成散列值
    • bob对数据校验
      • bob收到原始和散列值之后,
        • 处理原始数据: 通过秘钥和哈希算法对原始数据生成散列值
        • 散列值比较: 生成的散列值 和 接收到的散列值进行比对

1.2 消息认证码的使用步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KNhivAA1-1654826662903)(assets/1539177690828.png)]

  1. 前提条件:
    • 在消息认证码生成的一方和校验的一方, 必须有一个秘钥
    • 双方约定好使用同样的哈希函数对数据进行运算
  2. 流程:
    • 发送者:
      • 发送原始法消息
      • 将原始消息生成消息认证码
        • ((原始消息) + 秘钥) * 函数函数 = 散列值(消息认证码)
      • 将消息认证码发送给对方
    • 接收者:
      • 接收原始数据
      • 接收消息认证码
      • 校验:
        • ( 接收的消息 + 秘钥 ) * 哈希函数 = 新的散列值
        • 通过新的散列值和接收的散列值进行比较

1.3 go中对消息认证码的使用

有一个包: crypto/hmac

func New(h func() hash.Hash, key []byte) hash.Hash
- 返回值: hash接口
- 参数1: 函数函数的函数名
	sha1.new
	md5.new
	sha256.new
- 参数2: 秘钥

第二步: 添加数据
type Hash interface 
    // 通过嵌入的匿名io.Writer接口的Write方法向hash中添加更多数据,永远不返回错误
    io.Writer
    // 返回添加b到当前的hash值后的新切片,不会改变底层的hash状态
    Sum(b []byte) []byte
    // 重设hash为无数据输入的状态
    Reset()
    // 返回Sum会返回的切片的长度
    Size() int
    // 返回hash底层的块大小;Write方法可以接受任何大小的数据,
    // 但提供的数据是块大小的倍数时效率更高
    BlockSize() int

type Writer interface 
    Write(p []byte) (n int, err error)

第三步: 计算散列值

1.4 消息认证码的问题

  1. 弊端
    • 有秘钥分发困难的问题
  2. 无法解决的问题
    • 不能进行第三方证明
    • 不能防止否认

2. 数字签名

2.1 签名的生成和验证

  1. 签名
    • 有原始数据对其进行哈希运算 -> 散列值
    • 使用非对称加密的私钥对散列值加密 -> 签名
    • 将原始数据和签名一并发送给对方
  2. 验证
    • 接收数据
      • 原始数据
      • 数字签名
    • 数字签名, 需要使用公钥解密, 得到散列值
    • 对原始数据进行哈希运算得到新的散列值

2.2 非对称加密和数字签名

总结:

  1. 数据通信
    • 公钥加密, 私钥解密
  2. 数字签名:
    - 私钥加密, 公钥解密

2.3 数字签名的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBr7B5tS-1654826662907)(assets/1539177860475.png)]

2.4 使用RSA进行数字签名

  1. 使用rsa生成密钥对

    1. 生成密钥对
    2. 序列化
    3. 保存到磁盘文件
  2. 使用私钥进行数字签名

    1. 打开磁盘的私钥文件

    2. 将私钥文件中的内容读出

    3. 使用pem对数据解码, 得到了pem.Block结构体变量

    4. x509将数据解析成私钥结构体 -> 得到了私钥

    5. 创建一个哈希对象 -> md5/sha1

    6. 给哈希对象添加数据

    7. 计算哈希值

    8. 使用rsa中的函数对散列值签名

      func 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: 错误信息
      
  3. 使用公钥进行签名认证

    1. 打开公钥文件, 将文件内容读出 - []byte

    2. 使用pem解码 -> 得到pem.Block结构体变量

    3. 使用x509对pem.Block中的Bytes变量中的数据进行解析 -> 得到一接口

    4. 进行类型断言 -> 得到了公钥结构体

    5. 对原始消息进行哈希运算(和签名使用的哈希算法一致) -> 散列值

      1. 创建哈希接口
      2. 添加数据
      3. 哈希运算
    6. 签名认证 - rsa中的函数

      func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error)
      参数1: 公钥
      参数2: 哈希算法 -> 与签名使用的哈希算法一致
      参数3: 将原始数据进行哈希原始得到的散列值
      参数4: 签名的字符串
      返回值: 
      	- nil -> 验证成功
      	- !=nil -> 失败
      

2.5 使用椭圆曲线进行数字签名

椭圆曲线在go中对应的包: import “crypto/elliptic”

使用椭圆曲线在go中进行数字签名: import “crypto/ecdsa”

美国FIPS186-2标准, 推荐使用5个素域上的椭圆曲线, 这5个素数模分别是:

P192 = 2192 - 264 - 1

P224 = 2224 - 296 + 1

P256 = 2256 - 2224 + 2192 - 296 -1

P384 = 2384 - 2128 - 296 + 232 -1

P512 = 2512 - 1

  1. 秘钥对称的生成, 并保存到磁盘

    1. 使用ecdsa生成密钥对

      func GenerateKey(c elliptic.Curve, rand io.Reader) (priv *PrivateKey, err error)
      
    2. 将私钥写入磁盘

      • 使用x509进行序列化

        func MarshalECPrivateKey(key *ecdsa.PrivateKey) ([]byte, error)
        
      • 将得到的切片字符串放入pem.Block结构体中

        block := pem.Block

        Type : “描述…”,

        Bytes : MarshalECPrivateKey返回值中的切片字符串,

      • 使用pem编码

        pem.Encode();

    3. 将公钥写入磁盘

      • 从私钥中得到公钥

      • 使用x509进行序列化

        func MarshalPKIXPublicKey(pub interface) ([]byte, error)
        
      • 将得到的切片字符串放入pem.Block结构体中

        block := pem.Block

        Type : “描述…”,

        Bytes : MarshalECPrivateKey返回值中的切片字符串,

      • 使用pem编码

        pem.Encode();

  2. 使用私钥进行数字签名

    1. 打开私钥文件, 将内容读出来 ->[]byte

    2. 使用pem进行数据解码 -> pem.Decode()

    3. 使用x509, 对私钥进行还原

      func ParseECPrivateKey(der []byte) (key *ecdsa.PrivateKey, err error)
      
    4. 对原始数据进行哈希运算 -> 散列值

    5. 进行数字签名

      func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)
      - 得到的r和s不能直接使用, 因为这是指针
      	应该将这两块内存中的数据进行序列化 -> []byte
      	func (z *Int) MarshalText() (text []byte, err error)
      
  3. 使用公钥验证数字签名

    1. 打开公钥文件, 将里边的内容读出 -> []byte

    2. pem解码 -> pem.Decode()

    3. 使用x509对公钥还原

      func ParsePKIXPublicKey(derBytes []byte) (pub interface, err error)
      
    4. 将接口 -> 公钥

    5. 对原始数据进行哈希运算 -> 得到散列值

    6. 签名的认证 - > ecdsa

      func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
      - 参数1: 公钥
      - 参数2: 原始数据生成的散列值
      - 参数3,4: 通过签名得到的连个点
      	func (z *Int) UnmarshalText(text []byte) error
      

2.6 数字签名无法解决的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6JhKTCq-1654826662911)(assets/1539178819165.png)]

3. 代码

3.1 消息认证码


import (
	"crypto/hmac"
	"crypto/sha1"
	"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)


// 生成消息认证码
func GenerateHamc(plainText, key []byte)[]byte 
	// 1.创建哈希接口, 需要指定使用的哈希算法, 和秘钥
	myhash := hmac.New(sha1.New, key)
	// 2. 给哈希对象添加数据
	myhash.Write(plainText)
	// 3. 计算散列值
	hashText := myhash.Sum(nil)
	return hashText


// 验证消息认证码
func VerifyHamc(plainText, key, hashText []byte) bool 
	// 1.创建哈希接口, 需要指定使用的哈希算法, 和秘钥
	myhash := hmac.New(sha1.New, key)
	// 2. 给哈希对象添加数据
	myhash.Write(plainText)
	// 3. 计算散列值
	hamc1 := myhash.Sum(nil)
	// 4. 两个散列值比较
	return hmac.Equal(hashText, hamc1)


3.2 RSA签名和验证

func main() 
	src := []byte("在消息认证码中,需要发送者和接收者之间共享密钥,而这个密钥不能被主动攻击者Mallory获取。如果这个密钥落入Mallory手中,则Mallory也可以计算出MAC值,从而就能够自由地进行篡改和伪装攻击,这样一来消息认证码就无法发挥作用了。")
	sigText := SignatureRSA(src, "private.pem")
	bl := VerifyRSA(src, sigText, "public.pem")
	fmt.Println(bl)


// RSA签名 - 私钥
func SignatureRSA(plainText []byte, fileName string) []byte
	//1. 打开磁盘的私钥文件
	file, err := os.Open(fileName)
	if err != nil 
		panic(err)
	
	//2. 将私钥文件中的内容读出
	info, err := file.Stat()
	if err != nil 
		panic(err)
	
	buf := make([]byte, info.Size())
	file.Read(buf)
	file.Close()
	//3. 使用pem对数据解码, 得到了pem.Block结构体变量
	block, _ := pem.Decode(buf)
	//4. x509将数据解析成私钥结构体 -> 得到了私钥
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil 
		panic(err)
	
	//5. 创建一个哈希对象 -> md5/sha1 -> sha512
	// sha512.Sum512()
	myhash := sha512.New()
	//6. 给哈希对象添加数据
	myhash.Write(plainText)
	//7. 计算哈希值
	hashText := myhash.Sum(nil)
	//8. 使用rsa中的函数对散列值签名
	sigText, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA512, hashText)
	if err != nil 
		panic(err)
	
	return sigText

// RSA签名验证
func VerifyRSA(plainText, sigText []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)
	//3. 使用x509对pem.Block中的Bytes变量中的数据进行解析 ->  得到一接口
	pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil 
		panic(err)
	
	//4. 进行类型断言 -> 得到了公钥结构体
	publicKey := pubInterface.(*rsa.PublicKey)
	//5. 对原始消息进行哈希运算(和签名使用的哈希算法一致) -> 散列值
	hashText := sha512.Sum512(plainText)
	//6. 签名认证 - rsa中的函数
	err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA512, hashText[:], sigText)
	if err == nil 
		return true
	
	return false

3.3 椭圆曲线签名和验证


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)

// 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

以上是关于Go-加密学 - 消息认证码/数字签名的主要内容,如果未能解决你的问题,请参考以下文章

Go-加密学 - 对称加密

密码技术--消息认证码及Go语言应用

Go-加密学 - 证书/SSL/TLS/https单向认证/思维导图

Go-加密学 - 证书/SSL/TLS/https单向认证/思维导图

Go-加密学 - 证书/SSL/TLS/https单向认证/思维导图

Go-哈希函数与消息认证详解(含代码)