wz2b
wz2b

Reputation: 1025

Generating RSA public keys from go using x509.MarshalPKCS1PublicKey

I'm trying to generate a public key from go as follows:

reader := rand.Reader
bitSize := 2048

keypair, err := rsa.GenerateKey(reader, bitSize)

That seems to work and generates somethign that makes sense. Next, I want to write the public portion of this as an RSA file like this:

func PublicKeyToPemBytes(prvkey *rsa.PrivateKey) ([]byte, error) {
    var pubkey *rsa.PublicKey
    pubkey = &prvkey.PublicKey

    pubkey_bytes := x509.MarshalPKCS1PublicKey(pubkey)
    if pubkey_bytes == nil {
        return nil, errors.New("Public key could not be serialized")
    }

    pubkey_pem := pem.EncodeToMemory(
        &pem.Block{
            Type:  "RSA PUBLIC KEY",
            Bytes: pubkey_bytes,
        },
    )

    return pubkey_pem, nil
}

That generates something that looks more or less like you'd expect. Here's a dummy key I generated just to show:

-----BEGIN RSA PUBLIC KEY-----
MIIBCGKCAQEAMU6KIRUM2KACW7ISHRVRVPXG5YC7+D58Y26HV3TBHJCDNYE9Z8NE
S/XOJS58SCJL+6VLCH03RQWFLSSBZRDTAFGE4V0PTZXQ1ECUIVX6EIUWAVIKTQA9
7WEBNFU4MCHVLWFPULDAQOFP02M2WXUCI/DXCHH1R2QJCJWZKAUOERYDOP3+5YZI
CDHWX54T7GIAU6XV9M/5FH39EBLVDITK85/3RKRZIB/6SRBFSKQVWPNG69WJGIZU
YJYQNNKB8QXG5VCHRJ+OXITBWXYKFXBIKUIGE8AKUDL9OI2SR5I0HQ0AMLNCI9DA
SGHT6UQGZMVRKJC9/FVKLRQURLKMUL1AKWIDAQAB
-----END RSA PUBLIC KEY-----

but it isn't actually correct:

