How to store a Android Keystore safely on GitHub Actions

Stefan M.
4 min readMar 24, 2020

Have you ever wondered what the exact use case of Base64 is? I mean, I use it to “crypt” some text and paste it to somewhere else (e.g Slack). Just for fun. Not thinking about it further.
On the other hand, I know that images could be encoded in Base64 and displayed in a browser. For instance you could copy the content of this GitHub Gist into your browser address bar and it will display the image.

And well, this is basically the main idea behind Base64. But let me explain my problem and how Base64 (or better GnuPG) solves it first.

I plan to open source an Android app. Yep, you’re reading it right. An app not a library. And of course I want to publish the app to the Play Store. This requires that I sign it with a Java Keystore (JKS). But I don’t want to open source the JKS within the repository, also not encrypted. Additionally I want to use GitHub Actions to create the release APK and therefore it requires somehow access to the JKS.

So, how to do this?
First I thought I can simply add the JKS to my .gitignore and add the file to GitHub Actions ‘somehow’. The first thing worked of course. But then I realized that you can not upload files as GitHub Actions secrets! You can only add strings. In their official documentation they said that it should be used to store ‘access token’ and such kind of things. But they also suggest, because these secrets are limited to 64 KB, to use gpg to encrypt your secret files, put it into the repository and add the decryption passphrase to the secrets so that an Action can decrypt it.

But does it mean I have to add the JKS encrypted to the git repository? No!
I studied the gpg documentation a little bit and found an interesting parameter called --armor. The documentation say

This option takes output from commands and prints it in format that can be safely e-mailed.

This sounds interesting to me because what does “in a format that can be safely e-mailed” mean?

I tried it out and the output was surprisingly a Base64 string!

Guess what I can do with this string which represent a “file”? Right, placing it into GitHub Actions as a secret (string 😛), together with the passphrase of course, and decrypt the file in an Action 🎉:

gpg -d --passphrase [Password] --batch Message.txt.asc > Message.txt

After this test I opened the Base64 Wikipedia page and read:

[…] Base64 is a group of binary-to-text encoding schemes that represent binary data in an ASCII string format […]

And this is exactly what a browser does if you add a Base64 encoded string into the address bar. It decodes the string to get a “file”, which is hopefully an image, which can be displayed in the browser. Simple, isn’t it?

You can also make use of the base64 CLI tool — which is part of coreutils for Linux and (it seems) part of the utilities on macOS — to translate any file into a Base64 string or a Base64 string back to a file:

base64 --input [File] --output [File].b64
base64 -d --input [File].b64 --output [File]
Alpine & Ubuntu:
base64 [File] > [File].b64
base64 -d [File].b64 > [File]

Long story short.

I found out that Base64 is just a string representation of a file. The base64 CLI tool can translate any file to a Base64 string and vice versa. I can also use gpg to encrypt a file and get its Base64 representation with the --armor option. Decrypting the Base64 string back to a file with gpg works as well.

Back to the original question:
How to store the Android Keystore on GitHub Actions?

The flow should be the following.

  1. Encrypt the Keystore on the client safely with gpg:
gpg -c --armor release.keystore

This will prompt for a passphrase which you have to enter. The output is a release.keystore.asc file which contains the Base64 string.

2. Copy the Base64 string into a GitHub Action secret together with the passphrase:

3. Use the secrets in a Action to restore the JKS: