使用Go内置JSON编解码RSA证书失败,求原因解析
这个问题我之前也碰到过,核心在于Go的encoding/json包对big.Int类型的默认序列化/反序列化行为,再加上JSON本身没有“大整数”类型的限制,才导致了解码失败:
为什么会失败?
当你直接序列化x509.Certificate时,其中rsa.PublicKey的N字段是一个*big.Int类型的超大整数。Go的JSON编码器会把它转成纯数字的JSON值,但JSON里根本没有专门的大整数类型——而Go的JSON解码器在遇到没指定类型的数字时,默认会尝试把它解析成float64。
但你的这个公钥模数N太大了,远远超出了float64能精确表示的范围(float64最多只能精准存储53位以内的整数),不仅会丢精度,更关键的是:目标字段y.PublicKey.N是*big.Int类型,解码器没法把float64直接转成*big.Int,这就出现了你看到的错误:
json: cannot unmarshal number [...] into Go struct field Certificate.PublicKey of type float64
解决方案
有两种常用的解决思路,你可以根据场景选择:
方法1:自定义JSON序列化/反序列化逻辑
给rsa.PublicKey实现MarshalJSON和UnmarshalJSON接口,把big.Int序列化为字符串(避免数字精度丢失),解码时再从字符串转回big.Int。
修改后的示例代码:
package main import ( "bytes" "crypto/rsa" "crypto/x509" "encoding/json" "fmt" "math/big" ) // 给rsa.PublicKey自定义JSON序列化方法,将N转为字符串存储 func (pk rsa.PublicKey) MarshalJSON() ([]byte, error) { type Alias rsa.PublicKey return json.Marshal(struct { Alias N string `json:"N"` }{ Alias: Alias(pk), N: pk.N.String(), }) } // 给rsa.PublicKey自定义JSON反序列化方法,从字符串解析回big.Int func (pk *rsa.PublicKey) UnmarshalJSON(data []byte) error { type Alias rsa.PublicKey aux := &struct { Alias N string `json:"N"` }{} if err := json.Unmarshal(data, aux); err != nil { return err } *pk = rsa.PublicKey(aux.Alias) pk.N = new(big.Int) _, ok := pk.N.SetString(aux.N, 10) if !ok { return fmt.Errorf("invalid big integer string: %s", aux.N) } return nil } func main() { x := x509.Certificate{} N := big.Int{} N.SetString("22748727785943691703458259255009028279491017474470365445444638136335844588257736864763884308763893296520197853772438093808627310216929382637891614145169115712146316592081285259872752696564531634381437767246277672628285238806746417464073995314135364153811760424176263991818395775316575483628262670505480241278789196525640591586277923027755459881178666634831466031143685993230414451164180917857690755309066589767418739391025091379117132119476119702352443067398453637494721792111385747721853079909232828234315851102595340792201691113238210441101837352511126798300795766862744529103670862211990583319260851455953450220537", 10) x.PublicKey = &rsa.PublicKey{N: &N, E: 65537} var y x509.Certificate b := bytes.Buffer{} err := json.NewEncoder(&b).Encode(x) if err != nil { fmt.Println(err) return } err = json.NewDecoder(&b).Decode(&y) if err != nil { fmt.Println(err) return } fmt.Println("解码成功!公钥模数N匹配:", y.PublicKey.(*rsa.PublicKey).N.Cmp(&N) == 0) }
方法2:将证书序列化为标准PEM/DER格式后嵌入JSON
更通用的方式是先把x509.Certificate转换成标准的PEM或DER字节流,再转成Base64字符串存入JSON,解码时反向操作——这种方式兼容性更好,符合证书序列化的行业标准。
示例代码:
package main import ( "bytes" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "math/big" ) // 定义用于JSON序列化的结构体,存储Base64编码的证书 type CertJSON struct { CertBase64 string `json:"cert"` } func main() { x := x509.Certificate{} N := big.Int{} N.SetString("22748727785943691703458259255009028279491017474470365445444638136335844588257736864763884308763893296520197853772438093808627310216929382637891614145169115712146316592081285259872752696564531634381437767246277672628285238806746417464073995314135364153811760424176263991818395775316575483628262670505480241278789196525640591586277923027755459881178666634831466031143685993230414451164180917857690755309066589767418739391025091379117132119476119702352443067398453637494721792111385747721853079909232828234315851102595340792201691113238210441101837352511126798300795766862744529103670862211990583319260851455953450220537", 10) x.PublicKey = &rsa.PublicKey{N: &N, E: 65537} // 将证书转为DER格式 derBytes, err := x509.MarshalCertificate(x509.RawCertificate, &x) if err != nil { fmt.Println(err) return } // 转Base64字符串后序列化JSON certJSON := CertJSON{ CertBase64: base64.StdEncoding.EncodeToString(derBytes), } b := bytes.Buffer{} err = json.NewEncoder(&b).Encode(certJSON) if err != nil { fmt.Println(err) return } // 反序列化JSON并解析回证书 var decodedCertJSON CertJSON err = json.NewDecoder(&b).Decode(&decodedCertJSON) if err != nil { fmt.Println(err) return } decodedDerBytes, err := base64.StdEncoding.DecodeString(decodedCertJSON.CertBase64) if err != nil { fmt.Println(err) return } y, err := x509.ParseCertificate(decodedDerBytes) if err != nil { fmt.Println(err) return } fmt.Println("解码成功!公钥模数N匹配:", y.PublicKey.(*rsa.PublicKey).N.Cmp(&N) == 0) }
总结
- 直接序列化
x509.Certificate失败的本质是:JSON没有大整数类型,Go默认用float64解析超大数字,导致类型不匹配+精度丢失。 - 如果只需要处理公钥部分,方法1更轻量;如果要完整序列化证书,推荐方法2,兼容性和规范性更好。
内容的提问来源于stack exchange,提问作者Tom

