Skip to content

Instantly share code, notes, and snippets.

@goliatone
Forked from azakordonets/generate_rsa_ssh_keys.go
Last active August 28, 2025 09:34
Show Gist options
  • Select an option

  • Save goliatone/e9c13e5f046e34cef6e150d06f20a34c to your computer and use it in GitHub Desktop.

Select an option

Save goliatone/e9c13e5f046e34cef6e150d06f20a34c to your computer and use it in GitHub Desktop.

Revisions

  1. goliatone revised this gist Aug 30, 2020. 1 changed file with 35 additions and 0 deletions.
    35 changes: 35 additions & 0 deletions make_ssh_key_pair.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    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)
    }
  2. goliatone revised this gist Aug 26, 2020. 1 changed file with 250 additions and 0 deletions.
    250 changes: 250 additions & 0 deletions pbes2_format.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,250 @@
    // 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)
    }
  3. @azakordonets azakordonets renamed this gist Jul 28, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. @devinodaniel devinodaniel created this gist Nov 21, 2017.
    102 changes: 102 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,102 @@
    // 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
    }