Безопасное случайное начальное число не работает в MainActivity

Я пытаюсь получить одни и те же ключи каждый раз, когда запускаю свое приложение — это делается для того, чтобы я мог легко отлаживать и получать одинаковые выходные данные для своих функций при каждом запуске.

Однако у меня возникла очень странная проблема: в моих модульных тестах это работает так, как ожидалось, и каждый раз возвращает один и тот же открытый ключ. Однако если я запускаю один и тот же код из своего основного приложения, оно каждый раз генерирует другой код.

Создать терминалEphemeralPublicPrivateKeys

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()
    }
}

Создать терминалEphemeralPublicPrivateKeysTest

Это работает так, как ожидалось, и проходит каждый раз

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")
    }


}

Основная деятельность

При этом каждый раз регистрируется другой ключ, что неверно.

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")
    }
}

Сборка.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")

}
SecureRandom не полагается исключительно на начальное значение, предоставленное пользователем. Он будет генерировать новые случайные значения независимо от того, какое начальное значение вы предоставите. Если вам нужна повторяемость, используйте обычный Random.
Elliott Frisch 02.08.2024 11:58

Или примените специальную функцию получения ключей, например PBKDF2, вместо генератора случайных чисел. Ключевая функция вывода гарантирует воспроизводимость.

Topaco 02.08.2024 12:32

@Topaco У меня та же проблема с PBKDF2. Каждый раз, когда я запускаю действие, он генерирует другой, но в тестах он один и тот же.

Tom Rowbotham 02.08.2024 13:11

@ElliottFrisch, спасибо, но я не знаю, как это сделать. KeyGen требует SecureRandom

Tom Rowbotham 02.08.2024 13:13

Тогда вы делаете что-то не так. Без кода невозможно сказать, что именно. Опубликуйте MCVE, который содержит только соответствующую логику.

Topaco 02.08.2024 13:21
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Спасибо @Topaco, теперь это работает.

import org.bouncycastle.crypto.params.ECDomainParameters
import org.bouncycastle.crypto.params.ECPrivateKeyParameters
import org.bouncycastle.crypto.params.ECPublicKeyParameters
import org.bouncycastle.math.ec.FixedPointCombMultiplier
import java.math.BigInteger
import java.security.InvalidAlgorithmParameterException
import java.security.NoSuchAlgorithmException
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec

class GenerateTerminalEphemeralPublicPrivateKeys {

    @OptIn(ExperimentalStdlibApi::class)
    @Throws(NoSuchAlgorithmException::class, InvalidAlgorithmParameterException::class)
    fun generate(): String {
        // Derive key from a fixed seed using PBKDF2
        val seed = "test-seed"
        val salt = ByteArray(16) { 0 } // fixed salt for deterministic output
        val iterations = 10000
        val keyLength = 256
        val keySpec = PBEKeySpec(seed.toCharArray(), salt, iterations, keyLength)
        val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
        val derivedKey = secretKeyFactory.generateSecret(keySpec).encoded

        // Convert derived key to BigInteger
        val privateKeyD = BigInteger(1, derivedKey.sliceArray(0 until 32))

        // Use the derived private key to generate the public key
        val x9 = CustomNamedCurves.getByName("secp256r1")
        val curve = x9.curve
        val domainParams = ECDomainParameters(curve, x9.g, x9.n, x9.h)
        val privateKeyParams = ECPrivateKeyParameters(privateKeyD, domainParams)
        val publicKeyParams = ECPublicKeyParameters(
            FixedPointCombMultiplier().multiply(domainParams.g, privateKeyD),
            domainParams
        )

        val publicKeyByteArray = publicKeyParams.q.getEncoded(true)

        return publicKeyByteArray.toHexString()
    }

    private fun ByteArray.toHexString(): String {
        return joinToString("") { "%02x".format(it) }
    }
}

Другие вопросы по теме