密码学-公钥密码学

文章介绍了公钥密码学的基本原理,如非对称加密和数字签名,特别关注了以太坊中使用的椭圆曲线加密算法(ECC),包括ECC的数学基础、密钥生成以及地址生成过程。同时,提到了密钥协商的对称和非对称方法,如Diffie-Hellman算法和TLS/SSL握手协议,并阐述了哈希函数在区块链中验证内容和构建地址的重要作用。
摘要由CSDN通过智能技术生成

公钥密码学

定义:用一对密钥来加密和解密信息,公钥加密的信息只能由私钥解密,私钥加密的信息只能用公钥解密,也叫非对称密码学。广泛应用于数字签名、密钥协商、加密领域。
加密过程:A 将自己的公钥给 B ,B 将机密信息用 A 的公钥加密发送给 A,A再用自己的私钥对加密信息进行解密。区块链中使用非对称加密实现账户创建,并对交易和信息签名和签收。
数学原理:质因数分解、离散对数、椭圆曲线等难以计算的数学难题。

以太坊中的ECC

椭圆曲线定义:
y 2 = ( x 3 + a x + b ) m o d p y^2=(x^3 + ax + b) mod p y2=(x3+ax+b)modp
选择椭圆曲线的原因:

  1. 在这个曲线上加法是可定义的。
  2. 在这个曲线上的加法求解很简单。
  3. 在这个曲线上可以证明 对数运算是极复杂问题。

ECC原理:
基于椭圆曲线离散对数问题,是一个标准的椭圆曲线参数集,称为SECP-256k1。密钥长度为256位。在使用SECP-256k1曲线时,参与者获得长度为256位的公钥和私钥,私钥隐私,自己保存,公钥是公开的,用于加密数据和验证签名。

公钥私钥
生成私钥计算得到随机生成
属性公开隐私
用途加密数据、验证签名签名

基于ECC的算法细节

  1. 公钥产生算法(KeyGen):
    • 选择一条椭圆曲线函数 E p ( a , b ) E_p(a,b) Ep(a,b) 和基点G
    • 选择一个随机数作为私钥 d A ( d A < n , n 为该 G 的阶 ) d_A(d_A < n,n为该G的阶 ) dA(dA<n,n为该G的阶),利用基点 G 计算公钥 $Q_A = G \cdot d_A $
  2. 签名算法(sign):
    • 通过选择椭圆曲线和一个生成点,选择一个随机数k作为私钥,并计算公钥 K = k G K=kG K=kG
    • 对要签名的数据进行哈希,并使用私钥k对哈希结果进行数字签名,生成一个签名$ (S, R)$。
    • 使用公钥 K 和签名 ( S , R ) (S, R) (S,R)验证签名的有效性。

地址生成

过程:

  1. 根据ECC算法生成一对公钥和私钥,私钥随机生成,公钥则是私钥通过椭圆曲线点乘算法得到的一个点。
  2. 将公钥通过哈希算法(一般是kccak-256)进行处理,得到唯一的256位(32字节)哈希值。
  3. 取最后20字节(160位)作为以太坊钱包地址的值。
  4. 将这160位值进行Base58编码,得到以太坊钱包真正的地址。
算法用途
ECC算法私钥安全性
哈希算法钱包地址的唯一性
Base58编码钱包地址的易读性
package main

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "golang.org/x/crypto/sha3"
)

func main() {
    // 生成一个ECDSA密钥对
    privKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    
    // 获取公钥
    pubKey := privKey.PublicKey
    
    // 将公钥序列化为字符串形式,去掉前2个字节(04),并使用Keccak-256哈希算法生成一个32字节哈希值
    pubKeyBytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)[1:]
    hash := sha3.Sum256(pubKeyBytes)
    
    // 取哈希值的后20字节,得到以太坊钱包地址
    address := hex.EncodeToString(hash[12:])
    
    fmt.Printf("私钥: %x\n", privKey.D)
    fmt.Printf("公钥: %x\n", pubKeyBytes)
    fmt.Printf("以太坊钱包地址: 0x%s\n", address)
}

读代码:

  1. 生成密钥对
  • ecdsa.GenerateKey(elliptic.Curve,io.Reader)函数生成一个ECDSA的密钥对,接受两个参数:椭圆曲线函数类型和随机数生成器,这里 Curve 选择 P-256(以太坊使用的),随机数生成器选择 rand.Reader,标准库中提供的一个安全的随机数生成器。
    函数返回两个值,一个是 ECDSA 私钥,类型为 *ecdsa.Privkey,另一个是 error 类型,这里用 _ 忽略。
    • 如果错误需要处理
  privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
    // 处理错误,例如打印错误信息或记录日志
    fmt.Printf("生成ECDSA密钥对出错:%s\n", err)
    return
}
  • 私钥 = privKey.D , 公钥 = privKey.PublicKey
  1. 公钥取哈希
  • 在以太坊中为了提高公钥的可读性和便利性,方便在网络中传播,采用将公钥转换成字符串的方法,字符串形式的公钥称作公钥地址或简称地址。
    在生成钱包地址时,使用的字符串公钥需要减掉没用的前缀字节(0x04),可以减少求解哈希值的计算量。
    • 前缀字节的作用:区分不同的地址类型
