Skip to content

Commit

Permalink
Fix readme, update dependencies, fixe sha256
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-j69 committed Feb 2, 2021
1 parent 06bec4e commit 4508618
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 66 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# v0.1.1
###Fixes
* Fix sha256 generation : long keys are now decoded correctly. Reported in https://github.com/klaxit/hidden-secrets-gradle-plugin/issues/16
* Random string generation was not including lower case characters
###Improvements
* The plugin search for `Secrets.kt` to add other keys instead of using package's name to access it.
* Resolve package from the Kotlin file to edit the C++ file to be able to use a different package than the package of Secret.kt to encode/decode keys.
* Clearer error message for `hideSecret` command
* Clearer logs
* Tasks name become public
* Use more constants in code to avoid regression
* Automatic tests added
* Detekt added to the project to ensure Kotlin coding style
### Migration from 0.1.0
* To take advantage of the sha256 generation fix you need to :
1) Remove files : `secrets.cpp`, `sha256.cpp` and `Secrets.kt` from your project (that will delete all your keys previously added)
2) You need to re-add all your keys with `hideSecret` command (will copy new cpp files and encode your key)
# v0.1.0
* First release
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![travis ci status](https://travis-ci.org/klaxit/hidden-secrets-gradle-plugin.svg?branch=master)
[![travis ci status](https://travis-ci.org/klaxit/hidden-secrets-gradle-plugin.svg?branch=master)](https://travis-ci.com/github/klaxit/hidden-secrets-gradle-plugin/branches)
[![MIT license](https://img.shields.io/github/license/klaxit/hidden-secrets-gradle-plugin)](https://github.com/klaxit/hidden-secrets-gradle-plugin/blob/master/LICENSE)

# Gradle plugin to deeply hide secrets on Android

Expand All @@ -14,9 +15,6 @@ This plugin is **used in production** at [Klaxit - Covoiturage quotidien](https:

⚠️ Nothing on the client-side is unbreakable. So generally speaking, **keeping a secret in a mobile package is not a smart idea**. But when you absolutely need to, this is the best method we have found to hide it.

## This is a kotlin gradle plugin
This project is also a demonstration on how to create a full Kotlin gradle plugin for Android projects.

## Compatibility
This gradle plugin can be used with any Android project in Java or Kotlin.

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
}

group = "com.klaxit.hiddensecrets"
version = "0.1.0"
version = "0.1.1"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/klaxit/hiddensecrets/CodeGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object CodeGenerator {

return "\nextern \"C\"\n" +
"JNIEXPORT jstring JNICALL\n" +
"Java_" + Utils.getUnderScoredPackageName(packageName) + "_Secrets_get$keyName(\n" +
"Java_" + Utils.getSnakeCasePackageName(packageName) + "_Secrets_get$keyName(\n" +
" JNIEnv* pEnv,\n" +
" jobject pThis,\n" +
" jstring packageName) {\n" +
Expand Down
117 changes: 76 additions & 41 deletions src/main/kotlin/com/klaxit/hiddensecrets/HiddenSecretsPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ open class HiddenSecretsPlugin : Plugin<Project> {
companion object {
private const val APP_MAIN_FOLDER = "src/main/"
private const val DEFAULT_KEY_NAME_LENGTH = 8
private const val KEY_PLACEHOLDER = "YOUR_KEY_GOES_HERE"
private const val PACKAGE_PLACEHOLDER = "YOUR_PACKAGE_GOES_HERE"
private const val KOTLIN_FILE_NAME = "Secrets.kt"

//Tasks
const val TASK_UNZIP_HIDDEN_SECRETS = "unzipHiddenSecrets"
Expand All @@ -27,6 +30,7 @@ open class HiddenSecretsPlugin : Plugin<Project> {
const val TASK_HIDE_SECRET = "hideSecret"
const val TASK_OBFUSCATE = "obfuscate"
const val TASK_PACKAGE_NAME = "packageName"
const val TASK_FIND_KOTLIN_FILE = "findKotlinFile"

//Properties
private const val KEY = "key"
Expand Down Expand Up @@ -74,7 +78,6 @@ open class HiddenSecretsPlugin : Plugin<Project> {
*/
@Input
fun getPackageNameParam(): String {
//From config
var packageName: String? = null
if (project.hasProperty(PACKAGE)) {
//From command line
Expand All @@ -95,7 +98,7 @@ open class HiddenSecretsPlugin : Plugin<Project> {
*/
@Input
fun getKeyNameParam(): String {
val chars = ('a'..'Z') + ('A'..'Z')
val chars = ('a'..'z') + ('A'..'Z')
// Default random key name
var keyName = List(DEFAULT_KEY_NAME_LENGTH) { chars.random() }.joinToString("")
if (project.hasProperty(KEY_NAME)) {
Expand Down Expand Up @@ -136,20 +139,27 @@ open class HiddenSecretsPlugin : Plugin<Project> {
}
val directory = project.file(path)
if (!directory.exists()) {
error("Directory $path does not exist in the project, you might have selected a wrong package.")
println("Directory $path does not exist in the project, you might have selected a wrong package.")
}
path += fileName
return project.file(path)
}

/**
* If found, returns the Secrets.kt file in the Android app
*/
fun getKotlinFile(): File? {
return Utils.findFileInProject(project, APP_MAIN_FOLDER, KOTLIN_FILE_NAME)
}

/**
* Copy Cpp files from the lib to the Android project if they don't exist yet
*/
fun copyCppFiles() {
project.file("$tmpFolder/cpp/").listFiles()?.forEach {
val destination = getCppDestination(it.name)
if (destination.exists()) {
println(it.name + " already exists")
println("${it.name} already exists")
} else {
println("Copy $it.name to\n$destination")
it.copyTo(destination, true)
Expand All @@ -158,14 +168,18 @@ open class HiddenSecretsPlugin : Plugin<Project> {
}

/**
* Copy Kotlin files from the lib to the Android project if they don't exist yet
* Copy Kotlin file Secrets.kt from the lib to the Android project if it does not exist yet
*/
fun copyKotlinFiles() {
fun copyKotlinFile() {
if (getKotlinFile() != null) {
println("$KOTLIN_FILE_NAME already exists")
return
}
val packageName = getPackageNameParam()
project.file("$tmpFolder/kotlin/").listFiles()?.forEach {
val destination = getKotlinDestination(packageName, it.name)
if (destination.exists()) {
println(it.name + " already exists")
println("${it.name} already exists")
} else {
println("Copy $it.name to\n$destination")
it.copyTo(destination, true)
Expand All @@ -177,15 +191,15 @@ open class HiddenSecretsPlugin : Plugin<Project> {
* Unzip plugin into tmp directory
*/
project.tasks.create(TASK_UNZIP_HIDDEN_SECRETS, Copy::class.java,
object : Action<Copy?> {
@TaskAction
override fun execute(copy: Copy) {
// in the case of buildSrc dir
copy.from(project.zipTree(javaClass.protectionDomain.codeSource.location!!.toExternalForm()))
println("Unzip jar to $tmpFolder")
copy.into(tmpFolder)
}
})
object : Action<Copy?> {
@TaskAction
override fun execute(copy: Copy) {
// in the case of buildSrc dir
copy.from(project.zipTree(javaClass.protectionDomain.codeSource.location!!.toExternalForm()))
println("Unzip jar to $tmpFolder")
copy.into(tmpFolder)
}
})

/**
* Copy C++ files to your project
Expand All @@ -203,7 +217,7 @@ open class HiddenSecretsPlugin : Plugin<Project> {
project.task(TASK_COPY_KOTLIN)
{
doLast {
copyKotlinFiles()
copyKotlinFile()
}
}

Expand All @@ -225,54 +239,66 @@ open class HiddenSecretsPlugin : Plugin<Project> {
dependsOn(TASK_UNZIP_HIDDEN_SECRETS)

doLast {
//Copy files if they do not exist
//Assert that the key is present
getKeyParam()
//Copy files if they don't exist
copyCppFiles()
copyKotlinFiles()
copyKotlinFile()

val keyName = getKeyNameParam()
val packageName = getPackageNameParam()
val obfuscatedKey = getObfuscatedKey()

//Add method in Kotlin code
var secretsKotlin = getKotlinFile()
if (secretsKotlin == null) {
//File not found in project
secretsKotlin = getKotlinDestination(packageName, KOTLIN_FILE_NAME)
}
if (secretsKotlin.exists()) {
var text = secretsKotlin.readText(Charset.defaultCharset())
text = text.replace(PACKAGE_PLACEHOLDER, packageName)
if (text.contains(keyName)) {
println("⚠️ Method already added in Kotlin !")
}
text = text.dropLast(1)
text += CodeGenerator.getKotlinCode(keyName)
secretsKotlin.writeText(text)
} else {
error("Missing Kotlin file, please run gradle task : $TASK_COPY_KOTLIN")
}
//Resolve package name for C++ from the one used in Kotlin file
var kotlinPackage = Utils.getKotlinFilePackage(secretsKotlin)
if (kotlinPackage.isEmpty()) {
println("Empty package in $KOTLIN_FILE_NAME")
kotlinPackage = packageName
}

//Add obfuscated key in C++ code
val secretsCpp = getCppDestination("secrets.cpp")
if (secretsCpp.exists()) {
var text = secretsCpp.readText(Charset.defaultCharset())
if (text.contains(obfuscatedKey)) {
println("Key already added in C++ !")
println("⚠️ Key already added in C++ !")
}
if (text.contains("YOUR_KEY_GOES_HERE")) {
if (text.contains(KEY_PLACEHOLDER)) {
//Edit placeholder key
//Replace package name
text = text.replace("YOUR_PACKAGE_GOES_HERE", Utils.getUnderScoredPackageName(packageName))
text = text.replace(PACKAGE_PLACEHOLDER, Utils.getSnakeCasePackageName(kotlinPackage))
//Replace key name
text = text.replace("YOUR_KEY_NAME_GOES_HERE", keyName)
//Replace demo key
text = text.replace("{YOUR_KEY_GOES_HERE}", obfuscatedKey)
text = text.replace(KEY_PLACEHOLDER, obfuscatedKey)
secretsCpp.writeText(text)
} else {
//Add new key
text += CodeGenerator.getCppCode(packageName, keyName, obfuscatedKey)
text += CodeGenerator.getCppCode(kotlinPackage, keyName, obfuscatedKey)
secretsCpp.writeText(text)
}
} else {
error("Missing C++ file, please run gradle task : $TASK_COPY_CPP")
}

//Add method in Kotlin code
val secretsKotlin = getKotlinDestination(packageName, "Secrets.kt")
if (secretsKotlin.exists()) {
var text = secretsKotlin.readText(Charset.defaultCharset())
text = text.replace("YOUR_PACKAGE_GOES_HERE", packageName)
if (text.contains(keyName)) {
println("Method already added in Kotlin !")
}
text = text.dropLast(1)
text += CodeGenerator.getKotlinCode(keyName)
secretsKotlin.writeText(text)
} else {
error("Missing Kotlin file, please run gradle task : $TASK_COPY_KOTLIN")
}

println("You can now get your secret key by calling : Secrets().get$keyName(packageName)")
println("✅ You can now get your secret key by calling : Secrets().get$keyName(packageName)")
}
}

Expand All @@ -285,5 +311,14 @@ open class HiddenSecretsPlugin : Plugin<Project> {
println("APP PACKAGE NAME = " + getPackageNameParam())
}
}

/**
* Find Secrets.kt file in the project
*/
project.task(TASK_FIND_KOTLIN_FILE) {
doLast {
getKotlinFile()
}
}
}
}
31 changes: 30 additions & 1 deletion src/main/kotlin/com/klaxit/hiddensecrets/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.klaxit.hiddensecrets

import com.google.common.annotations.VisibleForTesting
import org.gradle.api.Project
import java.io.File
import java.nio.charset.Charset
import java.security.MessageDigest
import kotlin.experimental.xor
Expand All @@ -10,7 +12,7 @@ object Utils {
/**
* Transform package name com.klaxit.hidden to com_klaxit_hidden to ingrate in C++ code
*/
fun getUnderScoredPackageName(packageName: String): String {
fun getSnakeCasePackageName(packageName: String): String {
val packageComponents = packageName.split(".")
var packageStr = ""
val iterator: Iterator<String> = packageComponents.iterator()
Expand Down Expand Up @@ -63,4 +65,31 @@ object Utils {
encoded += " }"
return encoded
}

/**
* Search a file in the finale project, can provide a path to limit the search in some folders
*/
fun findFileInProject(project: Project, path: String, fileName: String): File? {
val directory = project.file(path)
directory.walkBottomUp().forEach {
if (it.name == fileName) {
println("$fileName found in ${it.absolutePath}\n")
return it
}
}
println("$fileName not found in $path")
return null
}

/**
* Return package from first line of a kotlin file
*/
fun getKotlinFilePackage(file: File): String {
var text = file.readLines(Charset.defaultCharset())[0]
text = text.replace("package ", "")
// Handle package name using keywords
text = text.replace("`", "")
println("Package : $text found in ${file.name}")
return text
}
}
9 changes: 6 additions & 3 deletions src/main/resources/cpp/secrets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ jstring getOriginalKey(

// Get the obfuscating string SHA256 as the obfuscator
const char *obfuscatingStr = pEnv->GetStringUTFChars(obfuscatingJStr, NULL);
const char *obfuscator = sha256(obfuscatingStr);
char buffer[2*SHA256::DIGEST_SIZE + 1];

// Apply a XOR between the obfuscated key and the obfuscating string to get original sting
sha256(obfuscatingStr, buffer);
const char* obfuscator = buffer;

// Apply a XOR between the obfuscated key and the obfuscating string to get original string
char out[obfuscatedSecretSize + 1];
for (int i = 0; i < obfuscatedSecretSize; i++) {
out[i] = obfuscatedSecret[i] ^ obfuscator[i % strlen(obfuscator)];
Expand All @@ -67,6 +70,6 @@ Java_YOUR_PACKAGE_GOES_HERE_Secrets_getYOUR_KEY_NAME_GOES_HERE(
JNIEnv *pEnv,
jobject pThis,
jstring packageName) {
char obfuscatedSecret[] = {YOUR_KEY_GOES_HERE};
char obfuscatedSecret[] = YOUR_KEY_GOES_HERE;
return getOriginalKey(obfuscatedSecret, sizeof(obfuscatedSecret), packageName, pEnv);
}
15 changes: 7 additions & 8 deletions src/main/resources/cpp/sha256.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,19 @@ void SHA256::final(unsigned char *digest)
}
}

const char* sha256(const char* input)
void sha256(const char* input, char buf[2*SHA256::DIGEST_SIZE + 1])
{
unsigned char digest[SHA256::DIGEST_SIZE];
memset(digest,0,SHA256::DIGEST_SIZE);
memset(digest, 0, SHA256::DIGEST_SIZE);

SHA256 ctx = SHA256();
ctx.init();
ctx.update( (unsigned char*)input, strlen(input));
ctx.final(digest);

char buf[2*SHA256::DIGEST_SIZE+1];

buf[2*SHA256::DIGEST_SIZE] = 0;
for (int i = 0; i < SHA256::DIGEST_SIZE; i++)
sprintf(buf+i*2, "%02x", digest[i]);
return buf;
for (int i = 0; i < SHA256::DIGEST_SIZE; i++) {
sprintf(buf + i * 2, "%02x", digest[i]);
}
}

4 changes: 2 additions & 2 deletions src/main/resources/cpp/sha256.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class SHA256
typedef unsigned int uint32;

const static uint32 sha256_k[];
static const unsigned int SHA224_256_BLOCK_SIZE = (512/8);
static const unsigned int SHA224_256_BLOCK_SIZE = (512 / 8);
public:
void init();
void update(const unsigned char *message, unsigned int len);
void final(unsigned char *digest);
static const unsigned int DIGEST_SIZE = ( 256 / 8);
static const unsigned int DIGEST_SIZE = (256 / 8);

protected:
void transform(const unsigned char *message, unsigned int block_nb);
Expand Down
Loading

0 comments on commit 4508618

Please sign in to comment.