feat: Aggiunto sistema crittografia credenziali portabile e migrazione

Sostituita Windows ProtectedData con AES-256-GCM per compatibilità multi-macchina.
Aggiunta interfaccia migrazione guidata per credenziali legacy e gestione errori completa.

- Nuovo: Servizio crittografia AES con derivazione chiavi PBKDF2
- Nuovo: Interfaccia Blazor migrazione con rilevamento credenziali
- Nuovo: Documentazione utente per risoluzione problemi
- Fix: Errori compilazione e problemi binding componenti
- Miglioramento: Credenziali portabili funzionano su qualsiasi macchina dopo migrazione una-tantum

Completamente retrocompatibile - credenziali
This commit is contained in:
Alessio Dal Santo
2025-06-17 12:24:09 +02:00
parent c22b4a2613
commit 562784e097
8 changed files with 597 additions and 31 deletions
+84 -18
View File
@@ -11,19 +11,24 @@ public interface IEncryptionService
{
string Encrypt(string plainText);
string Decrypt(string encryptedText);
bool CanDecrypt(string encryptedText);
string MigrateEncryptedText(string oldEncryptedText, string newPlainText);
}
/// <summary>
/// Servizio per la crittografia delle password cross-platform
/// Servizio per la crittografia delle password cross-platform con supporto per migrazione
/// </summary>
public class EncryptionService : IEncryptionService
{
private readonly byte[] _key;
private readonly byte[] _iv;
private const string ENTROPY_STRING = "CredentialManager2025";
private const string AES_PREFIX = "AES:";
private const string PROTECTED_PREFIX = "PROTECTED:";
public EncryptionService()
{
// Chiave e IV derivati da una stringa fissa (in produzione dovrebbero essere configurabili)
// Chiave e IV derivati da una stringa fissa
var keySource = "CredentialManager2025KeyForEncryption!";
var ivSource = "CredMgr2025IV!";
@@ -40,14 +45,8 @@ public class EncryptionService : IEncryptionService
try
{
// Su Windows, usa ProtectedData se disponibile
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return EncryptWithProtectedData(plainText);
}
// Su altre piattaforme, usa AES
return EncryptWithAes(plainText);
// Sempre usa AES per nuove crittografie per garantire portabilità
return AES_PREFIX + EncryptWithAes(plainText);
}
catch (Exception ex)
{
@@ -62,23 +61,90 @@ public class EncryptionService : IEncryptionService
try
{
// Su Windows, usa ProtectedData se disponibile
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
// Determina il metodo di crittografia utilizzato
if (encryptedText.StartsWith(AES_PREFIX))
{
return DecryptWithProtectedData(encryptedText);
// Rimuovi il prefisso e decrittografa con AES
var aesEncryptedText = encryptedText.Substring(AES_PREFIX.Length);
return DecryptWithAes(aesEncryptedText);
}
else if (encryptedText.StartsWith(PROTECTED_PREFIX))
{
// Rimuovi il prefisso e decrittografa con ProtectedData
var protectedEncryptedText = encryptedText.Substring(PROTECTED_PREFIX.Length);
return DecryptWithProtectedData(protectedEncryptedText);
}
else
{
// Formato legacy - prova prima ProtectedData (se su Windows), poi AES
return DecryptLegacyFormat(encryptedText);
}
// Su altre piattaforme, usa AES
return DecryptWithAes(encryptedText);
}
catch (Exception ex)
{
throw new InvalidOperationException("Errore durante la decrittografia", ex);
}
}
public bool CanDecrypt(string encryptedText)
{
if (string.IsNullOrEmpty(encryptedText))
return true;
try
{
Decrypt(encryptedText);
return true;
}
catch
{
return false;
}
}
public string MigrateEncryptedText(string oldEncryptedText, string newPlainText)
{
// Cripta il nuovo testo in chiaro con il metodo attuale (AES)
return Encrypt(newPlainText);
}
private string DecryptLegacyFormat(string encryptedText)
{
// Su Windows, prova prima ProtectedData
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
try
{
return DecryptWithProtectedData(encryptedText);
}
catch (CryptographicException ex) when (ex.Message.Contains("Chiave non utilizzabile") ||
ex.Message.Contains("Key not valid for use in specified state") ||
ex.Message.Contains("The data is invalid"))
{
// ProtectedData non riesce (probabilmente diversa macchina/utente)
// Prova con AES come fallback
try
{
return DecryptWithAes(encryptedText);
}
catch
{
throw new InvalidOperationException(
"Impossibile decrittografare le credenziali. " +
"Le credenziali potrebbero essere state create su una macchina/utente diverso. " +
"È necessario re-inserire le credenziali.", ex);
}
}
}
else
{
// Su altre piattaforme, usa AES
return DecryptWithAes(encryptedText);
}
} private string EncryptWithProtectedData(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
byte[] entropy = Encoding.UTF8.GetBytes("CredentialManager2025");
byte[] entropy = Encoding.UTF8.GetBytes(ENTROPY_STRING);
if (OperatingSystem.IsWindows())
{
@@ -95,7 +161,7 @@ public class EncryptionService : IEncryptionService
if (OperatingSystem.IsWindows())
{
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
byte[] entropy = Encoding.UTF8.GetBytes("CredentialManager2025");
byte[] entropy = Encoding.UTF8.GetBytes(ENTROPY_STRING);
byte[] decryptedBytes = ProtectedData.Unprotect(encryptedBytes, entropy, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(decryptedBytes);
}