DEV Community 👩‍💻👨‍💻

geraldew
geraldew

Posted on

Supplying encrypted credentials for use on a remote service - via bash, OpenSSL, OpenSSH and Python

Here is something that I wrote this week as a way to securely embed a credential (e.g. account name and password) to be supplied with a program when it gets put somewhere else for scheduled running.

In my case it works by having Python issue a command to bash and collecting what comes back, but it should work on any system with some kind of shell and that has stock SSL and SSH executables installed.

Please bear in mind that my goal was to get something that worked at all - there is no claim of the specifics being an ideal implementation.

Limited Context

It's easy for that to sound more impressive than it really is, so let me clarify the limited context in which it is useful.

My situation is that I am writing a Python program that will need to connect to multiple remote systems, therefore it will need to handle credentials for each of those systems.

The place where the program will eventually run is not the place where I develop it. Indeed, the place where it will run is going to be unknown to me - probably a "service account" on some kind of VM somewhere - that I will never see.

Also, I don't want to require the person who sets up that account to have to organise the credential handling tech - at least not in terms of them needing to modify my program at the point of deployment.

It happens that while I do my development and testing, I will have accounts on the remote systems to use, but those won't be the same accounts that will be used in deployment.

The Approach

So what I came up with is:

  • at run time my program will use the SSH private key of the account it is running on, to decrypt a file that has the credential pair for each remote system;
  • to enable that, it has a companion program that provides a way to use a nominated SSH public key to encrypt the required credential pairs into files to go with the deployed Python code.

This means that the encrypted credentials can only be decrypted by the target account (although, technically that means also anyone who is able to access its private key).

The encrypted credentials can be generated by whoever it is that knows what they are and has been given the public key of the target account.

During development, I can just use my own public/private key pair to generate and then use a set of credential files.

How To Do It

There are two commands, each implemented in two layers of code.

  • the lower level is two bash commands, one to encrypt, one to decrypt.
  • the outer level is a Python wrapper to construct and execute those two bash commands.

Note: while I've implemented this with bash one-liners, any other shell and a system with SSL and SSH that it can call should be feasible to construct.

Examples in bash

I'm showing two alternative modes of operation:

  • when the "secret" is already as a file
  • when the "secret" will be a run-time string

File to File to File

# Make a secret file
echo "the quick brown fox jumps over the lazy dog" > secret.key

# Encrypt it
openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f ~/.ssh/id_rsa.pub -m PKCS8) -in secret.key -out secret.key.enc

# Delete the secret
rm secret.key

# Decrypt
openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa -in secret.key.enc -out secret.key
Enter fullscreen mode Exit fullscreen mode

String to File to String

Here, as well as not working from a pre-existing file, I added the idea of base64 encoding the file to be transmitted. This should make it a bit more resilient across various file transfer modes. Is therefore has a corresponding base64 decoding step at the target.

# Storage

echo "The quick brown fox jumps over the lazy dog" | openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f ~/.ssh/id_rsa.pub -m PKCS8) | base64 > socret.key.enc.b64

# Retrieval

base64 -d socret.key.enc.b64 | openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa
Enter fullscreen mode Exit fullscreen mode

Examples in Python

For this, we will use the string-to-file-to-string method from above. This allows use by Python functions of even more abstraction to handle any desired structurings of the "secret" material.

All that I wrote For my use-case was to make a string of two lines of text separated by a newline character - i.e. as a userid + \n + password construction that was correspondingly split again after being decrypted.

But depending on your needs, the secret parcel could be anything at all - e.g. JSON, XML etc.

Wrapper in Python to Encrypt

import subprocess

def StoreEncryptedWithPublicKey( p_storeinto_pfn, p_secret, p_pubkey_pfn ):
    prep_secret = p_secret #rework the secret to be suitable for a bash echo command
    if len( p_pubkey_pfn.strip() ) == 0 :
        p_pubkey_pfn = "~/.ssh/id_rsa.pub"
    cmd_str = 'echo "' + prep_secret + '" | openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f ' + p_pubkey_pfn + ' -m PKCS8) | base64 > ' + p_storeinto_pfn
    process = subprocess.Popen(['bash', '-c', cmd_str], stdout=subprocess.PIPE)
    i_out, i_err = process.communicate()
Enter fullscreen mode Exit fullscreen mode

Wrapper in Python to Decrypt

def FetchDecryptingWithPrivateKey( p_pfn ):
    i_secret = ""
    cmd_str = "base64 -d " + p_pfn + " | openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa"
    process = subprocess.Popen(['bash', '-c', cmd_str], stdout=subprocess.PIPE)
    i_secret, err = process.communicate()
    return i_secret
Enter fullscreen mode Exit fullscreen mode

Acknowledgements

While I did quite a bit of reading around to reach this little solution, the two ideas that I eventually used came by adapting suggestions found on these two pages:

The reason I've written this posting is just to provide an example of putting it all together as a solution.

Top comments (0)

Update Your DEV Experience Level:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. 🛠