Encrypting Data With SSH Keys and Golang

3 minute read     Updated:

Alex Couture-Beil    Alex Couture-Beil

We’re currently working on a server for sharing secrets between developers and CI systems, and one of the features we decided to support is passwordless login via ssh keys. I had never used any of the public/private key encryption libraries in Go before, so I wanted to spend some time experimenting with them to familiarise myself with the libraries.

Here’s a short tutorial with some sample code for experimenting with public/private key RSA encryption.


Let’s start with what I knew, generating a new RSA key with ssh-keygen

    alex@earthly:~/$ ssh-keygen
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/alex/.ssh/id_rsa): /tmp/testkey
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /tmp/testkey
    Your public key has been saved in /tmp/testkey.pub
    The key fingerprint is:
    SHA256:7N/cMpD6ou4tdPDYfQKq6Xa7m2sEYl0nIIonf1+3kvg alex@mah
    The key's randomart image is:
    +---[RSA 3072]----+
    |  . ..           |
    |.. .  o .        |
    |+ .. . o         |
    | +o o ...        |
    | ..... *So..     |
    |   . .=++o=..    |
    |     =o.+..+     |
    |    + ++oo oo.   |
    |   o.=XBEoo oo.  |
    +----[SHA256]-----+

Perfect, we can then display my public key with:

    alex@earthly:~/$ cat /tmp/testkey.pub 
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBRl0U4mwO/jQ7kYJidSnQy0ci45j1QZ1do7NEC/08cG0jbNCSX6mblFr0JWruLpp6Z1WA/BL+GngCwATBeEt7dSAHNpOvT0fJ4roWv6/KmOLOCjKq26a0MvMf1g/YFa5tP5Zi7UW5Hp4vGCTXRPyywNJvh1/cHKuq2j79fUX+4cG9p01a1Y89/a3Q7L5UkB4JoFuaA9sVzVg4H5A2vRVR/pEIRRFuPuxHDVcNblA6CsKFf0zBoLatXv+aBn86dX8EtwB13HdRsKq+XmBwnWJiS+Cz1GBhnKf4LM/Ca46qy2ExQnOOt49COUOoU6DI7P5bf4I33pNDDLoTvFFKzyXWTRgwg1tiyiRzfIjO+mg0kQM/dZ7+M8W49AQv+MR8Uh0bykECXn6u8yEibEgInYlj0ziWXtf6lPEg+505hDTLlvPWXpo8nLluR5COwgFVSbNcMnY9o3KHeog598mQxiqrXWWbGmra7SgXrKmqJGqUbkZqH1z8l6QfFo9nTBlYI0k= alex@earthly

