Forked from azakordonets/generate_rsa_ssh_keys.go
Last active
January 15, 2025 03:04
-
-
Save goliatone/e9c13e5f046e34cef6e150d06f20a34c to your computer and use it in GitHub Desktop.
Generate SSH RSA Private/Public Key pair with Golang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This shows an example of how to generate a SSH RSA Private/Public key pair and save it locally | |
package main | |
import ( | |
"crypto/rand" | |
"crypto/rsa" | |
"crypto/x509" | |
"encoding/pem" | |
"golang.org/x/crypto/ssh" | |
"io/ioutil" | |
"log" | |
) | |
func main() { | |
savePrivateFileTo := "./id_rsa_test" | |
savePublicFileTo := "./id_rsa_test.pub" | |
bitSize := 4096 | |
privateKey, err := generatePrivateKey(bitSize) | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
publicKeyBytes, err := generatePublicKey(&privateKey.PublicKey) | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
privateKeyBytes := encodePrivateKeyToPEM(privateKey) | |
err = writeKeyToFile(privateKeyBytes, savePrivateFileTo) | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
err = writeKeyToFile([]byte(publicKeyBytes), savePublicFileTo) | |
if err != nil { | |
log.Fatal(err.Error()) | |
} | |
} | |
// generatePrivateKey creates a RSA Private Key of specified byte size | |
func generatePrivateKey(bitSize int) (*rsa.PrivateKey, error) { | |
// Private Key generation | |
privateKey, err := rsa.GenerateKey(rand.Reader, bitSize) | |
if err != nil { | |
return nil, err | |
} | |
// Validate Private Key | |
err = privateKey.Validate() | |
if err != nil { | |
return nil, err | |
} | |
log.Println("Private Key generated") | |
return privateKey, nil | |
} | |
// encodePrivateKeyToPEM encodes Private Key from RSA to PEM format | |
func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte { | |
// Get ASN.1 DER format | |
privDER := x509.MarshalPKCS1PrivateKey(privateKey) | |
// pem.Block | |
privBlock := pem.Block{ | |
Type: "RSA PRIVATE KEY", | |
Headers: nil, | |
Bytes: privDER, | |
} | |
// Private key in PEM format | |
privatePEM := pem.EncodeToMemory(&privBlock) | |
return privatePEM | |
} | |
// generatePublicKey take a rsa.PublicKey and return bytes suitable for writing to .pub file | |
// returns in the format "ssh-rsa ..." | |
func generatePublicKey(privatekey *rsa.PublicKey) ([]byte, error) { | |
publicRsaKey, err := ssh.NewPublicKey(privatekey) | |
if err != nil { | |
return nil, err | |
} | |
pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey) | |
log.Println("Public key generated") | |
return pubKeyBytes, nil | |
} | |
// writePemToFile writes keys to a file | |
func writeKeyToFile(keyBytes []byte, saveFileTo string) error { | |
err := ioutil.WriteFile(saveFileTo, keyBytes, 0600) | |
if err != nil { | |
return err | |
} | |
log.Printf("Key saved to: %s", saveFileTo) | |
return nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ( | |
"fmt" | |
"crypto/rsa" | |
"crypto/rand" | |
"crypto/x509" | |
"encoding/pem" | |
) | |
// MakeSSHKeyPair make a pair of public and private keys for SSH access. | |
// Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file. | |
// Private Key generated is PEM encoded | |
func MakeSSHKeyPair(pubKeyPath, privateKeyPath string) error { | |
privateKey, err := rsa.GenerateKey(rand.Reader, 1024) | |
if err != nil { | |
return err | |
} | |
// generate and write private key as PEM | |
privateKeyFile, err := os.Create(privateKeyPath) | |
defer privateKeyFile.Close() | |
if err != nil { | |
return err | |
} | |
privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} | |
if err := pem.Encode(privateKeyFile, privateKeyPEM); err != nil { | |
return err | |
} | |
// generate and write public key | |
pub, err := ssh.NewPublicKey(&privateKey.PublicKey) | |
if err != nil { | |
return err | |
} | |
return ioutil.WriteFile(pubKeyPath, ssh.MarshalAuthorizedKey(pub), 0655) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Released under CC0 1.0 https://creativecommons.org/publicdomain/zero/1.0/ | |
// To the extent possible under law, the author have dedicated all copyright | |
// and related and neighboring rights to this software to the public domain | |
// worldwide. This software is distributed without any warranty. | |
//https://play.golang.org/p/BK9rxDD87ur | |
//PBES2 format decoder | |
package main | |
import ( | |
"crypto/aes" | |
"crypto/cipher" | |
"crypto/des" | |
"crypto/sha1" | |
"crypto/sha256" | |
"crypto/sha512" | |
"crypto/x509" | |
"encoding/asn1" | |
"encoding/pem" | |
"errors" | |
"fmt" | |
"hash" | |
"os" | |
"strconv" | |
"golang.org/x/crypto/pbkdf2" | |
) | |
func appendOID(b asn1.ObjectIdentifier, v ...int) asn1.ObjectIdentifier { | |
n := make(asn1.ObjectIdentifier, len(b), len(b)+len(v)) | |
copy(n, b) | |
return append(n, v...) | |
} | |
var ( | |
oidRSADSI = asn1.ObjectIdentifier{1, 2, 840, 113549} | |
oidPKCS5 = appendOID(oidRSADSI, 1, 5) | |
oidPBKDF2 = appendOID(oidPKCS5, 12) | |
oidPBES2 = appendOID(oidPKCS5, 13) | |
oidDigestAlgorithm = appendOID(oidRSADSI, 2) | |
oidHMACWithSHA1 = appendOID(oidDigestAlgorithm, 7) | |
oidHMACWithSHA224 = appendOID(oidDigestAlgorithm, 8) | |
oidHMACWithSHA256 = appendOID(oidDigestAlgorithm, 9) | |
oidHMACWithSHA384 = appendOID(oidDigestAlgorithm, 10) | |
oidHMACWithSHA512 = appendOID(oidDigestAlgorithm, 11) | |
oidHMACWithSHA512_224 = appendOID(oidDigestAlgorithm, 12) | |
oidHMACWithSHA512_256 = appendOID(oidDigestAlgorithm, 13) | |
oidEncryptionAlgorithm = appendOID(oidRSADSI, 3) | |
oidDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} | |
oidDESEDE3CBC = appendOID(oidEncryptionAlgorithm, 7) | |
oidAES = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1} | |
oidAES128CBCPAD = appendOID(oidAES, 2) | |
oidAES192CBCPAD = appendOID(oidAES, 22) | |
oidAES256CBCPAD = appendOID(oidAES, 42) | |
) | |
var ( | |
ErrIncorrectPassword = errors.New("possilbly incorrect encryption password") | |
errInvalidDataLen = errors.New("data size is not a multiple of cipher block size") | |
errInvalidIVLen = errors.New("invalid IV size") | |
errInvalidIter = errors.New("invalid iteration count") | |
errNoData = errors.New("no data to decrypt") | |
errNotPBES2 = errors.New("not PBES2 data") | |
errUnsupportedKDF = errors.New("unsupported KDF") | |
errUnsupportedPRF = errors.New("unsupported PRF") | |
errUnsupportedEncS = errors.New("unsupported encryption scheme") | |
) | |
type ErrTooManyIterations int | |
func (err ErrTooManyIterations) Error() string { | |
return "too many PBKDF2 iterations: " + strconv.Itoa(int(err)) | |
} | |
func prfByOID(oid asn1.ObjectIdentifier) func() hash.Hash { | |
if len(oid) == 0 { | |
return sha1.New | |
} | |
if oid.Equal(oidHMACWithSHA1) { | |
return sha1.New | |
} | |
if oid.Equal(oidHMACWithSHA224) { | |
return sha256.New224 | |
} | |
if oid.Equal(oidHMACWithSHA256) { | |
return sha256.New | |
} | |
if oid.Equal(oidHMACWithSHA384) { | |
return sha512.New384 | |
} | |
if oid.Equal(oidHMACWithSHA512) { | |
return sha512.New | |
} | |
if oid.Equal(oidHMACWithSHA512_224) { | |
return sha512.New512_224 | |
} | |
if oid.Equal(oidHMACWithSHA512_256) { | |
return sha512.New512_256 | |
} | |
return nil | |
} | |
func encsByOID(oid asn1.ObjectIdentifier) (func([]byte) (cipher.Block, error), func(cipher.Block, []byte) cipher.BlockMode, int) { | |
if oid.Equal(oidDESCBC) { | |
return des.NewCipher, cipher.NewCBCDecrypter, 8 | |
} | |
if oid.Equal(oidDESEDE3CBC) { | |
return des.NewTripleDESCipher, cipher.NewCBCDecrypter, 24 | |
} | |
if oid.Equal(oidAES128CBCPAD) { | |
return aes.NewCipher, cipher.NewCBCDecrypter, 16 | |
} | |
if oid.Equal(oidAES192CBCPAD) { | |
return aes.NewCipher, cipher.NewCBCDecrypter, 24 | |
} | |
if oid.Equal(oidAES256CBCPAD) { | |
return aes.NewCipher, cipher.NewCBCDecrypter, 32 | |
} | |
return nil, nil, 0 | |
} | |
func DecryptPBES2(b, password []byte, maxIter int) (data, rest []byte, err error) { | |
var p struct { | |
ES struct { | |
ID asn1.ObjectIdentifier | |
Params struct { | |
KDF struct { | |
ID asn1.ObjectIdentifier | |
Params struct { | |
Salt []byte | |
Iter int | |
KeyLength int `asn1:"optional"` | |
PRF struct { | |
ID asn1.ObjectIdentifier | |
Params asn1.RawValue | |
} `asn1:"optional"` | |
} | |
} | |
EncS struct { | |
ID asn1.ObjectIdentifier | |
Params []byte | |
} | |
} | |
} | |
Data []byte | |
} | |
rest, err = asn1.Unmarshal(b, &p) | |
if err != nil { | |
return | |
} | |
if !p.ES.ID.Equal(oidPBES2) { | |
err = errNotPBES2 | |
return | |
} | |
if !p.ES.Params.KDF.ID.Equal(oidPBKDF2) { | |
err = errUnsupportedKDF | |
return | |
} | |
if p.ES.Params.KDF.Params.Iter < 1 { | |
err = errInvalidIter | |
return | |
} | |
prf := prfByOID(p.ES.Params.KDF.Params.PRF.ID) | |
if prf == nil { | |
err = errUnsupportedPRF | |
return | |
} | |
bcf, bmf, kl := encsByOID(p.ES.Params.EncS.ID) | |
if bcf == nil || bmf == nil { | |
err = errUnsupportedEncS | |
return | |
} | |
if len(p.Data) == 0 { | |
err = errNoData | |
return | |
} | |
if maxIter > 0 && p.ES.Params.KDF.Params.Iter > maxIter { | |
err = ErrTooManyIterations(p.ES.Params.KDF.Params.Iter) | |
return | |
} | |
key := pbkdf2.Key(password, p.ES.Params.KDF.Params.Salt, p.ES.Params.KDF.Params.Iter, kl, prf) | |
var bc cipher.Block | |
bc, err = bcf(key) | |
if err != nil { | |
return | |
} | |
if len(p.ES.Params.EncS.Params) != bc.BlockSize() { | |
err = errInvalidIVLen | |
return | |
} | |
bm := bmf(bc, p.ES.Params.EncS.Params) | |
if len(p.Data)%bm.BlockSize() != 0 { | |
err = errInvalidDataLen | |
return | |
} | |
data = make([]byte, len(p.Data)) | |
bm.CryptBlocks(data, p.Data) | |
pl := data[len(data)-1] | |
if pl == 0 || int(pl) > bm.BlockSize() { | |
err = ErrIncorrectPassword | |
return | |
} | |
dl := len(data) - int(pl) | |
for _, b := range data[dl:] { | |
if b != pl { | |
err = ErrIncorrectPassword | |
return | |
} | |
} | |
data = data[:dl] | |
return | |
} | |
var pemKey = ([]byte)(`-----BEGIN ENCRYPTED PRIVATE KEY----- | |
MIIBSzBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIQAY0IsXMhucCAggA | |
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECHi4EIJK+T6FBIH4Fatl16Lwznm/ | |
jIhKygStjhIlpww0A0aZDp/D0eEJpXzvPgRZWf2xhlf5gzTMblQ2XkNrbu/OWOOS | |
f+qx//lh30WTFYOwu0ZWBuxGnjDQav2nc+GKRfzCWbTvgdj8EOKi3vgt8PkuBZWp | |
IwX0GRrLLd19EmC/VpZ6zAoJIxeE2Oc76tBREJCs5T8o+4Y28rgo/mXbMJmxpdAK | |
ncWa4y0f1IEcjdw2u3I8csvtwUIj6WjVLkrS1R3I0DS9jEbs0rZ9uORk5aFatzre | |
ccfQA0JI0n15QPX8dGh/RnWmpzpGXMxShiwn434KGD/Fa0mZeQex26chknoV3YE= | |
-----END ENCRYPTED PRIVATE KEY-----`) | |
var password = ([]byte)("1234") | |
func main() { | |
block, _ := pem.Decode(pemKey) | |
if block == nil { | |
fmt.Fprintln(os.Stderr, "failed to decode PEM block") | |
os.Exit(1) | |
} | |
var derKey []byte | |
var err error | |
if block.Type == "ENCRYPTED PRIVATE KEY" { | |
derKey, _, err = DecryptPBES2(block.Bytes, password, 1000000) | |
} else if x509.IsEncryptedPEMBlock(block) { | |
derKey, err = x509.DecryptPEMBlock(block, password) | |
} else { | |
derKey = block.Bytes | |
} | |
if err != nil { | |
fmt.Fprintln(os.Stderr, "failed to decrypt private key:", err) | |
os.Exit(1) | |
} | |
key, err := x509.ParsePKCS8PrivateKey(derKey) | |
if err != nil { | |
fmt.Fprintln(os.Stderr, "failed to parse PKCS #8 private key:", err) | |
os.Exit(1) | |
} | |
fmt.Printf("key type: %T\n", key) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good evening
Please do you have some way to generate oly the ECDSA public keys from the private keys in sequence using this library "github.com/ethereum/go-ethereum/crypto/secp256k1"