I try to generate a pair of a PKCS8 encoded private key and the corresponding PKCS1 encoded public key using the rsa and x509 packages of Go. I want to have the same behavior as the openssl commands below:
$ openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt
$ openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
I have added my Go implementation below, boiled down to the essentials. To use it, I first generate a private key with GeneratePrivateKey (with bit size 2048 for the example attached on the end), and then encode the private key and the public key with the following two methods.
However, when running the second openssl command to encode the public key on the private key encoded by my Go implementation, the public keys differ (I included an example for the difference below). In particular, it looks as if the openssl output extends the output of my Go implementation by a prefix.
I would be grateful for any explaination of the difference (and in the best case, how I can fix my code). Thank you very much!
My Go code looks as follows:
package xyz
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
)
func GeneratePrivateKey(bitSize int) (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
return nil, err
}
err = privateKey.Validate()
if err != nil {
return nil, err
}
return privateKey, nil
}
func EncodePrivateKeyToPEM(privateKey *rsa.PrivateKey) (string, error) {
privateDER, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return "", err
}
privateBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privateDER,
}
privatePEM := pem.EncodeToMemory(&privateBlock)
return string(privatePEM), nil
}
func EncodePublicKeyToPEM(privateKey *rsa.PrivateKey) string {
publicRsaKey := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
publicKeyBytes := pem.EncodeToMemory(
&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicRsaKey,
})
return string(publicKeyBytes)
}
An exemplary private key:
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDnT3OOrKW S2m4
3fMyl3BjCxnZ2n39wlnYqunYSor5S8wlynhPc72tY8xmTVGhxBYul9LAXyO1/LLU
4cNARmHtSSmeUeYLCU3PMKP795oL4k/YPLqnracIyieN5s8JnmWGtqZo7hyysvhu
Yp0LDmJcQRpB3BoDyTs /amRP5fnRkkoFABqIejbJEgsjmfXlRnjLR8H4cK8Dmp
5tm2QzSAWos/E3LV/yhhe4b0oTqxGf lHC5i4FgPd4Co/oaq47QOQkqarxFd57DH
yKNQF3vbGJmVucFnHC 7l4QflcP2Kp/6WJL7IDXA5FC1CFLC5YCmT1pxog/0iPJb
bOiMC1KHAgMBAAECggEBANrJru7j5L3zDOP8o/1VestkA6apYQS2YU9sklWwZHVb
OZUBRv0359Bqljqppa4SED0wA/tLwjZv CMeDBXuvM 5pTEIHESSH1P7mRBKah4V
iRpqjs7tlRHeTL2O9IQPm85pU0vq9aAdYpjek9aSW30RYymqa7zrCSwRHYK1vblm
zbGUpz3PQp5Mp/CPPBGW2arK5Syo25AJ9YphaWAKub NXPRZTIVZFylvDtONQmwt
D8YknEPB9eQcgL41egW ViOs7WOpB6qM8nvbNa lvdGawog0cZefeNw0PyHxQmhB
Mt/VzvbFaqG2aeH73Na oFX9/DuQxrvns126R42VK4kCgYEA9lkqUWvVSq8MBb h
QLZKlSlJiJ1R/nmYqd98r7DCZYwNTRF4LqK0DUbHJCF0WJ3G0UxuKNc98 k3JVz
5YiZMj6S660XYgQR62GaGKLiVUQmFrKCYUmLrpgDse/pvnA6BfM6MMwR/eIlitGn
B65QarKMwRphCIRrVXnViFs/QNsCgYEA8F91NvVxk14cQPBzdeHK5Z1E ekHLQHU
nvdWpuqF4n/UWrQg4JI9ACjZIbEd5oJRkQa7M8KXonaQwFlqTe9Ov7MrMon4/CmC
l XxBLMIb5Pif biNle3Jhdk6N7H16S9w4PpyaT4nSCU7HNHVIbb yjr2awKzMSH
yKi51VYKXsUCgYBMGW1GK65 3KY1HGZezDEr9M4fYqHSn5N2XnBxAYR7xBDTzuK2
XVKLi2K3GZKMUWj6fMbG3P7pWxDK/PhMxa61Y7kVDqpbf/BdKxBkLeUG /9cIZa/
IO4CaDkz/W3Sg26ZKfK 4jtolzSLtooHiGSVIUTz3gc9j1Js3C8HaVCX4wKBgA2b
eck4r8tF4peShRbBR3kkJAl5tZxIpD6zhcZBNH8T XOBCvNrk R0a4ZKvoSUEAYD
uYk lNLvn1YkOa5bw7t6axGbWer8dpYewDwh3nJijsnqyheFc6rGXEIKiomcm9l7
mDqACkrq00NnazxPvhhDwsY5xQq fWlQk SN0Fx5AoGAKIMHfXkYrObD8NxE4kvs
Knx3/aH2vAAsDbC mA2wZOSZ hsD3SO62ypI3UW3Fxn5 55sQMsMCqaOJBXrLZMh
2iyTMk7V5Bx // pa5Hv3yPmDeGtlAT gbwaqrh5KM1eaW1TVZ1HdUNmamh9Iq0O
KrEPYf2r8 IjAhctV08Mx7I=
-----END RSA PRIVATE KEY-----
The public key as encoded by my Go implementation:
-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEA509zjqylvktpuN3zMpdwYwsZ2dp9/cJZ2Krp2EqK UvMJcp4T3O9
rWPMZk1RocQWLpfSwF8jtfyy1OHDQEZh7UkpnlHmCwlNzzCj /eaC JP2Dy6p62n
CMonjebPCZ5lhramaO4csrL4bmKdCw5iXEEaQdwaA8k7Pvv2pkT X50ZJKBQAaiH
o2yRILI5n15UZ4y0fB HCvA5qebZtkM0gFqLPxNy1f8oYXuG9KE6sRn/pRwuYuBY
D3eAqP6GquO0DkJKmq8RXeewx8ijUBd72xiZlbnBZxwvu5eEH5XD9iqf liS yA1
wORQtQhSwuWApk9acaIP9IjyW2zojAtShwIDAQAB
-----END PUBLIC KEY-----
The public key as generated by the second openssl command (note that it only differs from the former by the prefix MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA509zjqylvktpuN3zMpdw
YwsZ2dp9/cJZ2Krp2EqK UvMJcp4T3O9rWPMZk1RocQWLpfSwF8jtfyy1OHDQEZh
7UkpnlHmCwlNzzCj /eaC JP2Dy6p62nCMonjebPCZ5lhramaO4csrL4bmKdCw5i
XEEaQdwaA8k7Pvv2pkT X50ZJKBQAaiHo2yRILI5n15UZ4y0fB HCvA5qebZtkM0
gFqLPxNy1f8oYXuG9KE6sRn/pRwuYuBYD3eAqP6GquO0DkJKmq8RXeewx8ijUBd7
2xiZlbnBZxwvu5eEH5XD9iqf liS yA1wORQtQhSwuWApk9acaIP9IjyW2zojAtS
hwIDAQAB
-----END PUBLIC KEY-----
CodePudding user response:
The OpenSSL statements generate a private key in PKCS#8 format and a public key in X.509/SPKI format, both PEM encoded.
The private key generated with the Go Code has the PKCS#8 format, but the PEM encoding uses the wrong header and footer (correct would be -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----). The fix is to adjust in EncodePrivateKeyToPEM()
in the pem.Block()
call the type accordingly (Type: "PRIVATE KEY"
).
In the case of the public key generated with the Go Code, the situation is reversed: Here, the header and footer correspond to those of a PEM encoded X.509/SPKI key. However, the body is PKCS#1 formatted. This is the reason why the keys differ. The fix is to use in EncodePublicKeyToPEM()
the method MarshalPKIXPublicKey()
for the X.509/SPKI format instead of MarshalPKCS1PublicKey()
.
By the way, the best way to inspect the keys is to use an ASN.1 parser, e.g. https://lapo.it/asn1js/.