and since we’re all friends here, I’ll share my example private key (you should never share your key with anyone, I’m only sharing this as an example – I won’t ever be using this key anywhere).

    alex@earthly:~/$ cat /tmp/testkey
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
    NhAAAAAwEAAQAAAYEAwUZdFOJsDv40O5GCYnUp0MtHIuOY9UGdXaOzRAv9PHBtI2zQkl+p
    m5Ra9CVq7i6aemdVgPwS/hp4AsAEwXhLe3UgBzaTr09HyeK6Fr+vypjizgoyqtumtDLzH9
    YP2BWubT+WYu1FuR6eLxgk10T8ssDSb4df3Byrqto+/X1F/uHBvadNWtWPPf2t0Oy+VJAe
    CaBbmgPbFc1YOB+QNr0VUf6RCEURbj7sRw1XDW5QOgrChX9MwaC2rV7/mgZ/OnV/BLcAdd
    x3UbCqvl5gcJ1iYkvgs9RgYZyn+CzPwmuOqsthMUJzjrePQjlDqFOgyOz+W3+CN96TQwy6
    E7xRSs8l1k0YMINbYsokc3yIzvpoNJEDP3We/jPFuPQEL/jEfFIdG8pBAl5+rvMhImxICJ
    2JY9M4ll7X+pTxIPudOYQ0y5bz1l6aPJy5bkeQjsIBVUmzXDJ2PaNyh3qIOffJkMYqq11l
    mxpq2u0oF6ypqiRqlG5Gah9c/JekHxaPZ0wZWCNJAAAFgDNuxLAzbsSwAAAAB3NzaC1yc2
    EAAAGBAMFGXRTibA7+NDuRgmJ1KdDLRyLjmPVBnV2js0QL/TxwbSNs0JJfqZuUWvQlau4u
    mnpnVYD8Ev4aeALABMF4S3t1IAc2k69PR8niuha/r8qY4s4KMqrbprQy8x/WD9gVrm0/lm
    LtRbkeni8YJNdE/LLA0m+HX9wcq6raPv19Rf7hwb2nTVrVjz39rdDsvlSQHgmgW5oD2xXN
    WDgfkDa9FVH+kQhFEW4+7EcNVw1uUDoKwoV/TMGgtq1e/5oGfzp1fwS3AHXcd1Gwqr5eYH
    CdYmJL4LPUYGGcp/gsz8JrjqrLYTFCc463j0I5Q6hToMjs/lt/gjfek0MMuhO8UUrPJdZN
    GDCDW2LKJHN8iM76aDSRAz91nv4zxbj0BC/4xHxSHRvKQQJefq7zISJsSAidiWPTOJZe1/
    qU8SD7nTmENMuW89ZemjycuW5HkI7CAVVJs1wydj2jcod6iDn3yZDGKqtdZZsaatrtKBes
    qaokapRuRmofXPyXpB8Wj2dMGVgjSQAAAAMBAAEAAAGACK0d9JgNfcbPlXT8w2q7C9J0SQ
    6qiSf+5ns4yu822QW7AIIcAtYkiQVp59feKv8QlDobToUCXUHW7VitXfoGeW5Sl8BNdOs8
    L8Xr0KWeQJwIYnN2vtDJdQFshJtZbrvabrESETLRlHPZagfNb5R7O5MIX1VWak0nL65IcZ
    y0DbMYvWjLQi6gFYpTyTM3gBhQIOJ/+jP+G8ZyFWLlWG+4i0vAOvzOwYI1nSLuK34uP8zH
    2rJSQcbzLGk9VC7Ce19W1mUxmMMnJJHUMZK9c6UjVxIVqb/iGek3ZxquGCaBFFEiQd6Ltf
    t6tmGIngM/XMFEhMBX/rUldFNC/yHLqXYG+jDTio1hMHfWU82haZEM2o1DreV0qMypJVUJ
    mUPyf/tPmYX4GRS16+TWesfx3xAaW1l+ZI1thW9arviDHiL3PRfl4wYtFpzXWQfgNEgcri
    InSL6kNu3RlULFDs1iLVfcZCv7rmgpGl7cHsOB1fl+9P0xnoOW7Ira+0TOEMX1lCMBAAAA
    wAHOUkTH9Y394OAJCoVDpSy/5A073Z8I9b6nfyG6xr1GTtZPys3rPA567Mq7EEawU21aRl
    DJBIPhrc5Z4aVue30yI4mcd2MetL1BB6v+9wmCiD+DTXvPqSrbOw80M1y1sL3b1L68lmBz
    LdvBbDYYoVtxuF5j9cbajKAPsHpKD4tz1GCO1SyMeyH15QZqk+SHeXcPfFEQ4wWJxE6Qmw
    wCDEIwZC1EudznJiM/XUDsIwS2b6kAzXR1qlQCwI3t6CpphQAAAMEA5ugQFDu9qpAJXIRC
    V4yufh37cd9TmE7zjhIRs61/qz0jxUApRpRNn72W04OzxRyxBPpCg7yvn9mPISh+aCU178
    nIQM3vf+1xMBOCrxtal1vs4xAN3dASzry8u/gaVS0jcoQfuJbfvwcXcJ4sKxskUUMaCAky
    g+OLXdfo9KSFQqia4VZ9vAKEvWDjJg9pk3JJzwYTdG6mj1256qgpxfUIi72ZEnMojQrCrD
    l+aXzWlJ1JFTDHNA/wr3ER7rW81KFxAAAAwQDWR16MBrc9lHgDyzxYiQvu2GHg4yNBGK9W
    9C1dhsieQOuKOHNOpbgpgoysy/o9thYrHJdS7HKQKwwcGhtY26wCYAHZs1sEtXlXmf62BS
    Ej5bV/oFdTcJT7bdgL+K383w0Ug8UiYNrs5b2AhG/VriCNRJy5goo9XEkxXwqIN3OpuVtQ
    N/XMfHMt7FAdUIeNCZWA0tmvy3aZlTUzFF5LEx0/cHmpNgHdz7tshshGG5NvW3ct2AKKmB
    nuAwC6Meoqs1kAAAAIYWxleEBtYWgBAgM=
    -----END OPENSSH PRIVATE KEY-----

