I am trying to get the same keys every time I run my app – this is so I can easily debug and have the same output for my functions every time I run.
I am getting a super weird problem though, where in my unit tests, this works as expected and returns the same public key every time. However, if I run the exact same code from my main application, it generates a different code every time.
GenerateTerminalEphemeralPublicPrivateKeys
package com.test.tomtestplayground
import org.bouncycastle.asn1.x9.ECNamedCurveTable
import java.math.BigInteger
import java.security.InvalidAlgorithmParameterException
import java.security.KeyPairGenerator
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.security.interfaces.ECPublicKey
import java.security.spec.ECGenParameterSpec
class GenerateTerminalEphemeralPublicPrivateKeys {
@OptIn(ExperimentalStdlibApi::class)
@Throws(NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class)
fun generate(): String {
// Fixed seed for SecureRandom to generate the same key pair every time
val secureRandom = SecureRandom.getInstance("SHA1PRNG")
secureRandom.setSeed("test-seed".toByteArray())
val keyGen = KeyPairGenerator.getInstance("EC")
keyGen.initialize(ECGenParameterSpec("secp256r1"), secureRandom)
val pair = keyGen.generateKeyPair()
val publicKey = pair.public as ECPublicKey
val privateKey = pair.private
val x: ByteArray = publicKey.getW().getAffineX().toByteArray()
val y: ByteArray = publicKey.getW().getAffineY().toByteArray()
val xbi = BigInteger(1, x)
val ybi = BigInteger(1, y)
val x9 = ECNamedCurveTable.getByName("secp256r1")
val curve = x9.curve
val point = curve.createPoint(xbi, ybi)
val publicKeyByteArray = point.getEncoded(true)
return publicKeyByteArray.toHexString()
}
}
GenerateTerminalEphemeralPublicPrivateKeysTest
This works as expected and passes every time
package com.test.tomtestplayground
import android.nfc.Tag
import android.nfc.tech.IsoDep
import io.mockk.*
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.security.Security
@RunWith(RobolectricTestRunner::class)
class GenerateTerminalEphemeralPublicPrivateKeysTest {
private val generateTerminalEphemeralPublicPrivateKeys = GenerateTerminalEphemeralPublicPrivateKeys()
@Before
fun setUp() {
Security.addProvider(BouncyCastleProvider())
}
@Test
fun shouldReturnSameKeyEveryTime() {
val publicKeyCompressed = generateTerminalEphemeralPublicPrivateKeys.generate()
assertEquals(publicKeyCompressed, "032f0ca7da7eb706c7de924e534bcb76e36a471ed2ea0c99b2ad59e70394d83bf6")
}
}
MainActivity
This logs a different key every single time – which is wrong.
package com.test.tomtestplayground
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.passentry.tomtestplayground.ui.theme.TomTestPlaygroundTheme
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.Security
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Security.addProvider(BouncyCastleProvider())
Log.d("MainActivity", GenerateTerminalEphemeralPublicPrivateKeys().generate())
setContent {
TomTestPlaygroundTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
TomTestPlaygroundTheme {
Greeting("Android")
}
}
Build.gradle
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
}
android {
namespace = "com.passentry.tomtestplayground"
compileSdk = 34
defaultConfig {
applicationId = "com.passentry.tomtestplayground"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation("org.bouncycastle:bcpkix-jdk15on:1.68")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
testImplementation("org.robolectric:robolectric:4.11.1")
testImplementation("org.bouncycastle:bcprov-jdk15on:1.68") // Added BouncyCastle provider
testImplementation("org.bouncycastle:bcpkix-jdk15on:1.68") // Added BouncyCastle PKIX
testImplementation("io.mockk:mockk:1.12.0")
}