如何将Base64编码的公钥转换为crypto.PublicKey或ecdsa.PublicKey?

huangapple go评论72阅读模式
英文:

how to convert a base64 encoded public key to crypto.PublicKey or a ecdsa.PublicKey

问题

问题中提到,有人能否向我展示如何将一个base64编码的公钥转换为crypto.PublicKeyecdsa.PublicKey?我在网上找不到相关的示例。对于这个问题,我会很感激任何文档或代码片段。

英文:

As the question states can anyone show me how I can convert a base64 encoded public key into a crypto.PublicKey or a ecdsa.PublicKey ? I am unable to find an example of this online. Any documentation or snippets on this regard would be appreciated

答案1

得分: 0

由于特别提到了ECDSA,所以应该使用ParsePKIXPublicKey函数,正如问题的评论中所指出的。ParsePKCS1PublicKey用于旧格式的RSA公钥(可通过标题-----BEGIN RSA PUBLIC KEY-----识别)。顺便说一句,ParsePKIXPublicKey还可以解析新格式的RSA公钥(可通过标题-----BEGIN PUBLIC KEY-----识别,注意缺少RSA)- 使用编码的密钥类型对象标识符。除了ECDSA和RSA之外,还支持DSA和ED25519。

下面是一个完整的、自包含的示例,展示了如何使用openssl在ECDSARSA格式中创建示例密钥,并使用Go读取公钥。

生成RSA公钥

openssl genrsa -out rsaPrivateKey.pem 1024  
openssl rsa -in rsaPrivateKey.pem -pubout > rsaPublicKey.pem

生成ECDSA公钥

openssl ecparam -name prime256v1 -genkey -out ecdsaPrivateKey.pem 
openssl ec -in ecdsaPrivateKey.pem -pubout -out ecdsaPublicKey.pem

测试

测试代码可能如下所示:

package main

import (
	"crypto/dsa"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"log"
	"os"
)

func main() {
	rsaPublicKey, err := readPublicKey("rsaPublicKey.pem")
	if err != nil {
		log.Fatal(err)
	}
	err = printPublicKey(rsaPublicKey)
	if err != nil {
		log.Fatal(err)
	}

	ecdsaPublicKey, err := readPublicKey("ecdsaPublicKey.pem")
	if err != nil {
		log.Fatal(err)
	}
	err = printPublicKey(ecdsaPublicKey)
	if err != nil {
		log.Fatal(err)
	}
}

这里使用了两个函数readPublicKeyprintPublicKey,分别用于读取和打印带有公钥的PEM文件。

根据文档所示,读取RSA或ECDSA密钥的代码是相同的:

func readPublicKey(filename string) (pub any, err error) {
	publicKey, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(publicKey)
	if block == nil {
		return nil, errors.New("decoding public key PEM failed")
	}

	pub, err = x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return pub, nil
}

最后,可以使用类型切换轻松访问数据,就像在评论中引用的文档中演示的那样。

func printPublicKey(pub any) error {
	switch pub := pub.(type) {
	case *rsa.PublicKey:
		fmt.Println("pub is of type RSA:", pub)
	case *dsa.PublicKey:
		fmt.Println("pub is of type DSA:", pub)
	case *ecdsa.PublicKey:
		fmt.Println("pub is of type ECDSA:", pub)
	case ed25519.PublicKey:
		fmt.Println("pub is of type Ed25519:", pub)
	default:
		return errors.New("unknown type of public key")
	}
	return nil
}

区分密钥类型

使用在线的ASN.1 JavaScript解码器https://lapo.it/asn1js/,可以将生成的ECDSA pem输入。

第一个对象标识符将显示为ecPublicKey (ANSI X9.62 public key type)