Ok great, now how do we do that with go?

    package main
    
    import (
        "crypto/rand"
        "crypto/rsa"
        "encoding/pem"
        "crypto/x509"
        "fmt"
    
        "golang.org/x/crypto/ssh"
    )
    
    func marshalRSAPrivate(priv *rsa.PrivateKey) string {
        return string(pem.EncodeToMemory(&pem.Block{
            Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv),
        }))
    }
    
    func generateKey() (string, string, error) {
        reader := rand.Reader
        bitSize := 2048
    
        key, err := rsa.GenerateKey(reader, bitSize)
        if err != nil {
            return "", "", err
        }
    
        pub, err := ssh.NewPublicKey(key.Public())
        if err != nil {
            return "", "", err
        }
        pubKeyStr := string(ssh.MarshalAuthorizedKey(pub))
        privKeyStr := marshalRSAPrivate(key)
    
        return pubKeyStr, privKeyStr, nil
    }
    
    func main() {
        pubKey, privKey, _ := generateKey()
        fmt.Println("my public key is...")
        fmt.Println(pubKey)
        fmt.Println("my private key is...")
        fmt.Println(privKey)
    }

Try it out in the go playground, you should see something like this:

    my public key is...
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC97wOspXmARcUFThWVlNMnwxIiDIW7CrshmPRDfBV7RYlRtiNuSLlFaAIeXUGPWFnKzivScpBntrFqqj+aJRQ27/tsM/n5jT6ERnoJTbyF+jYCx5BxST5lssVSRrXJQ0dLKSD6OEvTKHK50RrxVtdU2E1cknwQWsYC2514xwmWYwEiNfFkO0QrU27BunPO/Gam+GJNTLt7o7diM0GawuqVI1S/hf0T7goMTA9wX7KaIDg5Q1x+/0MJa1kT7LswG8Rw2TFXRqI9Q+4UmmWN1MxBpeVK8VWx7NY9ngXnHUnJdzrXB4+E95SnKyhzaTlBnWDs9Em606SRb+g+tSYXl8DD
    
    my private key is...
    -----BEGIN RSA PRIVATE KEY-----
    MIIEogIBAAKCAQEAve8DrKV5gEXFBU4VlZTTJ8MSIgyFuwq7IZj0Q3wVe0WJUbYj
    bki5RWgCHl1Bj1hZys4r0nKQZ7axaqo/miUUNu/7bDP5+Y0+hEZ6CU28hfo2AseQ
    cUk+ZbLFUka1yUNHSykg+jhL0yhyudEa8VbXVNhNXJJ8EFrGAtudeMcJlmMBIjXx
    ZDtEK1NuwbpzzvxmpvhiTUy7e6O3YjNBmsLqlSNUv4X9E+4KDEwPcF+ymiA4OUNc
    fv9DCWtZE+y7MBvEcNkxV0aiPUPuFJpljdTMQaXlSvFVsezWPZ4F5x1JyXc61weP
    hPeUpysoc2k5QZ1g7PRJutOkkW/oPrUmF5fAwwIDAQABAoIBAHQGpaT69Q0yEdha
    yf61ioRYuyQHqE4JkSVGDbmH/ItwgCFldaFyVZObpOetqlYJ79hfOA/4IlTpGtqB
    JBdjHUUuNtXzrnoPGaiucPBsB4WEwyfRh2BdEPwJSFcpkPVg3xWAC4AvkcpthCAV
    KDNUDHjtJd0uMxG+kgW+6SSV2jp+NBMjEnL+6vSXQZ/wB6DU8bRa4VlAjA7xSeTX
    +NAS41yPSye/7F2lrSNT4VY8o5k8iRi7K91G1kJn+ewWdQMZEqzz29e1Uk3uxVV9
    XtcJuwsQ9oZ26rsO2PRXTRSE7cZUIhiIZ82ixmbhG1Oe0METpC47dlwwJuJOpJ61
    qszhAvkCgYEAyksKTxrv2pyt08UIRQJykvo72VVGT9IzLXQ07XymIF4m9JHHV13l
    HHbaxuDgzaoEBU3U9aRNi/U7J8o7c9U/sTv6q9JUI28VjrWOFHWafJbpSODy1yfz
    kKlPjXy0UvMHc1DREqDiPQYvdWz4JMp/C6dYS+A300HyUM7423OhHw0CgYEA8Fv0
    qfPoYetGX0Zf47xm0gku2ylj+0OoW0m8H4RXezptoPq0Piz1Lvq0RfubJVSUA9/S
    3epMZ7kFjxZEKfm4wprZpNkpFzbjmDwsx3COcsEcWcMuMf+DEJMsTwuF/I122vPe
    Boe+lTCi+2YWnAilbrQJXtpX3SMhFYtvKla+6w8CgYB1iqSy0jQMEn3uTs4/SuzH
    +h5MagAw4TJbdupKE+Nza0G3Wf06BpTZtTXp2UDGP8OWUWMsWAu3BwcYV6mz5HTd
    xrwgmlXJQQKFqXik6rCZNBbZAdwYqF4d8EMJMyyUBiKOHqdc656JVs68rFSDDCZF
    3zau39mQJwFlct2mpck5AQKBgC6sTIgr+rX478NUcQ5R6U1jxxt7oBSMgMapPMSJ
    +ErPf7ZAuHtSU5H50MO+JdRL5ioSbmn1Mzz46qFsW3QjL8NqOlUObjI50FwhYzif
    HKof4Zd0lSXUTekMCxCWVkBCYBAIRtbRySpDNYLHwiAudaFXiHJIx8MDLUt3tfBs
    w8n1AoGASlCTgmtDfJDcJpCbbHXs0W/Fo7L5ye5qXp6RwOcDnZen0Owt7QXGbEJm
    QjQOuFshp/TZ5jGkv7t2iVBh1whOOpaOmODMKAhueey+NGU47/Ww5vUgwVX/+WJQ
    HddCttBCyHl0vj+Ok4U4JjH05La+7Yrm/5q9wG2KptFe8c+RbeE=
    -----END RSA PRIVATE KEY-----

