How to create RSA private key with passphrase in Go? - go

How to create RSA private key with passphrase in Go?
I read the documentation for the crypto package but I was unable to piece together a solution from it.

First step, generate a private key.
Second step, convert it to PEM format. Third step, encrypt the PEM.
All can be done using Golang's standard library, which is very complete. The code is nothing difficult so I put it here. All there is to it is to know which functions to use.
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
)
func PrivateKeyToEncryptedPEM(bits int, pwd string) ([]byte, error) {
// Generate the key of length bits
key, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
// Convert it to pem
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
// Encrypt the pem
if pwd != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(pwd), x509.PEMCipherAES256)
if err != nil {
return nil, err
}
}
return pem.EncodeToMemory(block), nil
}

Related

Retrieve public SSH key from private key file using Go

I have a private key file id_rsa (starts with -----BEGIN RSA PRIVATE KEY-----). With the tool ssh-keygen I am able to generate an SSH public key using the following command:
ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
The generated file will have the following content:
ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9j...
I am trying to achieve the same within my Go code. The code will be executed on AWS Lambda so I want to avoid executing system commands with os.exec since I don't have control over the underlying environment.
Given that I have a variable private_key, how can I extract the ssh public key from it?
This is my solution to extract the public key in OpenSSH format, using packages rsa, pem and x509 from the standard library and golang.org/x/crypto/ssh
import (
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"os"
"golang.org/x/crypto/ssh"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
func PublicPEMtoOpenSSH(rsaPubKey *rsa.PublicKey) (string, error) {
pub, err := ssh.NewPublicKey(rsaPubKey)
if err != nil {
return "", err
}
sshPubKey := ssh.MarshalAuthorizedKey(pub)
return string(sshPubKey), nil
}
func main() {
private_PEM, err := os.ReadFile("path/to/file.pem")
check(err)
priv_parsed, err := ParseRsaPrivateKeyFromPemStr(string(private_PEM))
check(err)
openssh_key, err := PublicPEMtoOpenSSH(&priv_parsed.PublicKey)
check(err)
print(openssh_key) // ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA...
}

OpenSSL SHA256 signature

I have a private key in a file -----BEGIN PRIVATE KEY-----\nxxx-----END PRIVATE KEY-----\n
I am trying to perform the below which is in Ruby in Go and got stuck as Go accepts only pem file. Kindly suggest.
azure_certificate_private_key = OpenSSL::PKey.read(private_key_file)
base64_signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA256.new, headerPayloadConcatenated))
I have tried as,
signBytes, err := ioutil.ReadFile(privKeyPath)
signer, err := ParsePrivateKey(signBytes)
no key found error
package main
import (
"encoding/pem"
"crypto/x509"
"io/ioutil"
)
func main() {
signBytes, err := ioutil.ReadFile(privKeyPath)
if err != nil {
panic(err) //or do whatever with err
}
keyRSA := parseRSAKey(signBytes)
//keyRSA is a *rsa.PrivateKey
//....use the key
}
func parseRSAKey(b []byte) *rsa.PrivateKey {
block, _ := pem.Decode(b)
if block == nil {
panic("no PEM block")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
this should work to parse a RSA private key.

I have a public key and a JWT, how do I check if it's valid in Go?

I have a public key from my identity provider
-----BEGIN PUBLIC KEY-----
THIS
-----END PUBLIC KEY-----
And a JWT token from my client.
How do I check the token against the key?
I'm having difficulty with jwt-go because the Parse function takes the token string and a getKey function.
Not sure how exactly to proceed
The token was signed by RSA algorithm that uses a private key to sign and a public key to verify. Store your public key to the files system and use jwt.SigningMethodRS256.Verify() to verify. As the following snippet:
package main
import (
"fmt"
"strings"
"log"
"io/ioutil"
jwt "github.com/dgrijalva/jwt-go"
)
func main() {
publicKeyPath := "~/public_key.key"
token := "your_jwt_token_here"
if isValid, err := verifyToken(token, publicKeyPath)
if err != nil {
log.Fatal(err)
}
if isValid {
fmt.Println("The token is valid")
} else {
fmt.Println("The token is invalid")
}
}
func verifyToken(token, publicKeyPath string) (bool, error) {
keyData, err := ioutil.ReadFile(publicKeyPath)
if err != nil {
return false, err
}
key, err := jwt.ParseRSAPublicKeyFromPEM(keyData)
if err != nil {
return false, err
}
parts := strings.Split(token, ".")
err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], key)
if err != nil {
return false, nil
}
return true, nil
}
Using jwt-go, you can do this
token, err := p.Parse(IDToken, func(*jwt.Token) (interface{}, error) {
return []byte(signingKey), nil
})
And it will verify the token against the key.
Quoting the documentation,
type Keyfunc func(*Token) (interface{}, error)
Parse methods use this callback function to supply the key for verification. The function receives the parsed, but unverified Token. This allows you to use properties in the Header of the token (such as kid) to identify which key to use.

