Cleaning old generation
This commit is contained in:
parent
0b1499b32e
commit
a3532f4d36
@ -1,138 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:pointycastle/export.dart' as crypto;
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'widgets/key_storage.dart';
|
|
||||||
|
|
||||||
class ExportPrivateKeyPage extends StatefulWidget {
|
|
||||||
const ExportPrivateKeyPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
_ExportPrivateKeyPageState createState() => _ExportPrivateKeyPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ExportPrivateKeyPageState extends State<ExportPrivateKeyPage> {
|
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
|
||||||
|
|
||||||
Future<void> _exportPrivateKey() async {
|
|
||||||
final keyStorage = KeyStorage();
|
|
||||||
final privateKeyPem = await keyStorage.getPrivateKey();
|
|
||||||
|
|
||||||
if (privateKeyPem == null) {
|
|
||||||
// Show error message if there's no key
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('No private key found to export.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final password = _passwordController.text;
|
|
||||||
if (password.isEmpty) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Please enter a password.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final encryptedData = _encryptPrivateKey(privateKeyPem, password);
|
|
||||||
|
|
||||||
final outputFile = await FilePicker.platform.saveFile(
|
|
||||||
dialogTitle: 'Save encrypted private key',
|
|
||||||
fileName: 'private_key_encrypted.aes',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (outputFile != null) {
|
|
||||||
try {
|
|
||||||
final file = File(outputFile);
|
|
||||||
await file.writeAsBytes(encryptedData);
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Key Exported'),
|
|
||||||
content: const Text('The encrypted private key has been exported successfully.'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Failed to write file: $e'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List _encryptPrivateKey(String privateKey, String password) {
|
|
||||||
// Derive a key from the password using PBKDF2
|
|
||||||
final derivator = crypto.PBKDF2KeyDerivator(
|
|
||||||
crypto.HMac(crypto.SHA256Digest(), 64),
|
|
||||||
);
|
|
||||||
|
|
||||||
final salt = Uint8List.fromList(utf8.encode('some_salt')); // In production, use a random salt and store it securely
|
|
||||||
derivator.init(crypto.Pbkdf2Parameters(salt, 1000, 32));
|
|
||||||
final key = derivator.process(Uint8List.fromList(utf8.encode(password)));
|
|
||||||
|
|
||||||
// Initialize AES-CBC cipher with PKCS7 padding
|
|
||||||
final iv = Uint8List(16); // zero IV for example, in production use random IV and store it
|
|
||||||
final params = crypto.PaddedBlockCipherParameters<crypto.ParametersWithIV<crypto.KeyParameter>, Null>(
|
|
||||||
crypto.ParametersWithIV<crypto.KeyParameter>(crypto.KeyParameter(key), iv),
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
final cipher = crypto.PaddedBlockCipher('AES/CBC/PKCS7');
|
|
||||||
cipher.init(true, params);
|
|
||||||
|
|
||||||
final input = Uint8List.fromList(utf8.encode(privateKey));
|
|
||||||
final output = cipher.process(input);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Export Private Key'),
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Enter a password to encrypt the private key:',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: _passwordController,
|
|
||||||
obscureText: true,
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'Password',
|
|
||||||
hintStyle: TextStyle(color: Colors.grey),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: _exportPrivateKey,
|
|
||||||
child: const Text('Export Encrypted Private Key'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,85 +9,6 @@ import 'widgets/key_storage.dart';
|
|||||||
class GenerateNewKeyPairPage extends StatelessWidget {
|
class GenerateNewKeyPairPage extends StatelessWidget {
|
||||||
const GenerateNewKeyPairPage({super.key});
|
const GenerateNewKeyPairPage({super.key});
|
||||||
|
|
||||||
Future<Map<String, String>> _generateKeyPair() async {
|
|
||||||
final keyParams = crypto.RSAKeyGeneratorParameters(
|
|
||||||
BigInt.parse('65537'),
|
|
||||||
2048,
|
|
||||||
64,
|
|
||||||
);
|
|
||||||
|
|
||||||
final secureRandom = crypto.FortunaRandom();
|
|
||||||
final random = Random.secure();
|
|
||||||
final seeds = List<int>.generate(32, (_) => random.nextInt(256));
|
|
||||||
secureRandom.seed(crypto.KeyParameter(Uint8List.fromList(seeds)));
|
|
||||||
|
|
||||||
final rngParams = crypto.ParametersWithRandom(keyParams, secureRandom);
|
|
||||||
final keyGenerator = crypto.RSAKeyGenerator();
|
|
||||||
keyGenerator.init(rngParams);
|
|
||||||
|
|
||||||
final pair = keyGenerator.generateKeyPair();
|
|
||||||
final publicKey = pair.publicKey as crypto.RSAPublicKey;
|
|
||||||
final privateKey = pair.privateKey as crypto.RSAPrivateKey;
|
|
||||||
|
|
||||||
final publicKeyPem = _encodePublicKeyToPemPKCS1(publicKey);
|
|
||||||
final privateKeyPem = _encodePrivateKeyToPemPKCS1(privateKey);
|
|
||||||
|
|
||||||
// Save keys securely
|
|
||||||
final keyStorage = KeyStorage();
|
|
||||||
await keyStorage.saveKeys(publicKey: publicKeyPem, privateKey: privateKeyPem);
|
|
||||||
|
|
||||||
return {'publicKey': publicKeyPem, 'privateKey': privateKeyPem};
|
|
||||||
}
|
|
||||||
|
|
||||||
String _encodePublicKeyToPemPKCS1(crypto.RSAPublicKey publicKey) {
|
|
||||||
final bytes = _encodePublicKeyToDer(publicKey);
|
|
||||||
return _formatPem(bytes, 'RSA PUBLIC KEY');
|
|
||||||
}
|
|
||||||
|
|
||||||
String _encodePrivateKeyToPemPKCS1(crypto.RSAPrivateKey privateKey) {
|
|
||||||
final bytes = _encodePrivateKeyToDer(privateKey);
|
|
||||||
return _formatPem(bytes, 'RSA PRIVATE KEY');
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List _encodePublicKeyToDer(crypto.RSAPublicKey publicKey) {
|
|
||||||
final algorithmSeq = ASN1Sequence();
|
|
||||||
// Create the OID directly with the arcs
|
|
||||||
algorithmSeq.add(ASN1ObjectIdentifier([1, 2, 840, 113549, 1, 1, 1]));
|
|
||||||
algorithmSeq.add(ASN1Null());
|
|
||||||
|
|
||||||
final publicKeySeq = ASN1Sequence();
|
|
||||||
publicKeySeq.add(ASN1Integer(publicKey.modulus!));
|
|
||||||
publicKeySeq.add(ASN1Integer(publicKey.exponent!));
|
|
||||||
|
|
||||||
final publicKeyBitString = ASN1BitString(Uint8List.fromList(publicKeySeq.encodedBytes));
|
|
||||||
|
|
||||||
final topLevelSeq = ASN1Sequence();
|
|
||||||
topLevelSeq.add(algorithmSeq);
|
|
||||||
topLevelSeq.add(publicKeyBitString);
|
|
||||||
|
|
||||||
return Uint8List.fromList(topLevelSeq.encodedBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List _encodePrivateKeyToDer(crypto.RSAPrivateKey privateKey) {
|
|
||||||
final privateKeySeq = ASN1Sequence();
|
|
||||||
privateKeySeq.add(ASN1Integer(BigInt.from(0))); // Version
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.n!));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.exponent!));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.d!));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.p!));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.q!));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.d! % (privateKey.p! - BigInt.one)));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.d! % (privateKey.q! - BigInt.one)));
|
|
||||||
privateKeySeq.add(ASN1Integer(privateKey.q!.modInverse(privateKey.p!)));
|
|
||||||
|
|
||||||
return Uint8List.fromList(privateKeySeq.encodedBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatPem(Uint8List bytes, String label) {
|
|
||||||
final base64Data = base64Encode(bytes);
|
|
||||||
final chunks = RegExp('.{1,64}').allMatches(base64Data).map((m) => m.group(0)!);
|
|
||||||
return '-----BEGIN $label-----\n${chunks.join('\n')}\n-----END $label-----';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'show_public_key_qr.dart';
|
import 'show_public_key_qr.dart';
|
||||||
import 'show_public_key_text.dart';
|
import 'show_public_key_text.dart';
|
||||||
import 'generate_new_key_pair.dart';
|
import 'generate_new_key_pair.dart';
|
||||||
import 'export_private_key.dart';
|
|
||||||
import 'delete_key_pair.dart';
|
import 'delete_key_pair.dart';
|
||||||
|
|
||||||
class KeyManagementPage extends StatelessWidget {
|
class KeyManagementPage extends StatelessWidget {
|
||||||
@ -28,12 +27,6 @@ class KeyManagementPage extends StatelessWidget {
|
|||||||
MaterialPageRoute(builder: (context) => const GenerateNewKeyPairPage()),
|
MaterialPageRoute(builder: (context) => const GenerateNewKeyPairPage()),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'Export private key to password-encrypted file (AES 256)':
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => const ExportPrivateKeyPage()),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'Delete a key pair':
|
case 'Delete a key pair':
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
// 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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user