Trying new generation

This commit is contained in:
stcb 2025-01-28 13:08:01 +02:00
parent 823710d2b3
commit 0b1499b32e
7 changed files with 156 additions and 297 deletions

View File

@ -0,0 +1,28 @@
package com.example.keystore
import java.security.KeyStore
object KeyDeleterHelper {
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
/**
* Deletes the key pair associated with the given alias from the Android Keystore.
*
* @param alias The alias of the key pair to delete.
* @throws Exception if deletion fails.
*/
fun deleteKeyPair(alias: String) {
try {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
if (!keyStore.containsAlias(alias)) {
throw Exception("No key found with alias \"$alias\" to delete.")
}
keyStore.deleteEntry(alias)
} catch (e: Exception) {
throw Exception("Failed to delete key pair: ${e.message}", e)
}
}
}

View File

@ -0,0 +1,47 @@
package com.example.keystore
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyPairGenerator
import java.security.KeyStore
object KeyGeneratorHelper {
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
/**
* Generates an ECDSA P-256 key pair and stores it in the Android Keystore.
*
* @param alias Unique identifier for the key pair.
* @throws Exception if key generation fails.
*/
fun generateECKeyPair(alias: String) {
try {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
// Check if the key already exists
if (keyStore.containsAlias(alias)) {
throw Exception("Key with alias \"$alias\" already exists.")
}
val keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
ANDROID_KEYSTORE
)
val parameterSpec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
)
.setAlgorithmParameterSpec(java.security.spec.ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512)
.setUserAuthenticationRequired(false) // Set to true if you require user authentication
.build()
keyPairGenerator.initialize(parameterSpec)
keyPairGenerator.generateKeyPair()
} catch (e: Exception) {
throw Exception("Failed to generate EC key pair: ${e.message}", e)
}
}
}

View File

@ -14,102 +14,28 @@ import javax.crypto.SecretKey
import android.util.Base64
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.yourapp/keystore"
private val CHANNEL = "com.icingDialer/keystore"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"getSymmetricKey" -> {
try {
getOrCreateSymmetricKey()
result.success("symmetric_key_alias")
} catch (e: Exception) {
result.error("KEY_ERROR", e.message, null)
"generateKeyPair" -> {
val alias = call.argument<String>("alias")
if (alias != null) {
try {
KeyGeneratorHelper.generateECKeyPair(alias)
result.success(null)
} catch (e: Exception) {
result.error("KEY_GENERATION_FAILED", e.message, null)
}
} else {
result.error("INVALID_ARGUMENT", "Alias is required", null)
}
}
"encryptSeed" -> {
val args = call.arguments as Map<String, String>
val seed = args["seed"]?.let { Base64.decode(it, Base64.DEFAULT) }
if (seed == null) {
result.error("INVALID_ARGUMENT", "Seed is null", null)
return@setMethodCallHandler
}
try {
val encrypted = encryptSeed(seed)
result.success(Base64.encodeToString(encrypted, Base64.DEFAULT))
} catch (e: Exception) {
result.error("ENCRYPTION_ERROR", e.message, null)
}
}
"decryptSeed" -> {
val args = call.arguments as Map<String, String>
val encryptedSeed = args["encryptedSeed"]?.let { Base64.decode(it, Base64.DEFAULT) }
if (encryptedSeed == null) {
result.error("INVALID_ARGUMENT", "Encrypted seed is null", null)
return@setMethodCallHandler
}
try {
val decrypted = decryptSeed(encryptedSeed)
result.success(Base64.encodeToString(decrypted, Base64.DEFAULT))
} catch (e: Exception) {
result.error("DECRYPTION_ERROR", e.message, null)
}
}
else -> {
result.notImplemented()
}
// Handle other methods: signData, getPublicKey, deleteKeyPair
else -> result.notImplemented()
}
}
}
private fun getOrCreateSymmetricKey(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val alias = "symmetric_key_alias"
// Check if the key already exists
if (!keyStore.containsAlias(alias)) {
// Create the key if it doesn't exist
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
keyGenerator.init(keyGenParameterSpec)
return keyGenerator.generateKey()
}
// Retrieve the existing key
return keyStore.getKey(alias, null) as SecretKey
}
private fun encryptSeed(seed: ByteArray): ByteArray {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val key = keyStore.getKey("symmetric_key_alias", null) as SecretKey
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
val encryptionIv = cipher.iv
val encryptedBytes = cipher.doFinal(seed)
// Prepend IV to encrypted bytes for storage
return encryptionIv + encryptedBytes
}
private fun decryptSeed(encryptedSeed: ByteArray): ByteArray {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val key = keyStore.getKey("symmetric_key_alias", null) as SecretKey
// Extract IV and encrypted bytes
val iv = encryptedSeed.copyOfRange(0, 12) // GCM IV is 12 bytes
val ciphertext = encryptedSeed.copyOfRange(12, encryptedSeed.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, key, javax.crypto.spec.GCMParameterSpec(128, iv))
return cipher.doFinal(ciphertext)
}
}

View File