Perfect! Now I can generate a public and private key via Go. I wonder how I can encrypt a message using a public key which can only be decrypted by someone with the private key. Let’s try out some more code:

I want to keep my function signature as basic as possible for the purpose of learning, so we will pass in the public key as the regular base64-encoded id_rsa keyformat, and let that function handle parsing it:

    func encrypt(msg, publicKey string) (string, error) {
        parsed, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey))
        if err != nil {
            return "", err
        }
        // To get back to an *rsa.PublicKey, we need to first upgrade to the
        // ssh.CryptoPublicKey interface
        parsedCryptoKey := parsed.(ssh.CryptoPublicKey)
    
        // Then, we can call CryptoPublicKey() to get the actual crypto.PublicKey
        pubCrypto := parsedCryptoKey.CryptoPublicKey()
    
        // Finally, we can convert back to an *rsa.PublicKey
        pub := pubCrypto.(*rsa.PublicKey)
    
        encryptedBytes, err := rsa.EncryptOAEP(
            sha256.New(),
            rand.Reader,
            pub,
            []byte(msg),
            nil)
        if err != nil {
            return "", err
        }
        return base64.StdEncoding.EncodeToString(encryptedBytes), nil
    }

Try out the complete example here

Finally, how do we decrypt it?

    func decrypt(data, priv string) (string, error) {
        data2, err := base64.StdEncoding.DecodeString(data)
        if err != nil {
            return "", err
        }
    
        block, _ := pem.Decode([]byte(priv))
        key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
        if err != nil {
            return "", err
        }
    
        decrypted, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, key, data2, nil)
        if err != nil {
            return "", err
        }
        return string(decrypted), nil
    }

Try it out here

So there we have a end-to-end example of how to generate a new public/private key, and encrypt and decrypt data all in golang.

Based on my experimentation with private/public key encryption in go, I put together a small program that allows users to share encrypted data between parties using a rather simple command line tool on my personal repo

Our server’s authentication process is slightly different from the above code – we create a digital signature using the private key, which I’ll be covering in a future blog post.

Alex Couture-Beil

Alex Couture-Beil

Linux and Legumes

Categories:

Updated: