There are many wide-spread systems for dependency and build management. The officially supported build system for Android development is Gradle for a long time now. The build scripts (build.gradle files) used for assembling the modules are typically stored in version control systems. This post will show how to avoid storing passwords in these version controlled scripts.

Before uploading an application to the Play Store, its APK file has to be signed with a key created by the developer of the application. This key is used to prevent an unauthorized person from publishing an update to your application, because the APK in the new version has to be signed with the exact same key that was used for the first version.

Signing methods

There are multiple ways for creating and using the signing key. The first and most straight-forward method is to use Android Studio to build the APK and sign it.

However, for shared and version controlled codes, the best way is to describe the signing configurations in the build script. This can be seen in this example code. The relevant configuration can be seen in app/build.gradle

signingConfigs {
	releaseSigningConfig {
		storeFile file('ExampleKeyStore')
		storePassword 'keystorepassword'
		keyAlias 'examplekey'
		keyPassword 'keypassword'
	}
}

buildTypes {
	release {
		minifyEnabled false
		proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
		signingConfig signingConfigs.releaseSigningConfig
	}
}

This is a typical configuration: release build is signed with custom key, but debug build uses the default Android debug key. However, as you can see from the highlighted lines, there is a huge issue with this configuration: the plain text passwords appear in the build script that is under version control. This is a very bad practice that has to be avoided somehow.

Securing the passwords

Securing the passwords can be achieved in more than one ways. The official documentation recommends using properties files for storing the sensitive information. Those files are not version controlled, so the secrets are not exposed. The properties can be in a project-specific property file or the global gradle.properties in the user’s home directory.

Properties files still contain plain text passwords. The Gradle Credentials Plugin can improve the property-based solution. However, all of the aforementioned solutions mean that the passwords or their encrypted forms are still stored at some place. What if I say that it is possible to sign the release builds with dynamically entered passwords?

Interactive passwords

It is a well-known advice that passwords should never be written down. Fortunately, there is a way to request the passwords interactively during build. However, this method is not suitable for automated builds because it requires user interaction.

The project with the final build script can be seen here. Before the explanation, I would like to mention that the basic idea is not mine. I found the base of this solution on StackOverflow long ago and I do not have the link to the source any more. Anyway, I tried to sophisticate and automate the script and what I present here is the result of that.

There are two main elements. The first one is a task that is defined in the main build.gradle file:

def inputPassword(String dialogTitle, String dialogMessage) {
	def console = System.console();
	if (console != null) {
		return console.readPassword(dialogMessage)
	}

	while (true) {
		def enteredPassword = null
		new SwingBuilder().edt {
			dialog(
					modal: true, //Otherwise the build will continue running
					title: dialogTitle,
					alwaysOnTop: true,
					resizable: false,
					locationRelativeTo: null, //Place dialog in center of the screen
					pack: true, //We need to pack the dialog (so it will take the size of its children
					show: true
			) {
				vbox { //Put everything below each other
					label(text: dialogMessage)
					input = passwordField()
					button(defaultButton: true, text: 'OK', actionPerformed: {
						enteredPassword = new String(input.password)
						dispose();
					})
				}
			}
		}
		return enteredPassword
	}
}

I chose the main build script because a project may have multiple application modules and duplication can be avoided this way. As it can be seen, the method tries to use the system console, if there is one. If the console is not available, a window will pop up asking for the password. The title and message for the windows (and console input) are input parameters of this method. The method is called by a new task that is defined in the application module’s build.gradle file:

task('inputKeyPasswords') {
	doLast {
		def keyStorePassword = null
		while (!(keyStorePassword instanceof String) || ((String) keyStorePassword).isEmpty()) {
			keyStorePassword = inputPassword('KeyStore password', 'Please enter the password for the keystore:')
		}

		def keyPassword = null
		while (!(keyPassword instanceof String) || ((String) keyPassword).isEmpty()) {
			keyPassword = inputPassword('Key password', 'Please enter the password for the signing key:')
		}

		android.signingConfigs.releaseSigningConfig.storePassword = keyStorePassword
		android.signingConfigs.releaseSigningConfig.keyPassword = keyPassword
	}
}

As you can see, this is a new task called inputKeyPasswords. The task asks for the keystore password first, and then it prompts for the key password. Though it cannot check the correctness of the values, it does not accept null or empty value and tries again until a usable input is received. As soon as this happens, the storePassword and keyPassword of releaseSigningConfig is updated to the values entered by the developer running the build.

The only thing that’s left is to add the new task to the task execution graph. Since the Android Gradle Plugin creates and adds tasks dynamically for every build variant, this callback has to be used for settings this task as a dependency of the release build’s signing configuration validation:

tasks.whenTaskAdded { task ->
	if (task.name.trim() == 'validateSigningRelease') {
		task.dependsOn inputKeyPasswords
	}
}

Please note that the highlighted condition depends on your project structure. In this example, no product flavors are defined, so the project only has two build variants: debug and release, and the signature is only needed for the release build. The password input task can be the dependency of another task or even multiple tasks if needed. This can be achieved easily by adjusting the condition.