@ -0,0 +1,30 @@
package com.example.keystore
import java.security.KeyStore
import java.security.PublicKey
import android.util.Base64
object PublicKeyHelper {
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
/**
* Retrieves the public key associated with the given alias.
*
* @param alias The alias of the key pair.
* @return The public key as a Base64-encoded string.
* @throws Exception if retrieval fails.
*/
fun getPublicKey(alias: String): String {
try {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
val certificate = keyStore.getCertificate(alias) ?: throw Exception("Certificate not found for alias \"$alias\".")
val publicKey: PublicKey = certificate.publicKey
return Base64.encodeToString(publicKey.encoded, Base64.DEFAULT)
} catch (e: Exception) {
throw Exception("Failed to retrieve public key: ${e.message}", e)
}
}
}

View File

@ -0,0 +1,36 @@
package com.example.keystore
import android.security.keystore.KeyProperties
import java.security.KeyStore
import java.security.Signature
import android.util.Base64
object SignerHelper {
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
/**
* Signs the provided data using the private key associated with the given alias.
*
* @param alias The alias of the key pair.
* @param data The data to sign.
* @return The signature as a Base64-encoded string.
* @throws Exception if signing fails.
*/
fun signData(alias: String, data: ByteArray): String {
try {
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
val privateKey = keyStore.getKey(alias, null) ?: throw Exception("Private key not found for alias \"$alias\".")
val signature = Signature.getInstance("SHA256withECDSA")
signature.initSign(privateKey)
signature.update(data)
val signedBytes = signature.sign()
return Base64.encodeToString(signedBytes, Base64.DEFAULT)
} catch (e: Exception) {
throw Exception("Failed to sign data: ${e.message}", e)
}
}
}

View File

@ -1,93 +0,0 @@
// generate_with_recovery_phrase.dart
import 'package:bip39_mnemonic/bip39_mnemonic.dart';
import 'package:flutter/material.dart';
import 'widgets/key_management.dart';
class GenerateWithRecoveryPhrasePage extends StatefulWidget {
const GenerateWithRecoveryPhrasePage({super.key});
@override
_GenerateWithRecoveryPhrasePageState createState() => _GenerateWithRecoveryPhrasePageState();
}
class _GenerateWithRecoveryPhrasePageState extends State<GenerateWithRecoveryPhrasePage> {
final KeyManagement _keyManagement = KeyManagement();
bool _isGenerating = false;
String? _recoveryPhrase;
Future<void> _generateKeyPair() async {
setState(() {
_isGenerating = true;
});
try {
Mnemonic mnemonic = await _keyManagement.generateKeyPairWithRecovery();
setState(() {
_recoveryPhrase = mnemonic.toString();
});
_showRecoveryPhraseDialog(mnemonic.toString());
} catch (e) {
_showErrorDialog('Failed to generate key pair: $e');
} finally {
setState(() {
_isGenerating = false;
});
}
}
void _showRecoveryPhraseDialog(String phrase) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Recovery Phrase'),
content: SingleChildScrollView(
child: Text(
'Please write down your recovery phrase and keep it in a safe place. This phrase can be used to restore your key pair.\n\n$phrase',
style: const TextStyle(fontSize: 16),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Generate Key Pair with Recovery'),
),
body: Center(
child: _isGenerating
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _generateKeyPair,
child: const Text('Generate Key Pair'),
),
),
);
}
}

View File

@ -1,115 +0,0 @@
// restore_from_recovery_phrase.dart
import 'package:flutter/material.dart';
import './widgets/key_management.dart';
class RestoreFromRecoveryPhrasePage extends StatefulWidget {
const RestoreFromRecoveryPhrasePage({super.key});
@override
_RestoreFromRecoveryPhrasePageState createState() => _RestoreFromRecoveryPhrasePageState();
}
class _RestoreFromRecoveryPhrasePageState extends State<RestoreFromRecoveryPhrasePage> {
final KeyManagement _keyManagement = KeyManagement();
final TextEditingController _controller = TextEditingController();
bool _isRestoring = false;
Future<void> _restoreKeyPair() async {
String mnemonic = _controller.text.trim();
if (mnemonic.isEmpty) {
_showErrorDialog('Please enter your recovery phrase.');
return;
}
setState(() {
_isRestoring = true;
});
try {
EcdsaKeyPair keyPair = await _keyManagement.restoreKeyPair(mnemonic);
_showSuccessDialog('Key pair restored successfully.');
} catch (e) {
_showErrorDialog('Failed to restore key pair: $e');
} finally {
setState(() {
_isRestoring = false;
});
}
}
void _showSuccessDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Success'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Error'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Restore Key Pair'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: _isRestoring
? const Center(child: CircularProgressIndicator())
: Column(
children: [
const Text(
'Enter your 12-word recovery phrase to restore your key pair.',
style: TextStyle(color: Colors.white),
),
const SizedBox(height: 20),
TextField(
controller: _controller,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Recovery Phrase',
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _restoreKeyPair,
child: const Text('Restore Key Pair'),
),
],
),
),
);
}
}