地址类型前缀字节
主网地址0x00
测试网地址0x6F
Contract 地址0x00
EOA 地址0x00

数字签名

ECDSA数字签名算法包括三个步骤:

  1. 密钥生成:通过选择椭圆曲线和一个生成点,选择一个随机数k作为私钥,并计算公钥 K = k G K=kG K=kG
  2. 签名:对要签名的数据进行哈希,并使用私钥k对哈希结果进行数字签名,生成一个签名 ( S , R ) (S, R) (S,R)
  3. 验证:使用公钥K和签名 ( S , R ) (S, R) (S,R)验证签名的有效性。
package main

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/rand"
	"fmt"
	"log"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
)

func main() {
	// 选择椭圆曲线secp256k1和一个生成点
	curve := crypto.S256()
	pubkey := &ecdsa.PublicKey{Curve: curve}

	// 生成随机私钥
	privkey, err := ecdsa.GenerateKey(curve, rand.Reader)
	if err != nil {
		log.Fatal(err)
	}

	// 计算公钥
	pubkey.X, pubkey.Y = curve.ScalarBaseMult(privkey.D.Bytes())

	// 要签名的数据
	msg := []byte("hello world")

	// 哈希数据
	hash := crypto.Keccak256Hash(msg)

	// 对哈希结果进行数字签名
	r, s, err := ecdsa.Sign(rand.Reader, privkey, hash.Bytes())
	if err != nil {
		log.Fatal(err)
	}

	// 验证签名的有效性
	isValid := ecdsa.Verify(pubkey, hash.Bytes(), r, s)
	fmt.Printf("Signature is valid: %v\n", isValid)

	// 将签名(S, R)组成字节数组
	signature := make([]byte, 64)
	rbytes := r.Bytes()
	sbytes := s.Bytes()
	copy(signature[32-len(rbytes):32], rbytes)
	copy(signature[64-len(sbytes):64], sbytes)

	// 验证签名的有效性
	pubkeyHash := crypto.Keccak256Hash(pubkey.X.Bytes(), pubkey.Y.Bytes())
	address := crypto.PubkeyToAddress(*pubkey)

	fmt.Printf("Public key: %x\n", pubkeyHash)
	fmt.Printf("Address: %s\n", address.Hex())

	isValid = crypto.VerifySignature(pubkey, hash.Bytes(), signature)
	fmt.Printf("Signature is valid: %v\n", isValid)
}

读代码:

  1. 选择 secp256k1 椭圆曲线和一个生成点。
  2. 使用ecdsa.GenerateKey()方法生成一个随机私钥和公钥。
  3. 对要签名的数据进行哈希,并使用ecdsa.Sign()方法对哈希结果进行数字签名,生成一个签名。
  4. 使用公钥和签名验证签名的有效性,即使用ecdsa.Verify()方法。在验证签名时,我们还可以使用crypto.VerifySignature()方法来验证签名的有效性。
  5. 最后,此代码还输出了公钥的哈希值和地址。

密钥协商

