[API] Encrypting a secret using the public key and libsodium

I am trying to add a “secret” via the API.

I am using the sodium R package which provides support for libsodium.

Below I outline my approach where “pub_key_gh” is the character value representing the public key mentioned in the API docs. 

  1. Serialize the private key which should be encrypted

  2. Decode the public key from Github

  3. Encrypt the serialized private key using the public key

  4. Encode the encrypted private key again because only base64 encoded strings can be pushed via the API

    private_key = sodium::hash(charToRaw(private_key))
    pub_key_gh_dec = base64enc::base64decode(pub_key_gh)
    private_key_encr = sodium::simple_encrypt(private_key, pub_key_gh_dec)
    private_key_encr = base64enc::base64encode(private_key_encr)

However, during the Action run the private key is marked as “invalid format”. Hence, most likely the pushed secret was not decoded correctly behind the scenes when being inserted in the run / was uploaded in the wrong format.

Any help is appreciated - I am newbie when it comes to encryption so I can very well be that one of the steps outlined above does not make sense / is not needed.

Manually adding the “private key” via the Github web interface works without problems so its really about how to encrypt the key correctly / upload it in the right format.

Although I am also an encryption-noob (who isn’t?), I just recently created a CLI that pushes secrets to GitHub using the API, so the information is fresh in my mind.

I didn’t see nor use any private key in the process.

The same documentation page you referenced has examples in several languages (not R unfortunately) further down the page.

In general, the process as described is this (pseudocode):

# API call to get the public key for the repo
base64_encoded_public_key, key_id = get_public_key_from_github(repo_name)

# Base64 decode the key
public_key = base64_decode(base64_encoded_public_key)

# Encrypt the secret with sodium and the public key
plain_secret = "secret"
encrypted_secret = sodium_encrypt(public_key, plain_secret)

# Base64 encode the encrypted secret
base64_encoded_secret = base64_encode(encrypted_secret)

# Send it to GitHub using the API, and including the key_id as received in step 1
push_secret_to_github(key_id, base64_encoded_secret)

And this is the example in Ruby:

key = Base64.decode64("+ZYvJDZMHUfBkJdyq5Zm9SKqeuBQ4sj+6sfjlH4CgG0=")
public_key = RbNaCl::PublicKey.new(key)

box = RbNaCl::Boxes::Sealed.from_public_key(public_key)
encrypted_secret = box.encrypt("my_secret")

# Print the base64 encoded secret
puts Base64.strict_encode64(encrypted_secret)

In my development, I also tested that I can decrypt it back. For this you will need to use sodium to generate a public/private key pair (instead of the one you receive from GitHub), then follow the encryption steps as above, using the public key, and then decrypt it with the same sodium method, only this time using the private key - to verify the output is decrypting properly.

If it will help, or if you are in need of a way to do it and are not married to doing it in R, I can share a link to my CLI (in Ruby).

Thanks @dannyben!

Your post helped, thanks! I got it working now and I think I was already very close yesterday but did some mistakes on the public key side - but I can’t really recall.

In the end, my solution looks like this. Here, we assume that the Github public key is already available via some external code.


# convert to raw for sodium
msg = "message"
msg_raw <- charToRaw(msg)
# decode public key
pub_key_gh_dec <- base64enc::base64decode(pub_key_gh)
# encrypt using the pub key
msg_raw_encr <- sodium::simple_encrypt(msg_raw, pub_key_gh_dec)
# base64 encode secret
msg_raw_encr_enc <- base64enc::base64encode(msg_raw_encr)
1 Like

Is there a way to do this with only using the sodium package in R? Why do we have to decode the public key and encode the secret with base64 encoding? How do you know to use base64, are there other methods that can be used?