import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:pointycastle/export.dart'; import 'package:pointycastle/asymmetric/api.dart'; import 'package:asn1lib/asn1lib.dart'; /// Service for handling asymmetric cryptography operations class AsymmetricCryptoService { static const String _privateKeyTag = 'private_key'; static const String _publicKeyTag = 'public_key'; final FlutterSecureStorage _secureStorage = const FlutterSecureStorage(); AsymmetricKeyPair? _currentKeyPair; /// Initialize with the default key pair, creating one if it doesn't exist Future initializeDefaultKeyPair() async { try { // Try to load existing keys final privateKeyStr = await _secureStorage.read(key: _privateKeyTag); final publicKeyStr = await _secureStorage.read(key: _publicKeyTag); if (privateKeyStr != null && publicKeyStr != null) { try { // Parse existing keys final privateKey = _parsePrivateKeyFromPem(privateKeyStr); final publicKey = _parsePublicKeyFromPem(publicKeyStr); _currentKeyPair = AsymmetricKeyPair(publicKey, privateKey); debugPrint('Loaded existing key pair successfully'); return; } catch (e) { debugPrint('Error parsing stored keys: $e'); // Continue to generate new keys } } // Generate new key pair await generateAndStoreKeyPair(); } catch (e) { debugPrint('Error initializing key pair: $e'); // Instead of rethrowing, we'll continue without encryption capability // This ensures the app doesn't crash during startup } } /// Generate a new key pair and store it securely Future generateAndStoreKeyPair() async { try { debugPrint('Generating new RSA key pair...'); // Generate key pair with a simpler approach final keyPair = await _generateRSAKeyPairSimple(1024); // Smaller keys for faster generation _currentKeyPair = keyPair; // Export to PEM format final publicKeyPem = _encodePublicKeyToPem(keyPair.publicKey); final privateKeyPem = _encodePrivateKeyToPem(keyPair.privateKey); // Store in secure storage await _secureStorage.write(key: _publicKeyTag, value: publicKeyPem); await _secureStorage.write(key: _privateKeyTag, value: privateKeyPem); debugPrint('New key pair generated and stored successfully'); } catch (e) { debugPrint('Failed to generate key pair: $e'); // Don't throw, allow the app to continue without encryption } } /// Encrypt data with the public key Uint8List? encrypt(String plainText) { if (_currentKeyPair == null) { debugPrint('No key pair available for encryption'); return null; } try { final cipher = PKCS1Encoding(RSAEngine()) ..init(true, PublicKeyParameter(_currentKeyPair!.publicKey)); final input = Uint8List.fromList(utf8.encode(plainText)); return cipher.process(input); } catch (e) { debugPrint('Encryption error: $e'); return null; } } /// Decrypt data with the private key String? decrypt(Uint8List encryptedData) { if (_currentKeyPair == null) { debugPrint('No key pair available for decryption'); return null; } try { final cipher = PKCS1Encoding(RSAEngine()) ..init(false, PrivateKeyParameter(_currentKeyPair!.privateKey)); final decrypted = cipher.process(encryptedData); return utf8.decode(decrypted); } catch (e) { debugPrint('Decryption error: $e'); return null; } } /// Get the current public key in PEM format Future getPublicKeyPem() async { return await _secureStorage.read(key: _publicKeyTag); } // Simpler RSA key pair generation that doesn't use FortunaRandom Future> _generateRSAKeyPairSimple(int bitLength) async { // Use a simple secure random instead of FortunaRandom final secureRandom = _SecureRandom(); // Create RSA key generator final keyGen = RSAKeyGenerator() ..init(ParametersWithRandom( RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64), secureRandom, )); // Generate and return the key pair return keyGen.generateKeyPair() as AsymmetricKeyPair; } // Original method (but no longer used directly) static AsymmetricKeyPair _generateRSAKeyPair(int bitLength) { try { final secureRandom = _SecureRandom(); final keyGen = RSAKeyGenerator() ..init(ParametersWithRandom( RSAKeyGeneratorParameters(BigInt.parse('65537'), bitLength, 64), secureRandom, )); return keyGen.generateKeyPair() as AsymmetricKeyPair; } catch (e) { debugPrint('Error in _generateRSAKeyPair: $e'); rethrow; } } String _encodePublicKeyToPem(RSAPublicKey publicKey) { final asn1Sequence = ASN1Sequence(); asn1Sequence.add(ASN1Integer(publicKey.modulus!)); asn1Sequence.add(ASN1Integer(publicKey.exponent!)); final base64 = base64Encode(asn1Sequence.encodedBytes); return '-----BEGIN PUBLIC KEY-----\n$base64\n-----END PUBLIC KEY-----'; } String _encodePrivateKeyToPem(RSAPrivateKey privateKey) { final asn1Sequence = ASN1Sequence(); asn1Sequence.add(ASN1Integer(BigInt.from(0))); // version asn1Sequence.add(ASN1Integer(privateKey.modulus!)); asn1Sequence.add(ASN1Integer(privateKey.publicExponent!)); asn1Sequence.add(ASN1Integer(privateKey.privateExponent!)); asn1Sequence.add(ASN1Integer(privateKey.p!)); asn1Sequence.add(ASN1Integer(privateKey.q!)); // d mod (p-1) asn1Sequence.add(ASN1Integer(privateKey.privateExponent! % (privateKey.p! - BigInt.from(1)))); // d mod (q-1) asn1Sequence.add(ASN1Integer(privateKey.privateExponent! % (privateKey.q! - BigInt.from(1)))); // q^-1 mod p asn1Sequence.add(ASN1Integer(_modInverse(privateKey.q!, privateKey.p!))); final base64 = base64Encode(asn1Sequence.encodedBytes); return '-----BEGIN RSA PRIVATE KEY-----\n$base64\n-----END RSA PRIVATE KEY-----'; } RSAPrivateKey _parsePrivateKeyFromPem(String pemString) { final pemContent = pemString .replaceAll('-----BEGIN RSA PRIVATE KEY-----', '') .replaceAll('-----END RSA PRIVATE KEY-----', '') .replaceAll('\n', ''); final asn1Parser = ASN1Parser(base64Decode(pemContent)); final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; // Parse sequence values final values = topLevelSeq.elements!.map((obj) => (obj as ASN1Integer).valueAsBigInteger).toList(); // Create RSA private key from components return RSAPrivateKey( values[1], // modulus values[3], // privateExponent values[4], // p values[5], // q ); } RSAPublicKey _parsePublicKeyFromPem(String pemString) { final pemContent = pemString .replaceAll('-----BEGIN PUBLIC KEY-----', '') .replaceAll('-----END PUBLIC KEY-----', '') .replaceAll('\n', ''); final asn1Parser = ASN1Parser(base64Decode(pemContent)); final topLevelSeq = asn1Parser.nextObject() as ASN1Sequence; // Extract modulus and exponent final modulus = (topLevelSeq.elements![0] as ASN1Integer).valueAsBigInteger; final exponent = (topLevelSeq.elements![1] as ASN1Integer).valueAsBigInteger; return RSAPublicKey(modulus, exponent); } // Modular multiplicative inverse BigInt _modInverse(BigInt a, BigInt m) { // Extended Euclidean Algorithm to find modular inverse BigInt t = BigInt.zero, newT = BigInt.one; BigInt r = m, newR = a; while (newR != BigInt.zero) { final quotient = r ~/ newR; final tempT = t; t = newT; newT = tempT - quotient * newT; final tempR = r; r = newR; newR = tempR - quotient * newR; } if (r > BigInt.one) throw Exception('$a is not invertible modulo $m'); if (t < BigInt.zero) t += m; return t; } } // Simple secure random implementation that doesn't use AESEngine class _SecureRandom implements SecureRandom { final Random _random = Random.secure(); @override String get algorithmName => 'Dart_SecureRandom'; @override void seed(CipherParameters params) { // No additional seeding required as Random.secure() is already seeded } @override BigInt nextBigInteger(int bitLength) { final fullBytes = bitLength ~/ 8; final remainingBits = bitLength % 8; final bytes = Uint8List(fullBytes + (remainingBits > 0 ? 1 : 0)); for (var i = 0; i < bytes.length; i++) { bytes[i] = nextUint8(); } // Adjust the last byte to match the remaining bits if (remainingBits > 0) { bytes[bytes.length - 1] &= (1 << remainingBits) - 1; } return BigInt.parse(bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''), radix: 16); } @override int nextUint16() => (_random.nextInt(1 << 16) & 0xFFFF); @override int nextUint32() => (_random.nextInt(1 << 32) & 0xFFFFFFFF); @override int nextUint8() => (_random.nextInt(1 << 8) & 0xFF); @override void reset() { // No reset needed for Random.secure() } @override @override Uint8List nextBytes(int count) { final bytes = Uint8List(count); for (var i = 0; i < count; i++) { bytes[i] = nextUint8(); } return bytes; } }