How to read an RSA key from file

I need to read in an RSA private key from a file to sign a JWT. I have found some examples on how to save a generated RSA key to disk but nothing showing how to build a key struct based on a pre-generated key from a file.
The key is generated like this:
openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt
Example key:
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClHYNDPVSF‌​FmWF
oKGTqd/n7Dt2+tGXh97KJjVLAqCBZZHlQJ534v2OzFjTgzuMNehD9Y6HnkYF‌​dkRb
QzYi2YDROOzRl1bhyyWPA35OGf50r7LiNvSvNPNtswsCuK7ywOcH0yEMKSiW‌​4q5R
GKYi42w961EcTQQPrfihavY+c2FYPv4+pXymzaIz9hGBPLHwaHq/QTAyHxPC‌​fkOo
s/x3mxUVd7Ni2bz1VJGlyqcNEeU88wTAYMmv8oQ3y2NfKExtYn+W6TCDiq/+‌​ZkOp
wacuAU0J7tCNgcXvkq39KH5uza2uSiTniye6uhlkvYWD3s9riIIiekTEiHk/‌​kkc6
jMg8HN/7AgMBAAECggEBAJ12u8vQHV6esUrymaTdCG+BVmRtZpyA ...
-----END RSA PRIVATE KEY-----
A combination of pem.Decode and x509.ParsePKCS1PrivateKey should do the trick:
package main
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
func main() {
pemString := `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDLets8+7M+iAQAqN/5BVyCIjhTQ4cmXulL+gm3v0oGMWzLupUS
v8KPA+Tp7dgC/DZPfMLaNH1obBBhJ9DhS6RdS3AS3kzeFrdu8zFHLWF53DUBhS92
5dCAEuJpDnNizdEhxTfoHrhuCmz8l2nt1pe5eUK2XWgd08Uc93h5ij098wIDAQAB
AoGAHLaZeWGLSaen6O/rqxg2laZ+jEFbMO7zvOTruiIkL/uJfrY1kw+8RLIn+1q0
wLcWcuEIHgKKL9IP/aXAtAoYh1FBvRPLkovF1NZB0Je/+CSGka6wvc3TGdvppZJe
rKNcUvuOYLxkmLy4g9zuY5qrxFyhtIn2qZzXEtLaVOHzPQECQQDvN0mSajpU7dTB
w4jwx7IRXGSSx65c+AsHSc1Rj++9qtPC6WsFgAfFN2CEmqhMbEUVGPv/aPjdyWk9
pyLE9xR/AkEA2cGwyIunijE5v2rlZAD7C4vRgdcMyCf3uuPcgzFtsR6ZhyQSgLZ8
YRPuvwm4cdPJMmO3YwBfxT6XGuSc2k8MjQJBAI0+b8prvpV2+DCQa8L/pjxp+VhR
Xrq2GozrHrgR7NRokTB88hwFRJFF6U9iogy9wOx8HA7qxEbwLZuhm/4AhbECQC2a
d8h4Ht09E+f3nhTEc87mODkl7WJZpHL6V2sORfeq/eIkds+H6CJ4hy5w/bSw8tjf
sz9Di8sGIaUbLZI2rd0CQQCzlVwEtRtoNCyMJTTrkgUuNufLP19RZ5FpyXxBO5/u
QastnN77KfUwdj3SJt44U/uh1jAIv4oSLBr8HYUkbnI8
-----END RSA PRIVATE KEY-----`
block, _ := pem.Decode([]byte(pemString))
key, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
fmt.Println(key.N)
}
If what you are sitting on is a PKCS#8 encoded key, instead you would do something like the following:
func main() {
pemString := `-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKhPSTDs4cpKfnMc
p86fCkpnuER7bGc+mGkhkw6bE+BnROfrDCFBSjrENLS5JcsenANQ1kYGt9iVW2fd
ZAWUdDoj+t7g6+fDpzY1BzPSUls421Dmu7joDPY8jSdMzFCeg7Lyj0I36bJJ7ooD
VPW6Q0XQcb8FfBiFPAKuY4elj/YDAgMBAAECgYBo2GMWmCmbM0aL/KjH/KiTawMN
nfkMY6DbtK9/5LjADHSPKAt5V8ueygSvI7rYSiwToLKqEptJztiO3gnls/GmFzj1
V/QEvFs6Ux3b0hD2SGpGy1m6NWWoAFlMISRkNiAxo+AMdCi4I1hpk4+bHr9VO2Bv
V0zKFxmgn1R8qAR+4QJBANqKxJ/qJ5+lyPuDYf5s+gkZWjCLTC7hPxIJQByDLICw
iEnqcn0n9Gslk5ngJIGQcKBXIp5i0jWSdKN/hLxwgHECQQDFKGmo8niLzEJ5sa1r
spww8Hc2aJM0pBwceshT8ZgVPnpgmITU1ENsKpJ+y1RTjZD6N0aj9gS9UB/UXdTr
HBezAkEAqkDRTYOtusH9AXQpM3zSjaQijw72Gs9/wx1RxOSsFtVwV6U97CLkV1S+
2HG1/vn3w/IeFiYGfZXLKFR/pA5BAQJAbFeu6IaGM9yFUzaOZDZ8mnAqMp349t6Q
DB5045xJxLLWsSpfJE2Y12H1qvO1XUzYNIgXq5ZQOHBFbYA6txBy/QJBAKDRQN47
6YClq9652X+1lYIY/h8MxKiXpVZVncXRgY6pbj4pmWEAM88jra9Wq6R77ocyECzi
XCqi18A/sl6ymWc=
-----END PRIVATE KEY-----`
block, _ := pem.Decode([]byte(pemString))
parseResult, _ := x509.ParsePKCS8PrivateKey(block.Bytes)
key := parseResult.(*rsa.PrivateKey)
fmt.Println(key.N)
}
There is a helpful function ssh.ParseRawPrivateKey which already implements some of the required logic. I suggest building upon it, especially if your key is used as a SSH key, which in my case it is.
import (
"os"
"crypto/rsa"
"golang.org/x/crypto/ssh"
"io/ioutil"
)
func mustNot(err error) {
if err != nil {
panic(err)
}
}
func loadRsaPrivateKey() *rsa.PrivateKey {
bytes, err := ioutil.ReadFile(os.Getenv("HOME") + "/.ssh/id_rsa")
mustNot(err)
key, err := ssh.ParseRawPrivateKey(bytes)
mustNot(err)
return key.(*rsa.PrivateKey)
}
Here's a full example for both private and public pem files:
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"log"
)
func RSAConfigSetup(rsaPrivateKeyLocation, privatePassphrase, rsaPublicKeyLocation string) (*rsa.PrivateKey, error) {
if rsaPrivateKeyLocation == "" {
log.Println("No RSA Key given, generating temp one")
return GenRSA(4096)
}
priv, err := ioutil.ReadFile(rsaPrivateKeyLocation)
if err != nil {
log.Println("No RSA private key found, generating temp one")
return GenRSA(4096)
}
privPem, _ := pem.Decode(priv)
if privPem.Type != "RSA PRIVATE KEY" {
log.Println("RSA private key is of the wrong type", privPem.Type)
}
if x509.IsEncryptedPEMBlock(privPem) && privatePassphrase == "" {
log.Println("Passphrase is required to open private pem file")
return GenRSA(4096)
}
var privPemBytes []byte
if privatePassphrase != "" {
privPemBytes, err = x509.DecryptPEMBlock(privPem, []byte(privatePassphrase))
} else {
privPemBytes = privPem.Bytes
}
var parsedKey interface{}
//PKCS1
if parsedKey, err = x509.ParsePKCS1PrivateKey(privPemBytes); err != nil {
//If what you are sitting on is a PKCS#8 encoded key
if parsedKey, err = x509.ParsePKCS8PrivateKey(privPemBytes); err != nil { // note this returns type `interface{}`
log.Println("Unable to parse RSA private key, generating a temp one", err)
return GenRSA(4096)
}
}
var privateKey *rsa.PrivateKey
var ok bool
privateKey, ok = parsedKey.(*rsa.PrivateKey)
if !ok {
log.Println("Unable to parse RSA private key, generating a temp one", err)
return GenRSA(4096)
}
pub, err := ioutil.ReadFile(rsaPublicKeyLocation)
if err != nil {
log.Println("No RSA public key found, generating temp one")
return GenRSA(4096)
}
pubPem, _ := pem.Decode(pub)
if pubPem == nil {
log.Println("Use `ssh-keygen -f id_rsa.pub -e -m pem > id_rsa.pem` to generate the pem encoding of your RSA public key - rsa public key not in pem format")
return GenRSA(4096)
}
if pubPem.Type != "RSA PUBLIC KEY" {
log.Println("RSA public key is of the wrong type", pubPem.Type)
return GenRSA(4096)
}
if parsedKey, err = x509.ParsePKIXPublicKey(pubPem.Bytes); err != nil {
log.Println("Unable to parse RSA public key, generating a temp one", err)
return GenRSA(4096)
}
var pubKey *rsa.PublicKey
if pubKey, ok = parsedKey.(*rsa.PublicKey); !ok {
log.Println("Unable to parse RSA public key, generating a temp one", err)
return GenRSA(4096)
}
privateKey.PublicKey = *pubKey
return privateKey, nil
}
// GenRSA returns a new RSA key of bits length
func GenRSA(bits int) (*rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, bits)
return key, err
}

