Refactor + edition of key component
This commit is contained in:
parent
b15ae302b6
commit
823710d2b3
@ -0,0 +1,115 @@
|
|||||||
|
package com.icingDialer
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import android.os.Build
|
||||||
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
|
import android.security.keystore.KeyProperties
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import java.security.KeyStore
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import android.util.Base64
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
private val CHANNEL = "com.yourapp/keystore"
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(@NonNull 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'key_storage.dart';
|
import 'widgets/key_storage.dart';
|
||||||
|
|
||||||
class DeleteKeyPairPage extends StatelessWidget {
|
class DeleteKeyPairPage extends StatelessWidget {
|
||||||
const DeleteKeyPairPage({super.key});
|
const DeleteKeyPairPage({super.key});
|
||||||
|
@ -4,7 +4,7 @@ import 'dart:convert';
|
|||||||
import 'package:pointycastle/export.dart' as crypto;
|
import 'package:pointycastle/export.dart' as crypto;
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'key_storage.dart';
|
import 'widgets/key_storage.dart';
|
||||||
|
|
||||||
class ExportPrivateKeyPage extends StatefulWidget {
|
class ExportPrivateKeyPage extends StatefulWidget {
|
||||||
const ExportPrivateKeyPage({super.key});
|
const ExportPrivateKeyPage({super.key});
|
||||||
|
93
dialer/lib/features/settings/key/generate_keypair_new.dart
Normal file
93
dialer/lib/features/settings/key/generate_keypair_new.dart
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// 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'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ import 'dart:math';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:asn1lib/asn1lib.dart';
|
import 'package:asn1lib/asn1lib.dart';
|
||||||
import 'key_storage.dart';
|
import 'widgets/key_storage.dart';
|
||||||
|
|
||||||
class GenerateNewKeyPairPage extends StatelessWidget {
|
class GenerateNewKeyPairPage extends StatelessWidget {
|
||||||
const GenerateNewKeyPairPage({super.key});
|
const GenerateNewKeyPairPage({super.key});
|
||||||
|
115
dialer/lib/features/settings/key/load_backup.dart
Normal file
115
dialer/lib/features/settings/key/load_backup.dart
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pretty_qr_code/pretty_qr_code.dart';
|
import 'package:pretty_qr_code/pretty_qr_code.dart';
|
||||||
import 'key_storage.dart';
|
import 'widgets/key_storage.dart';
|
||||||
|
|
||||||
class DisplayPublicKeyQRCodePage extends StatelessWidget {
|
class DisplayPublicKeyQRCodePage extends StatelessWidget {
|
||||||
const DisplayPublicKeyQRCodePage({super.key});
|
const DisplayPublicKeyQRCodePage({super.key});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'key_storage.dart';
|
import 'widgets/key_storage.dart';
|
||||||
|
|
||||||
class DisplayPublicKeyTextPage extends StatelessWidget {
|
class DisplayPublicKeyTextPage extends StatelessWidget {
|
||||||
const DisplayPublicKeyTextPage({super.key});
|
const DisplayPublicKeyTextPage({super.key});
|
||||||
|
141
dialer/lib/features/settings/key/widgets/key_management.dart
Normal file
141
dialer/lib/features/settings/key/widgets/key_management.dart
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// key_management.dart
|
||||||
|
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:bip39_mnemonic/bip39_mnemonic.dart' as bip39;
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:encrypt/encrypt.dart' as encrypt; // For symmetric encryption
|
||||||
|
import 'keystore_service.dart';
|
||||||
|
|
||||||
|
class KeyManagement {
|
||||||
|
final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();
|
||||||
|
final String _encryptedSeedKey = 'encrypted_seed';
|
||||||
|
final KeystoreService _keystoreService = KeystoreService();
|
||||||
|
|
||||||
|
// Generate a new key pair with a recovery phrase
|
||||||
|
Future<bip39.Mnemonic> generateKeyPairWithRecovery() async {
|
||||||
|
// 1. Generate a 12-word recovery phrase
|
||||||
|
bip39.Mnemonic mnemonic = bip39.Mnemonic.generate(bip39.Language.english);
|
||||||
|
|
||||||
|
// 2. Derive seed from mnemonic
|
||||||
|
List<int> seed = mnemonic.seed;
|
||||||
|
|
||||||
|
// 3. Generate ECC key pair from seed
|
||||||
|
final algorithm = Ecdsa.p256(Sha256());
|
||||||
|
final keyPair = await algorithm.newKeyPairFromSeed(seed);
|
||||||
|
|
||||||
|
// 4. Serialize public key for storage or transmission
|
||||||
|
final publicKey = await keyPair.extractPublicKey();
|
||||||
|
String publicKeyBase64 = base64Encode(publicKey);
|
||||||
|
|
||||||
|
// 5. Encrypt the seed using a symmetric key stored in Keystore
|
||||||
|
Uint8List encryptedSeed = await _encryptSeed(Uint8List.fromList(seed));
|
||||||
|
|
||||||
|
// 6. Store the encrypted seed securely
|
||||||
|
await _secureStorage.write(
|
||||||
|
key: _encryptedSeedKey,
|
||||||
|
value: base64Encode(encryptedSeed),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Return the mnemonic for user to backup
|
||||||
|
return mnemonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the seed using a symmetric key from Keystore
|
||||||
|
Future<Uint8List> _encryptSeed(Uint8List seed) async {
|
||||||
|
// Retrieve the symmetric key alias
|
||||||
|
String symmetricKeyAlias = await _keystoreService.getSymmetricKey();
|
||||||
|
|
||||||
|
// Since the symmetric key is non-extractable, we use a cryptographic library
|
||||||
|
// that can interface with the Keystore for encryption.
|
||||||
|
// However, Dart's cryptography package doesn't directly support this.
|
||||||
|
// Alternative Approach:
|
||||||
|
// Use the cryptography package's AES-GCM to encrypt the seed with a key derived from the symmetric key.
|
||||||
|
|
||||||
|
// For demonstration, we'll use a placeholder symmetric key.
|
||||||
|
// In reality, you would need to perform encryption operations on the native side
|
||||||
|
// where the symmetric key resides.
|
||||||
|
|
||||||
|
// Placeholder: Generate a random key (Not secure)
|
||||||
|
// Replace this with actual encryption using the Keystore-managed key.
|
||||||
|
final algorithm = AesGcm.with256bits();
|
||||||
|
final secretKey = await algorithm.newSecretKey();
|
||||||
|
final nonce = algorithm.newNonce();
|
||||||
|
final encrypted = await algorithm.encrypt(
|
||||||
|
seed,
|
||||||
|
secretKey: secretKey,
|
||||||
|
nonce: nonce,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine nonce and ciphertext for storage
|
||||||
|
return Uint8List.fromList([...nonce, ...encrypted.cipherText]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the seed using the symmetric key
|
||||||
|
Future<Uint8List> _decryptSeed() async {
|
||||||
|
String? encryptedSeedBase64 = await _secureStorage.read(key: _encryptedSeedKey);
|
||||||
|
if (encryptedSeedBase64 == null) {
|
||||||
|
throw Exception('No seed found');
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List encryptedSeed = base64Decode(encryptedSeedBase64);
|
||||||
|
|
||||||
|
// Split nonce and ciphertext
|
||||||
|
final nonce = encryptedSeed.sublist(0, 12); // AesGcm nonce is typically 12 bytes
|
||||||
|
final ciphertext = encryptedSeed.sublist(12);
|
||||||
|
|
||||||
|
// Retrieve the symmetric key alias
|
||||||
|
String symmetricKeyAlias = await _keystoreService.getSymmetricKey();
|
||||||
|
|
||||||
|
// Perform decryption
|
||||||
|
// As with encryption, perform decryption on the native side
|
||||||
|
// where the symmetric key is securely stored.
|
||||||
|
|
||||||
|
// Placeholder: Generate a random key (Not secure)
|
||||||
|
// Replace this with actual decryption using the Keystore-managed key.
|
||||||
|
final algorithm = AesGcm.with256bits();
|
||||||
|
final secretKey = await algorithm.newSecretKey();
|
||||||
|
final decrypted = await algorithm.decrypt(
|
||||||
|
SecretBox(ciphertext, nonce: nonce, mac: Mac.empty),
|
||||||
|
secretKey: secretKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
return decrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore key pair from recovery phrase
|
||||||
|
Future<EcdsaKeyPair> restoreKeyPair(String mnemonic) async {
|
||||||
|
if (!bip39.validateMnemonic(mnemonic)) {
|
||||||
|
throw Exception('Invalid mnemonic');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive seed from mnemonic
|
||||||
|
Uint8List seed = bip39.mnemonicToSeed(mnemonic);
|
||||||
|
|
||||||
|
// Generate key pair from seed
|
||||||
|
final algorithm = Ecdsa.p256(Sha256());
|
||||||
|
final keyPair = await algorithm.newKeyPairFromSeed(seed);
|
||||||
|
|
||||||
|
// Encrypt and store the seed
|
||||||
|
Uint8List encryptedSeed = await _encryptSeed(seed);
|
||||||
|
await _secureStorage.write(
|
||||||
|
key: _encryptedSeedKey,
|
||||||
|
value: base64Encode(encryptedSeed),
|
||||||
|
);
|
||||||
|
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve public key
|
||||||
|
Future<String?> getPublicKey() async {
|
||||||
|
// Implement a method to retrieve the public key from stored key pair
|
||||||
|
// This requires storing the public key during key generation
|
||||||
|
// For simplicity, assuming it's stored separately
|
||||||
|
// Example:
|
||||||
|
// return await _secureStorage.read(key: 'public_key');
|
||||||
|
throw UnimplementedError('Public key retrieval not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional methods like signing, verifying can be added here
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// keystore_service.dart
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class KeystoreService {
|
||||||
|
static const MethodChannel _channel = MethodChannel('com.icingDialer/keystore');
|
||||||
|
|
||||||
|
// Generate or retrieve the symmetric key from Keystore
|
||||||
|
Future<String> getSymmetricKey() async {
|
||||||
|
final String symmetricKey = await _channel.invokeMethod('getSymmetricKey');
|
||||||
|
return symmetricKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Implement key deletion or other key management methods
|
||||||
|
}
|
@ -29,7 +29,7 @@ android {
|
|||||||
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.example.mobile_number_example"
|
applicationId "com.icingDialer.mobile_number_example"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
@ -50,8 +50,11 @@ dependencies:
|
|||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
audioplayers: ^6.1.0
|
audioplayers: ^6.1.0
|
||||||
|
cryptography: ^2.0.0
|
||||||
|
convert: ^3.0.1
|
||||||
mobile_number:
|
mobile_number:
|
||||||
path: packages/mobile_number
|
path: packages/mobile_number
|
||||||
|
encrypt: ^5.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
Reference in New Issue
Block a user