而对于RSA,这将是rsaEncryption (PKCS #1)

secp256k1

并非所有的曲线类型都受到Go的crypto包支持,特别是secp256k1。可以通过上述提到的在线工具中的第二个对象标识符来识别曲线类型。

尝试使用上述代码将会产生以下错误消息:x509: unsupported elliptic curve

在这种情况下,可以使用github.com/decred/dcrd/secp256k1包。为了获得secp256k1.ParsePubKey的适当输入形式,例如可以使用未压缩的密钥格式(0x04 + x + y),而这可以通过asn1包中的Unmarshal从中提取(还可以参考ParsePKIXPublicKey的内部实现)。

自包含示例

使用secp256k1曲线类型生成ECDSA公钥

openssl ecparam -name secp256k1 -genkey -out ecdsaSecp256k1PrivateKey.pem
openssl ec -in ecdsaSecp256k1PrivateKey.pem -pubout -out ecdsaSecp256k1PublicKey.pem

用于测试的自包含示例可能如下所示:

package main

import (
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/decred/dcrd/dcrec/secp256k1"
	"log"
	"os"
)

type publicKeyInfo struct {
	Raw       asn1.RawContent
	Algorithm pkix.AlgorithmIdentifier
	PublicKey asn1.BitString
}

func readSecp256k1PublicKey(filename string) (*secp256k1.PublicKey, error) {
	publicKey, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(publicKey)
	if block == nil {
		return nil, errors.New("decoding public key PEM failed")
	}
	var pki publicKeyInfo
	if _, err := asn1.Unmarshal(block.Bytes, &pki); err != nil {
		return nil, err
	}
	pub, err := secp256k1.ParsePubKey(pki.PublicKey.Bytes)
	if err != nil {
		return nil, err
	}
	return pub, nil
}

func main() {
	pub, err := readSecp256k1PublicKey("ecdsaSecp256k1PublicKey.pem")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("public key %T", pub)
}

在调试控制台上的输出将是:

public key *secp256k1.PublicKey
英文:

Since ECDSA was specifically mentioned, ParsePKIXPublicKey should be the function to use, as noted in the comments to the questions. ParsePKCS1PublicKey would be for an RSA public key in an older format (recognizable by the header -----BEGIN RSA PUBLIC KEY-----). By the way, ParsePKIXPublicKey can also parse RSA public keys in the newer format (recognizable by the header -----BEGIN PUBLIC KEY-----, note the missing RSA) - using the encoded key type object identifier. Besides ECDSA and RSA also DSA and ED25519 are supported.

Here is a complete, self-contained example showing the creation of sample keys with openssl in ECDSA and RSA formats and reading the public key with Go.

Generation of an RSA public key

openssl genrsa -out rsaPrivateKey.pem 1024  
openssl rsa -in rsaPrivateKey.pem -pubout > rsaPublicKey.pem

Generation of an ECDSA public key

openssl ecparam -name prime256v1 -genkey -out ecdsaPrivateKey.pem 
openssl ec -in ecdsaPrivateKey.pem -pubout -out ecdsaPublicKey.pem

Test

A test might look like this:

package main

import (
	"crypto/dsa"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"log"
	"os"
)

func main() {
	rsaPublicKey, err := readPublicKey("rsaPublicKey.pem")
	if err != nil {
		log.Fatal(err)
	}
	err = printPublicKey(rsaPublicKey)
	if err != nil {
		log.Fatal(err)
	}

	ecdsaPublicKey, err := readPublicKey("ecdsaPublicKey.pem")
	if err != nil {
		log.Fatal(err)
	}
	err = printPublicKey(ecdsaPublicKey)
	if err != nil {
		log.Fatal(err)
	}
}

Here two functions readPublicKey and printPublicKey are used, which respectively read and print the PEM file with the public key.

As shown in the documentation, the code to read an RSA or ECDSA key would be the same:

func readPublicKey(filename string) (pub any, err error) {
	publicKey, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(publicKey)
	if block == nil {
		return nil, errors.New("decoding public key PEM failed")
	}

	pub, err = x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	return pub, nil
}

Finally, the data can be easily accessed with a type switch, as nicely illustrated in the documentation referenced in the comment.

func printPublicKey(pub any) error {
	switch pub := pub.(type) {
	case *rsa.PublicKey:
		fmt.Println("pub is of type RSA:", pub)
	case *dsa.PublicKey:
		fmt.Println("pub is of type DSA:", pub)
	case *ecdsa.PublicKey:
		fmt.Println("pub is of type ECDSA:", pub)
	case ed25519.PublicKey:
		fmt.Println("pub is of type Ed25519:", pub)
	default:
		return errors.New("unknown type of public key")
	}
	return nil
}

Differentiation of the key types

Using the cool online ASN.1 JavaScript decoder https://lapo.it/asn1js/ one can feed in the generated ECDSA pem.

The will reveal in the first object identifer ecPublicKey (ANSI X9.62 public key type).

Whereas for RSA this would be rsaEncryption (PKCS #1).

secp256k1

Not all curve types are supported by Go's crypto package, notably secp256k1. You can identify the curve type, for example, by the second object identifier in the online tool mentioned above.

Attempting to use the above code with it would produce the following error message: x509: unsupported elliptic curve.

In such case, you could for example use the package github.com/decred/dcrd/secp256k1.

To get the appropriate input form for secp256k1.ParsePubKey, e.g. the uncompressed key format (0x04 + x + y) can be used, which in turn can be extracted via Unmarshal from the asn1 package (see also to the internals of ParsePKIXPublicKey).

Self-contained example

Generation of an ECDSA public key with secp256k1 curve type

openssl ecparam -name secp256k1 -genkey -out ecdsaSecp256k1PrivateKey.pem
openssl ec -in ecdsaSecp256k1PrivateKey.pem -pubout -out ecdsaSecp256k1PublicKey.pem

A self-contained example for testing might look something like this:

package main

import (
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/decred/dcrd/dcrec/secp256k1"
	"log"
	"os"
)

type publicKeyInfo struct {
	Raw       asn1.RawContent
	Algorithm pkix.AlgorithmIdentifier
	PublicKey asn1.BitString
}

func readSecp256k1PublicKey(filename string) (*secp256k1.PublicKey, error) {
	publicKey, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(publicKey)
	if block == nil {
		return nil, errors.New("decoding public key PEM failed")
	}
	var pki publicKeyInfo
	if _, err := asn1.Unmarshal(block.Bytes, &pki); err != nil {
		return nil, err
	}
	pub, err := secp256k1.ParsePubKey(pki.PublicKey.Bytes)
	if err != nil {
		return nil, err
	}
	return pub, nil
}

func main() {
	pub, err := readSecp256k1PublicKey("ecdsaSecp256k1PublicKey.pem")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("public key %T", pub)
}

The output on the debug console would be:

public key *secp256k1.PublicKey

huangapple
  • 本文由 发表于 2023年2月25日 09:20:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/75562744.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定