Save and load crypto/rsa PrivateKey to and from the disk

I'm using crypto/rsa, and trying to find a way to properly save and load a key. Is there a correct way to create a []byte from an rsa.PrivateKey. If so, is there a way to properly do so for an rsa.PublicKey?
Thank you all very much.
You need some sort of format to marshal the key into. One format supported by the Go standard library can be found here: http://golang.org/pkg/crypto/x509/#MarshalPKCS1PrivateKey
func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte
The inverse function is http://golang.org/pkg/crypto/x509/#ParsePKCS1PrivateKey.
func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err error)
However, it is relatively standard to encode the marshaled key into a PEM file.
pemdata := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
You can find a full example here.
Here's code snippet that shows the import and export of both public and private keys. It's based on the other answers which were super helpful, as well as copy-pasta from the official docs.
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
)
func GenerateRsaKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) {
privkey, _ := rsa.GenerateKey(rand.Reader, 4096)
return privkey, &privkey.PublicKey
}
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
privkey_bytes := x509.MarshalPKCS1PrivateKey(privkey)
privkey_pem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkey_bytes,
},
)
return string(privkey_pem)
}
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) {
pubkey_bytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkey_pem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkey_bytes,
},
)
return string(pubkey_pem), nil
}
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *rsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not RSA")
}
func main() {
// Create the keys
priv, pub := GenerateRsaKeyPair()
// Export the keys to pem string
priv_pem := ExportRsaPrivateKeyAsPemStr(priv)
pub_pem, _ := ExportRsaPublicKeyAsPemStr(pub)
// Import the keys from pem string
priv_parsed, _ := ParseRsaPrivateKeyFromPemStr(priv_pem)
pub_parsed, _ := ParseRsaPublicKeyFromPemStr(pub_pem)
// Export the newly imported keys
priv_parsed_pem := ExportRsaPrivateKeyAsPemStr(priv_parsed)
pub_parsed_pem, _ := ExportRsaPublicKeyAsPemStr(pub_parsed)
fmt.Println(priv_parsed_pem)
fmt.Println(pub_parsed_pem)
// Check that the exported/imported keys match the original keys
if priv_pem != priv_parsed_pem || pub_pem != pub_parsed_pem {
fmt.Println("Failure: Export and Import did not result in same Keys")
} else {
fmt.Println("Success")
}
}
Since the public key part of your question wasn't answered and I just ran into the same problem and solved it, here it is:
Note the & in front of the Argument to MarshalPKIXPublicKey
Priv := rsa.GenerateKey(rand.Reader, 4096)
pubASN1, err := x509.MarshalPKIXPublicKey(&Priv.PublicKey)
if err != nil {
// do something about it
}
pubBytes := pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubASN1,
})
ioutil.WriteFile("key.pub", pubBytes, 0644)
Relevant reads:
MarshalPKIXPublicKey(pub interface{}) ([]byte, error) godoc
EncodeToMemory(b *Block) []byte godoc
Block godoc
PS: MarshalPKIXPublicKey also accepts ECDSA keys, ajust the pem header appropriately.

Resources