在被攻击者窥探的情况下,客户端与服务器依靠密钥协商机制生成加密应用层数据的密钥(也称“会话密钥”)。解决身份认证前提下的“偷窥”问题。

  1. 对称密钥协商

    • 定义:通讯双方在没有对方任何预先信息的情况下在不安全的信道建立共享密钥,后续通信过程中通过该密钥进行加密和解密。数学原理基于求解离散对数问题的复杂性,确保即使通信过程被监听也不会泄露机密信息。

    • 过程:Diffie–Hellman 算法实现流程:

      1. 客户端先连上服务端
      2. 服务端生成一个随机数 s 作为自己的私钥,然后根据算法参数计算出公钥 S(算法参数通常是固定的)
      3. 服务端使用某种签名算法把“算法参数(模数p,基数g)和服务端公钥S”作为一个整体进行签名
      4. 服务端把“算法参数(模数p,基数g)、服务端公钥S、签名”发送给客户端
      5. 客户端收到后验证签名是否有效
      6. 客户端生成一个随机数 c 作为自己的私钥,然后根据算法参数计算出公钥 C
      7. 客户端把 C 发送给服务端
      8. 客户端和服务端(根据上述 DH 算法)各自计算出 k 作为会话密钥
    • 实例:

      1. Alice和Bob约定使用一个模 p = 23 和 g = 5
      2. Alice选择一个保密的整数 a = 4作为私钥,计算出公钥 A = g a m o d p A =g^a mod p A=gamodp,将公钥A发送给Bob; A = 5 4 m o d 23 = 4 A = 5^4 mod 23 = 4 A=54mod23=4
      3. Bob选在一个保密的整数 b = 3,计算出公钥 B = g b m o d p B = g^b mod p B=gbmodp,将公钥B发送给Alice;
      4. B = 5 3 m o d 23 = 10 B = 5^3 mod 23 = 10 B=53mod23=10
      5. Alice 计算出共享密钥 s = B a m o d p s = 1 0 4 m o d 23 = 18 s = B^a mod p s = 10^4 mod 23 = 18 s=Bamodps=104mod23=18
      6. Bob 计算共享密钥 s = A b m o d p s = 4 3 m o d 23 = 18 s = A^b mod p s = 4^3 mod 23 = 18 s=Abmodps=43mod23=18
      7. Alice和Bob现在就共享一个密钥(s = 18)
      • 注:P 得足够大(至少300位);a , b 也应足够大(大于100位);g 得足够小,不然会影响性能。
      • 缺点:无法防止中间人攻击,需要配合签名算法使用。
  2. 非对称密钥协商

    • TLS/SSL 握手流程:
      1. 客户端连上服务端
      2. 服务端发送 CA 证书给客户端
      3. 客户端验证该证书的可靠性
      4. 客户端从 CA 证书中取出公钥
      5. 客户端生成一个随机密钥 k,并用这个公钥加密得到 k’
      6. 客户端把 k’ 发送给服务端
      7. 服务端收到 k’ 后用自己的私钥解密得到 k
      8. 此时双方都得到了密钥 k,协商完成。
    • 实例:
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net"
)

func main() {
	// 服务端配置
	cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
	if err != nil {
		fmt.Println("LoadX509KeyPair error:", err)
		return
	}
	config := &tls.Config{Certificates: []tls.Certificate{cert}}

	// 监听地址
	ln, err := net.Listen("tcp", ":8888")
	if err != nil {
		fmt.Println("net.Listen error:", err)
		return
	}
	defer ln.Close()

	for {
		// 等待客户端连接
		conn, err := ln.Accept()
		if err != nil {
			fmt.Println("Accept error:", err)
			continue
		}

		// 创建 TLS 连接
		tlsConn := tls.Server(conn, config)

		// 开始握手
		err = tlsConn.Handshake()
		if err != nil {
			fmt.Println("Handshake error:", err)
			continue
		}

		// 获取客户端证书
		state := tlsConn.ConnectionState()
		peerCert := state.PeerCertificates[0]

		// 打印客户端证书信息
		fmt.Printf("Received client cert:\nSubject: %s\nIssuer: %s\n",
			peerCert.Subject, peerCert.Issuer)

		// 获取客户端公钥
		clientPubKey := peerCert.PublicKey.(*rsa.PublicKey)

		// 随机生成对称密钥
		key := make([]byte, 16)
		_, err = rand.Read(key)
		if err != nil {
			fmt.Println("rand.Read error:", err)
			continue
		}

		// 使用客户端公钥加密对称密钥
		encryptedKey, err := rsa.EncryptPKCS1v15(rand.Reader, clientPubKey, key)
		if err != nil {
			fmt.Println("rsa.EncryptPKCS1v15 error:", err)
			continue
		}

		// 发送加密后的对称密钥到客户端
		_, err = tlsConn.Write(encryptedKey)
		if err != nil {
			fmt.Println("tlsConn.Write error:", err)
			continue
		}

		// 关闭连接
		tlsConn.Close()
	}
}

哈希函数

定义:将任意长度的输入经过变化后得到固定长度的输出,是一种单向密码体制,只有加密过程没有解密。比特币使用的是 SHA-256,以太坊使用的是 Keccak-256(SHA-3-256)。

特性解释
不可逆性从哈希值推算原始输入信息是不可能的
唯一性不同的输入产生的哈希值是不同的,且输出长度一致
固定性同样的输入映射的哈希值应该相同
散列性输入进行微小的改变应该导致哈希值的巨大变化
用途:区块、交易的编号(地址)和内容验证、共识机制中挖矿节点对随机数的搜索与区块散列验证。
package main

import (
	"fmt"
	"crypto/sha3"
)

func main() {
	data := []byte("Hello, world!")
	hash := sha3.Sum256(data)
	fmt.Printf("Hash: %x\n", hash)
}

SHA-3 算法通过使用crypto/sha3包实现,调用sha3.Sum256()函数生成哈希值。
crypto/sha3golang.org/x/crypto/sha3的区别:
前者是后者的子集,golang.org/x/xcrypto/sha3包提供了更多的哈希数选项和其他密码学算法,crypto/sha3包只提供SHA3-256算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值