import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:uuid/uuid.dart'; import '../constants/app_constants.dart'; class FingerprintService { final FlutterSecureStorage _storage; final DeviceInfoPlugin _deviceInfo; static const MethodChannel _nativeChannel = MethodChannel('iq.urukprize.uruk_prize/device_fingerprint'); FingerprintService({ FlutterSecureStorage? storage, DeviceInfoPlugin? deviceInfo, }) : _storage = storage ?? const FlutterSecureStorage(), _deviceInfo = deviceInfo ?? DeviceInfoPlugin(); String? _cachedFingerprint; /// Retrieves or generates a tamper-proof hardware-bound device fingerprint. /// Prioritizes Native MethodChannel (Kotlin on Android / Swift on iOS with Keychain persistence). Future getDeviceFingerprint() async { if (_cachedFingerprint != null) { return _cachedFingerprint!; } // 1. Check if previously generated and stored securely String? storedFp = await _storage.read(key: AppConstants.keyFingerprint); if (storedFp != null && storedFp.length == 64) { _cachedFingerprint = storedFp; return storedFp; } // 2. Fetch or create persistent random cryptographic salt String? salt = await _storage.read(key: AppConstants.keySalt); if (salt == null) { salt = const Uuid().v4() + DateTime.now().microsecondsSinceEpoch.toString(); await _storage.write(key: AppConstants.keySalt, value: salt); } // 3. Extract hardware parameters via Native MethodChannel first String hardwareData = ''; try { final String? nativeRaw = await _nativeChannel.invokeMethod('getHardwareRawData'); if (nativeRaw != null && nativeRaw.isNotEmpty) { hardwareData = nativeRaw; } } catch (_) { // MethodChannel fallback to device_info_plus } if (hardwareData.isEmpty) { try { if (Platform.isAndroid) { final androidInfo = await _deviceInfo.androidInfo; hardwareData = '${androidInfo.brand}-${androidInfo.model}-${androidInfo.id}-${androidInfo.hardware}-${androidInfo.manufacturer}'; } else if (Platform.isIOS) { final iosInfo = await _deviceInfo.iosInfo; hardwareData = '${iosInfo.name}-${iosInfo.model}-${iosInfo.systemName}-${iosInfo.identifierForVendor}'; } else { hardwareData = 'generic-client-uruk'; } } catch (_) { hardwareData = 'fallback-hardware-id'; } } // 4. Generate SHA-256 Digest final combinedBytes = utf8.encode('$hardwareData::$salt::uruk_prize_secure'); final digest = sha256.convert(combinedBytes); final fingerprint = digest.toString(); // 5. Store permanently await _storage.write(key: AppConstants.keyFingerprint, value: fingerprint); _cachedFingerprint = fingerprint; return fingerprint; } Future getDeviceModel() async { try { final String? nativeModel = await _nativeChannel.invokeMethod('getDeviceModel'); if (nativeModel != null && nativeModel.isNotEmpty) { return nativeModel; } } catch (_) {} try { if (Platform.isAndroid) { final info = await _deviceInfo.androidInfo; return '${info.manufacturer} ${info.model}'; } else if (Platform.isIOS) { final info = await _deviceInfo.iosInfo; return info.utsname.machine; } } catch (_) {} return 'Unknown Device'; } Future saveAuthSession({ required int userId, required String token, required String deviceSecret, }) async { await _storage.write(key: AppConstants.keyUserId, value: userId.toString()); await _storage.write(key: AppConstants.keyToken, value: token); await _storage.write(key: AppConstants.keyDeviceSecret, value: deviceSecret); } Future getUserId() async { final val = await _storage.read(key: AppConstants.keyUserId); return val != null ? int.tryParse(val) : null; } Future getSessionToken() async { return await _storage.read(key: AppConstants.keyToken); } Future getDeviceSecret() async { return await _storage.read(key: AppConstants.keyDeviceSecret); } Future clearSession() async { await _storage.delete(key: AppConstants.keyUserId); await _storage.delete(key: AppConstants.keyToken); await _storage.delete(key: AppConstants.keyDeviceSecret); await _storage.delete(key: AppConstants.keyUserData); } }