Reputation: 1025
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
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