package com.example.boidelov3.utils; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.Base64; /** * Classe utilitaire pour la gestion sécurisée des clés API et configuration sensible. * * SECURITY PRINCIPLES: * - Les clés API ne sont JAMAIS stockées en dur dans le code * - Utilise SharedPreferences chiffrés (doit être combiné avec AndroidX Security pour un chiffrage réel) * - Valide les clés API avant utilisation * - Fournit des méthodes pour nettoyer les données sensibles * * RECOMMANDATION: Pour une production sécurisée, utilisez AndroidX Security Library: * implementation "androidx.security:security-crypto:1.1.0-alpha06" * Et remplace les SharedPreferences par EncryptedSharedPreferences */ public class SecureConfig { private static final String TAG = "SecureConfig"; private static final String PREFS_NAME = "SecureConfig"; private static final String KEY_API_KEY = "api_key_openai"; private static final String KEY_API_KEY_OPENROUTER = "api_key_openrouter"; private static final String KEY_API_KEY_ZAI = "api_key_zai"; private final SharedPreferences sharedPreferences; private final SecureRandom secureRandom; /** * Constructeur * @param context Contexte de l'application */ public SecureConfig(Context context) { // Pour plus de sécurité, utiliser EncryptedSharedPreferences d'AndroidX Security // Pour l'instant, on utilise des SharedPreferences standards avec des avertissements this.sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); this.secureRandom = new SecureRandom(); } /** * Sauvegarde une clé API de manière sécurisée * NOTE: Pour une vraie sécurité, utilisez EncryptedSharedPreferences d'AndroidX Security * * @param provider Le fournisseur (openai, openrouter, zai) * @param apiKey La clé API à stocker * @return true si sauvegardé avec succès */ public boolean saveApiKey(String provider, String apiKey) { if (apiKey == null || apiKey.trim().isEmpty()) { Log.w(TAG, "Tentative de sauvegarder une clé API vide"); return false; } // Valider la clé avant sauvegarde if (!validateApiKeyFormat(provider, apiKey)) { Log.w(TAG, "Format de clé API invalide pour " + provider); return false; } SharedPreferences.Editor editor = sharedPreferences.edit(); String key = getPrefKeyForProvider(provider); editor.putString(key, apiKey); boolean success = editor.commit(); if (success) { Log.i(TAG, "Clé API sauvegardée pour " + provider); } else { Log.e(TAG, "Erreur lors de la sauvegarde de la clé API"); } return success; } /** * Récupère une clé API stockée * * @param provider Le fournisseur (openai, openrouter, zai) * @return La clé API ou null si non trouvée */ public String getApiKey(String provider) { if (provider == null || provider.trim().isEmpty()) { return null; } String key = getPrefKeyForProvider(provider); return sharedPreferences.getString(key, null); } /** * Supprime une clé API stockée * * @param provider Le fournisseur * @return true si supprimée avec succès */ public boolean removeApiKey(String provider) { if (provider == null || provider.trim().isEmpty()) { Log.w(TAG, "Provider null ou vide pour removeApiKey"); return false; } SharedPreferences.Editor editor = sharedPreferences.edit(); String key = getPrefKeyForProvider(provider); editor.remove(key); boolean success = editor.commit(); if (success) { Log.i(TAG, "Clé API supprimée pour " + provider); } return success; } /** * Supprime toutes les clés API stockées * Utiliser cette méthode lors de la déconnexion ou pour nettoyer les données */ public void clearAllApiKeys() { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.remove(KEY_API_KEY); editor.remove(KEY_API_KEY_OPENROUTER); editor.remove(KEY_API_KEY_ZAI); editor.apply(); Log.i(TAG, "Toutes les clés API ont été supprimées"); } /** * Vérifie si une clé API existe pour un provider * * @param provider Le fournisseur * @return true si une clé existe */ public boolean hasApiKey(String provider) { if (provider == null || provider.trim().isEmpty()) { return false; } String key = getPrefKeyForProvider(provider); return sharedPreferences.contains(key) && sharedPreferences.getString(key, null) != null; } /** * Valide le format d'une clé API selon le provider * * @param provider Le fournisseur (openai, openrouter, zai) * @param apiKey La clé API à valider * @return true si le format est valide */ public boolean validateApiKeyFormat(String provider, String apiKey) { if (apiKey == null || apiKey.trim().isEmpty()) { return false; } if (provider == null || provider.trim().isEmpty()) { Log.w(TAG, "Provider null ou vide"); return false; } String trimmedKey = apiKey.trim(); switch (provider.toLowerCase().trim()) { case "openai": // Les clés OpenAI commencent par "sk-" return trimmedKey.startsWith("sk-") && trimmedKey.length() >= 20; case "openrouter": // Les clés OpenRouter commencent par "sk-or-" return trimmedKey.startsWith("sk-or-") && trimmedKey.length() >= 20; case "zai": // Les clés Z.ai/Anthropic commencent par "sk-ant-" return trimmedKey.startsWith("sk-ant-") && trimmedKey.length() >= 20; default: Log.w(TAG, "Provider inconnu: " + provider); return false; } } /** * Génère un hash sécurisé d'une clé pour vérification (stockage local) * Ne stocke JAMAIS la clé en clair dans les logs * * @param apiKey La clé API * @return Le hash de la clé */ public String hashApiKey(String apiKey) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(apiKey.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(hash); } catch (Exception e) { Log.e(TAG, "Erreur lors du hash de la clé", e); return null; } } /** * Génère une chaîne aléatoire sécurisée pour utilisation comme nonce ou token * * @param length Longueur de la chaîne * @return Chaîne aléatoire sécurisée */ public String generateSecureToken(int length) { byte[] token = new byte[length]; secureRandom.nextBytes(token); return Base64.getEncoder().encodeToString(token); } /** * Retourne la clé SharedPreferences appropriée selon le provider */ private String getPrefKeyForProvider(String provider) { if (provider == null) { return KEY_API_KEY; // Default to OpenAI key } switch (provider.toLowerCase().trim()) { case "openrouter": return KEY_API_KEY_OPENROUTER; case "zai": return KEY_API_KEY_ZAI; case "openai": default: return KEY_API_KEY; } } /** * Vérifie si des clés API sont stockées (pour vérifier la configuration) * * @return true si au moins une clé API est configurée */ public boolean isAnyApiKeyConfigured() { return hasApiKey("openai") || hasApiKey("openrouter") || hasApiKey("zai"); } }