Sign JAR Files Securely with Gradle

Asterios Raptis
3 min readFeb 4, 2022

--

Securing your Java application by signing JAR files is a best practice. My concern stems from a past experience where a company I worked for frequently modified class files directly within JAR files on the server to avoid redeployment, as their deployment process was extremely time-consuming. To prevent such behavior and ensure the integrity of your application, signing your JAR files is essential.

We will use gradle as build tool so we will have to add the jar task. Here is an example took from my repository mystic-crpyt-ui.

jar {
manifest {
attributes(
"Name" : project.name,
"Manifest-Version" : project.version,
"Main-Class" : "$mainClass",
"Implementation-Title" : "$groupPackage" + "." + "$rootProject.name",
"Implementation-Version": project.version,
"Implementation-Vendor" : "$projectLeaderName",
"Created-By" : "Gradle ${gradle.gradleVersion}",
"Built-By" : "$projectLeaderName",
"Build-Timestamp" : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
"Build-Jdk" : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})",
"Build-OS" : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}")
}
}

This creates the jar file with a manifest file. But is still not signed. For signing all jar files you have to add new ‘doLast’ action:

jar.doLast {
def pkAlias = project.property("$mysticCryptKeyAlias")
def keystoreFileName = project.property("$mysticCryptStoreFile")
def storePassword = project.property("$mysticCryptStorePassword")
def keyPassword = project.property("$mysticCryptKeyPassword")
def keystoreType = project.property("$mysticCryptStoreType")
def libsFilePath = "$buildDir/libs"
def libFiles = files { file(libsFilePath).listFiles() }
def destDir = "$buildDir/signed"
def signedDir = new File(destDir)
signedDir.mkdirs()
libFiles.each {
ant.signjar(
jar: it,
destDir: "$destDir",
alias:"$pkAlias",
storetype:"$keystoreType",
keystore:"$keystoreFileName",
storepass:"$storePassword",
keypass:"$keyPassword",
preservelastmodified:"true"
)
}
}

I extracted all sensible data as first step in the project gradle.properties.

##############################
# keystore properties keys #
##############################
mysticCryptStoreFile=release.mystic-crypt.store.file
mysticCryptStoreType=release.mystic-crypt.store.type
mysticCryptStorePassword=release.mystic-crypt.store.password
mysticCryptKeyPassword=release.mystic-crypt.key.password
mysticCryptKeyAlias=release.mystic-crypt.key.alias

The repository gradle.properties is public, so the real sensible data are kept in the local file ‘~/.gradle/gradle.properties’.

##############################
# keystore values #
##############################
release.mystic-crypt.store.file=/home/astrapi69/dev/app.jks
release.mystic-crypt.store.type=jks
release.mystic-crypt.store.password=secret-password-hack-me
release.mystic-crypt.key.alias=mystic-crypt-pk-alias
release.mystic-crypt.key.password=secret-password-hack-me

So in the repository gradle.properties are kept only the properties keys of the local file ‘~/.gradle/gradle.properties’.

Note that the full path of the keystore file have to be given, if not you get an error:

jarsigner returned: 1

If you encounter the error jarsigner returned: 1, revisit the jar.doLast task to ensure everything is set up correctly. After building your project, all JAR files are stored in the $buildDir/libs directory:

def libsFilePath = "$buildDir/libs"
def libFiles = files { file(libsFilePath).listFiles() }

The signed JAR files will be stored in the $buildDir/signed directory. If the directory doesn’t already exist, it will be created:

def destDir = "$buildDir/signed"
def signedDir = new File(destDir)
signedDir.mkdirs()

Next, iterate over all JAR files from the $buildDir/libs directory and sign them using ant.signjar:

libFiles.each {
ant.signjar(
jar: it,
destDir: "$destDir",
alias: "$pkAlias",
storetype: "$keystoreType",
keystore: "$keystoreFileName",
storepass: "$storePassword",
keypass: "$keyPassword",
preservelastmodified: "true"
)
}

Once the signing process is complete, all JAR files in your build will be signed. In my workflow, these signed JAR files are used to create the installation package with IzPack.

Conclusion

Signing JAR files as part of your Gradle build process is a critical step to ensure the integrity and authenticity of your Java applications. This practice prevents unauthorized modifications, such as the alteration of class files, which can compromise the security and reliability of your application. By integrating a signing task into your Gradle build, you ensure that every JAR file generated is protected against tampering.

The approach outlined here demonstrates how to set up signing using ant.signjar within a Gradle task. Sensitive keystore information is securely managed through externalized property files, separating public and private data for enhanced security. By defining these properties in gradle.properties and localizing their actual values in the ~/.gradle/gradle.properties file, your project maintains both transparency and confidentiality.

This process not only secures your application but also streamlines its deployment. With signed JAR files, you can confidently distribute your software, knowing that its integrity is verifiable. For a complete example, refer to the mystic-crypt-ui GitHub repository, where this signing setup is implemented and integrated into the build process.

Feel free to connect on LinkedIn or check out more articles on Medium.

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏

--

--

Asterios Raptis
Asterios Raptis

Written by Asterios Raptis

🚀 Software Consultant, Fullstack Developer, Author, Philosopher & Data Scientist with 30+ years' experience. Writing about tech, coding, and innovation. 💻✨

No responses yet