$ grep -v -- ----- < remote.pub  | base64 -d | dumpasn1 -
Warning: Input is non-seekable, some functionality has been disabled.
  0 264: SEQUENCE {
  4 257:   [APPLICATION 2] {
       :       Error: Spurious EOC in definite-length item.

Error: Invalid data encountered at position 12: 4E 8A.

$ openssl asn1parse -in remote.pub 
    0:d=0  hl=4 l= 264 cons: SEQUENCE          
    4:d=1  hl=4 l= 257 cons: appl [ 2 ]        
    8:d=2  hl=2 l=  49 prim: EOC               
   59:d=2  hl=2 l=   8 prim: appl [ 11 ]       
   69:d=2  hl=2 l=  16 cons: appl [ 5 ]        
   71:d=3  hl=2 l=   0 prim: priv [ 19 ]       
Error in encoding
140590716953024:error:0D07209B:asn1 encoding routines:ASN1_get_object:too long:../crypto/asn1/asn1_lib.c:91:

It should look like this:

  0 266: SEQUENCE {
  4 257:   INTEGER
       :     00 FB 11 99 FF 07 33 F6 E8 05 A4 FD 3B 36 CA 68
       :     E9 4D 7B 97 46 21 16 21 69 C7 15 38 A5 39 37 2E
             ...
       :             [ Another 129 bytes skipped ]
265   3:   INTEGER 12345
       :   }

so I don't think I'm generating the file correctly. The marshalling code in pkcs1.go looks like this:

func MarshalPKCS1PublicKey(key *rsa.PublicKey) []byte {
    derBytes, _ := asn1.Marshal(pkcs1PublicKey{
        N: key.N,
        E: key.E,
    })
    return derBytes
}

I don't know how that works, but I think it should be generating just one sequenc with two integers in it, one of those being N, the other E. I'm not sure why dumpasn1 thinks it encoded an [APPLICATION 2] but neither dumpasn1 nor openssl think what was generated was even valid ASN.1.

If I understand the documentation for Marshal correctly, I think the MarshalPKCS1PublicKey function should do the right thing, but for some reason it doesn't.

I took a look at the encoding a little. For an rsa.pub file generated by openssl, it begins:

0000000 30 82 01 0a 02 82 01 01 00 fb 11 99 ff 07 33 f6

Using my method from go, it generates this:

0000000 30 82 01 08 62 82 01 01 00 31 4e 8a 21 15 0c d8

the key piece of information there, I belive, is that theirs generates 0x02 (INTEGER) but the go method generates 0x62. That 0x62 tag is an APPLICATION/CONSTRUCTED integer as described here. I'm not an expert on this, but I think that's the problem. I think it should be generating tag 0x02 (integer) with the tag type set to UNIVERSAL.

That's really as far as I can get on my own, though. Can somebody tell me where I might have gone off the rails?

--Chris

UPDATE: The public key object defined in pkcs1.go looks like this:

type pkcs1PublicKey struct {
    N *big.Int
    E int
}

I was wondering if for some reason the default parameters weren't working correctly, so I planned on copying that structure and making my own marshal method. My plan was to put asn1 field tags on N and E, but I didn't get that far before it was somehow 'fixed':

type myPublicKey struct {
    N *big.Int
    E int
}

func myMarshalPKCS1PublicKey(key *rsa.PublicKey) []byte {
    derBytes, _ := asn1.Marshal(myPublicKey{
        N: key.N,
        E: key.E,
    })
    return derBytes
}

so I did exactly that and guess what ... if I call myMarshalPKCS1PublicKey() instead of the one in x509, I end up with an ASN.1 file that parses correctly. Not only that but

openssl rsa -RSAPublicKey_in -in mykey.pub -text

works - it correctly spits out the modulus and exponent.

So something is different, even though I think I'm just using a copy of the identical code. I confirmed this by just replacing the call to my marshaller with a call to the one in the x509 module, and it stops working. I'm stumped.

Upvotes: 2

Views: 2006

Answers (1)

YaFred
YaFred

Reputation: 10008

I tried to reproduce your question, and this works for me ...

Consider

package main

import "fmt"
import "crypto/rand"
import "crypto/rsa"
import "crypto/x509"
import "encoding/hex"
import (
    "encoding/pem"
    "log"
    "os"
)


func main() {
reader := rand.Reader
bitSize := 64


keypair, _:= rsa.GenerateKey(reader, bitSize)

fmt.Println("Public key ", &keypair.PublicKey)

pubkey_bytes := x509.MarshalPKCS1PublicKey(&keypair.PublicKey)

fmt.Println(hex.Dump(pubkey_bytes))

block := &pem.Block{
        Type: "MESSAGE",
        Bytes: pubkey_bytes ,
    }

    if err := pem.Encode(os.Stdout, block); err != nil {
        log.Fatal(err)
    }

}

When you run it, the console will show

Public key  &{14927333011981288097 65537}
00000000  30 10 02 09 00 cf 28 8a  49 37 1b 42 a1 02 03 01  |0.....(.I7.B....|
00000010  00 01                                             |..|

-----BEGIN MESSAGE-----
MBACCQDPKIpJNxtCoQIDAQAB
-----END MESSAGE-----

I use https://asn1.io/asn1playground/

Paste this in schema

World-Schema DEFINITIONS  ::= 
BEGIN
 RSAPublicKey ::= SEQUENCE {
          modulus           INTEGER,  -- n
          publicExponent    INTEGER   -- e
      }                                
END

Hit compile

Paste this in decode

30 10 02 09 00 cf 28 8a  49 37 1b 42 a1 02 03 01 00 01

Result is

RSAPublicKey SEQUENCE: tag = [UNIVERSAL 16] constructed; length = 16
D0023E: Integer or enumerated value too long: 9; check field 'modulus' (type: INTEGER) of PDU #1 'RSAPublicKey'.
  modulus INTEGER: tag = [UNIVERSAL 2] primitive; length = 9
    2147483647
  publicExponent INTEGER: tag = [UNIVERSAL 2] primitive; length = 3
    65537
S0012E: Decoding of PDU #1 failed with the return code '10'.

I am not sure why they find an error but the SEQUENCE and the 2 INTEGER are definitely there (you don't really need a tool to see it) EDIT: the error is not relevant for us.

I used https://base64.guru/converter/decode/hex to check that the Base64 generated by go pem.Encode is correct.

The only difference with your code is the bitSize I used (64 instead of 2048) because it was a bit long on https://play.golang.org/p/VQ7h9hYtO3W

Upvotes: 2

Related Questions