Last Updated: September 29, 2021
·
145.3K
· itseranga

Signing configs with gradle android

Signing apps

  • Android apps required to digitally signed with a certificate before installing

  • Android identifies author of the app using this certificate,

  • This certificate need not to be signed by certificate authority(you can use self signed certificate)

  • There are two types of signing modes debug mode and release mode

  • In debug mode, you sign your app with a debug certificate generated by the Android SDK tools. This certificate has a private key with a known password

  • This certificate locates on $HOME/.android/debug.keystore

  • In release mode you sign the app with your own certificate (actually you have to generate your own key store)

  • More information about signing android application
    http://developer.android.com/tools/publishing/app-signing.html

Signing config

  • In gradle based android projects, the signing configuration should be specified in the gradle build scripts

  • Following are the details that need to be specify in gradle build script

  1. keystore(location)
  2. keystore password
  3. key alias name
  4. key password
  5. The store type
  • In debug version you don't need to specify any of these details, it automatically takes debug certificate details

  • But build system does not sign the release version unless you explicitly define a signing configuration for the build, so in release type you have to specify above details in gradle build file

  • Rest of this article gives you detailed information about specifying release signing config

Directory structure

  • Following is my project structure

Picture

  • I have senz app under Score project, I'm define signing configs in build.gradle file in senz app(since it is my main app)

Simply define release signing configs

  • First copy release key-store in to senz app

Picture

  • Then define key-store details in build.gradle file(it need to add under android section in build file)
android {
    ...
    signingConfigs {
        release {
            storeFile file("release-key.keystore")
            storePassword 'passwotd'
            keyAlias 'alias'
            keyPassword 'password'
        }
    }

    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }

        debug {
            debuggable true
        }
    }
}
...
  • Now execute following command from command line
./gradlew assembleRelease
  • It will generate release apk - Score/senz/build/apk/senz-release.apk

  • In above signing config I have defined all key-store and key passwords in my gradle file, it is not a good security practice

  • To solve the security issue, you can specify the build process prompt you for these passwords(If you are building from command line)

Prompt password with gradle build file

  • You can get the passwords from System.console()
storePassword System.console().readLine("\nKeystore password: ")
keyPassword System.console().readLIne("\nKey password: ")
  • So build file would be looks like below
android {
    ...
    signingConfigs {
        release {
            storeFile file("release-key.keystore")
            storePassword System.console().readLine("\nKeystore password: ")
            keyAlias 'alias'
            keyPassword System.console().readLIne("\nKey password: ")
        }
    }
    ...
}
...
  • When you build from command line it will prompt for the passwords

Picture

  • There are several issues with this approach also

Problem 1 - Password will prompt every time

  • This password will be prompt for every time,

  • For an instance if you clean the build ./gradlew clean, it will ask for password

Picture

gradle.taskGraph.whenReady { taskGraph ->
    if(taskGraph.hasTask(':senz:assembleRelease')) {
        password = System.console().readPassword("\nEnter password: ")
        password = new String(password)

        if(password.size() <= 0) {
            throw new InvalidUserDataException("Empty password")
        }

        // set signing config key passwords
        android.signingConfigs.release.storePassword = password
        android.signingConfigs.release.keyPassword = password
    }
}

android {
    ...
    signingConfigs {
        release {
            storeFile file("release-key.keystore")
            storePassword ''
            keyAlias 'alias'
            keyPassword ''
        }
    }
    ...
}
  • This will ask for a password only assembleRelease and assign the password into signing configs passwords(I'm using one password for both key-store and key so I can have same password for both values)

Picture

  • In this code we are using System.console().readPassword() instead of readLine(), readPassword() functions returns a char array. So need to convert it to String, otherwise build will fail

  • SignConfigs.release passwords set to empty since we are read it via taskGraph

android {
    ...
    signingConfigs {
        release {
            storeFile file("release-key.keystore")
            storePassword ''
            keyAlias 'alias'
            keyPassword ''
        }
    }
    ...
}

Problem 2 - No Console

  • If you run above build in Android Studio there is no System.console(), it will return null

  • So build will fail on AndroidStudio

  • In here instead of getting password from console we can prompt it from dialog when not having the console(actually when running the release build from AndroidStudio)

  • Following code will do it via Groovy SwingBuilder

import groovy.swing.SwingBuilder

...

gradle.taskGraph.whenReady { taskGraph ->
    if(taskGraph.hasTask(':senz:assembleRelease')) {
        def password = ""

        if (System.console() == null) {
            new SwingBuilder().edt {
                dialog(modal: true,
                        title: "Enter password",
                        alwaysOnTop: true,
                        resizable: false,
                        locationRelativeTo: null,
                        pack: true,
                        show: true
                ) {
                    vbox {
                        label(text: "Enter password: ")
                        input = passwordField()
                        button(defaultButton: true, 
                                    text: 'OK', 
                                    actionPerformed: {
                            password = input.password;
                            dispose();
                        })
                    }
                }
            }
        } else {
            password = System.console().readPassword("\nEnter password: ")
            password = new String(password)
        }

        if (password.size() <= 0) {
            throw new InvalidUserDataException("Empty password")
        }
    }
}

android {

...

}
Error:(10) java.awt.AWTError: Toolkit not found: apple.awt.CToolkit
> Toolkit not found: apple.awt.CToolkit

Reference

  1. http://developer.android.com/sdk/installing/studio-build.html
  2. http://developer.android.com/tools/publishing/app-signing.html
  3. http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Signing-Configurations
  4. https://www.timroes.de/2013/09/22/handling-signing-configs-with-gradle/
  5. https://www.timroes.de/2014/01/19/using-password-prompts-with-gradle-build-files/
  6. http://gmariotti.blogspot.com/2013/10/common-tips-about-gradle.html

2 Responses
Add your response

It's not good idea to store your keystore in project directory - you can use Gradle to fetch it from outside filesystem (outside of project directory of course)

over 1 year ago ·

Very good Post!

Yes agree with above comment. For Security reasons against reverse engineering it's not good practice to package your key in your project.

You can save your key in any directory in your computer, then point to it from gradle.
example:
storeFile file("/Users/AndroidStudioProjects/Release_keys/My-release-key.jks")

over 1 year ago ·