In questa paginaObiettiviIl modulo consente di: | • | Creare una libreria gestita che utilizza DPAPI per crittografare e decrittografare i dati. |
Ambito di applicazioneLe informazioni contenute in questo modulo sono valide per i seguenti prodotti e tecnologie: | • | Microsoft® Windows® XP o Windows 2000 Server con Service Pack 3 e sistemi operativi successivi | | • | Microsoft Data Protection API | | • | Microsoft .NET Framework versione 1.0 con Service Pack 2 e versioni successive | | • | Microsoft Visual C#® .NET |
Utilizzo del moduloIn questo modulo vengono descritti in dettaglio i passaggi e il codice necessari per creare una libreria gestita DPAPI utilizzando Visual C#. Per trarre il massimo vantaggio dal modulo: RiepilogoPer le applicazioni Web, spesso è necessario archiviare dati di protezione quali le stringhe di connessione al database e le credenziali degli account di servizio nei file di configurazione delle applicazioni. Per motivi di sicurezza, è consigliabile non archiviare mai questo tipo di informazioni in formato testo normale, ma crittografarli sempre prima dell'archiviazione. In questo modulo viene descritto come creare una libreria di classi gestita che incapsula le chiamate all'interfaccia DPAPI (Data Protection API) per crittografare e decrittografare i dati utilizzando l'archivio chiavi del computer o l'archivio chiavi basato sull'utente. Questa libreria può essere utilizzata da altre applicazioni gestite quali le applicazioni Web ASP.NET, i servizi Web e le applicazioni di Enterprise Services. Conoscenze necessariePrima di utilizzare questo modulo, tenere presente quanto segue: | • | NeI sistema operativo Windows 2000 e in quelli successivi è disponibile l'interfaccia DPAPI (Data Protection API) Win32® per la crittografia e la decrittografia dei dati. | | • | DPAPI fa parte dell'interfaccia API Microsoft Cryptography (Crypto API), è implementata in crypt32.dll e include due metodi, CryptProtectData e CryptUnprotectData. | | • | DPAPI è particolarmente utile perché consente di eliminare il problema della gestione delle chiavi per le applicazioni che utilizzano la crittografia. La crittografia garantisce la protezione dei dati, ma è necessario eseguire operazioni aggiuntive per garantire la protezione della chiave. Per ottenere la chiave di codifica, DPAPI utilizza la password dell'account utente associato al codice che richiama le funzioni DPAPI. Pertanto, la chiave viene gestita dal sistema operativo anziché dall'applicazione. | | • | DPAPI è in grado di funzionare con l'archivio del computer o con l'archivio dell'utente (che richiede il caricamento di un profilo utente). DPAPI utilizza per impostazione predefinita l'archivio dell'utente, ma è possibile specificare che deve essere utilizzato l'archivio del computer passando il flag CRYPTPROTECT_LOCAL_MACHINE alle funzioni DPAPI. | | • | La soluzione del profilo utente offre un livello di protezione aggiuntivo, perché limita gli utenti che possono accedere alle informazioni riservate. I dati possono essere decrittografati solo dall'utente che li ha crittografati. L'utilizzo del profilo utente richiede tuttavia interventi di sviluppo aggiuntivi se DPAPI viene eseguito da un'applicazione Web ASP.NET, perché è necessario caricare e scaricare in modo esplicito un profilo utente (operazione che non viene eseguita automaticamente da ASP.NET). | | • | La soluzione dell'archivio del computer è più semplice da sviluppare, dato che non è necessario gestire il profilo utente. A meno che non si utilizzi un parametro di entropia aggiuntivo, risulta tuttavia meno sicura perché qualsiasi utente del computer è in grado di decrittografare i dati. (Il valore di entropia è un valore casuale che rende più difficile la decrittografia delle informazioni riservate). L'utilizzo di un parametro di entropia aggiuntivo comporta il fatto che l'applicazione deve memorizzarlo in modalità protetta e quindi implica un altro problema di gestione della chiave. Nota: se si utilizza DPAPI con l'archivio del computer, la stringa crittografata è specifica di un determinato computer e pertanto è necessario generare i dati crittografati in ogni computer. Non copiare i dati crittografati nei computer di una farm o di un cluster. Se si utilizza DPAPI con l'archivio dell'utente, è possibile decrittografare i dati in qualsiasi computer mediante un profilo utente comune. |
Creazione di una libreria di classi Visual C#Questa procedura consente di creare una libreria di classi Visual C# che espone i metodi Encrypt e Decrypt, incapsulando le chiamate alle funzioni DPAPI Win32. | • | Per creare una libreria di classi Visual C# 1. | Avviare Visual Studio .NET e creare un nuovo progetto Libreria di classi Visual C# denominato DataProtection. | 2. | Utilizzare Esplora soluzioni per rinominare class1.cs come DataProtection.cs. | 3. | All'interno di DataProtection.cs rinominare class1 come DataProtector e rinominare di conseguenza in modo appropriato il costruttore predefinito. | 4. | In Esplora soluzioni fare clic con il pulsante destro del mouse su DataProtection, quindi scegliere Proprietà. | 5. | Fare clic sulla cartella Proprietà di configurazione e impostare Consenti uso di blocchi di codice unsafe su True. | 6. | Scegliere OK per chiudere la finestra di dialogo Proprietà. | 7. | Aggiungere all'inizio di DataProtection.cs le istruzioni using riportate di seguito, sotto l'istruzione using esistente. using System.Text;
using System.Runtime.InteropServices;
| 8. | Aggiungere all'inizio della classe DataProtector le istruzioni DllImport riportate di seguito per consentire la chiamata alle funzioni DPAPI Win32 e alla funzione FormatMessage attraverso P/Invoke. [DllImport("Crypt32.dll", SetLastError=true,
CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool CryptProtectData(
ref DATA_BLOB pDataIn,
String szDataDescr,
ref DATA_BLOB pOptionalEntropy,
IntPtr pvReserved,
ref CRYPTPROTECT_PROMPTSTRUCT
pPromptStruct,
int dwFlags,
ref DATA_BLOB pDataOut);
[DllImport("Crypt32.dll", SetLastError=true,
CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool CryptUnprotectData(
ref DATA_BLOB pDataIn,
String szDataDescr,
ref DATA_BLOB pOptionalEntropy,
IntPtr pvReserved,
ref CRYPTPROTECT_PROMPTSTRUCT
pPromptStruct,
int dwFlags,
ref DATA_BLOB pDataOut);
[DllImport("kernel32.dll",
CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private unsafe static extern int FormatMessage(int dwFlags,
ref IntPtr lpSource,
int dwMessageId,
int dwLanguageId,
ref String lpBuffer, int
nSize,
IntPtr *Arguments);
| 9. | Aggiungere le definizioni di struttura e le costanti riportate di seguito, utilizzate dalle funzioni DPAPI. [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
internal struct DATA_BLOB
{
public int cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
internal struct CRYPTPROTECT_PROMPTSTRUCT
{
public int cbSize;
public int dwPromptFlags;
public IntPtr hwndApp;
public String szPrompt;
}
static private IntPtr NullPtr = ((IntPtr)((int)(0)));
private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;
| 10. | Aggiungere alla classe un tipo enumerato pubblico denominato Store. Questo indica se l'interfaccia DPAPI deve essere utilizzata insieme all'archivio del computer o all'archivio utente. public enum Store {USE_MACHINE_STORE = 1, USE_USER_STORE};
| 11. | Aggiungere alla classe una variabile membro privata di tipo Store. private Store store;
| 12. | Sostituire il costruttore predefinito della classe con il costruttore riportato di seguito, che accetta un parametro Store e inserisce il valore specificato nella variabile membro privata store. public DataProtector(Store tempStore)
{
store = tempStore;
}
| 13. | Aggiungere alla classe il seguente metodo pubblico Encrypt: public byte[] Encrypt(byte[] plainText, byte[] optionalEntropy)
{
bool retVal = false;
DATA_BLOB plainTextBlob = new DATA_BLOB();
DATA_BLOB cipherTextBlob = new DATA_BLOB();
DATA_BLOB entropyBlob = new DATA_BLOB();
CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT();
InitPromptstruct(ref prompt);
int dwFlags;
try
{
try
{
int bytesSize = plainText.Length;
plainTextBlob.pbData = Marshal.AllocHGlobal(bytesSize);
if(IntPtr.Zero == plainTextBlob.pbData)
{
throw new Exception("Unable to allocate plaintext buffer.");
}
plainTextBlob.cbData = bytesSize;
Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize);
}
catch(Exception ex)
{
throw new Exception("Exception marshalling data. " + ex.Message);
}
if(Store.USE_MACHINE_STORE == store)
{//Using the machine store, should be providing entropy.
dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
//Check to see if the entropy is null
if(null == optionalEntropy)
{//Allocate something
optionalEntropy = new byte[0];
}
try
{
int bytesSize = optionalEntropy.Length;
entropyBlob.pbData = Marshal.AllocHGlobal(optionalEntropy
.Length);;
if(IntPtr.Zero == entropyBlob.pbData)
{
throw new Exception("Unable to allocate entropy data buffer.");
}
Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
entropyBlob.cbData = bytesSize;
}
catch(Exception ex)
{
throw new Exception("Exception entropy marshalling data. " +
ex.Message);
}
}
else
{//Using the user store
dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
}
retVal = CryptProtectData(ref plainTextBlob, "", ref entropyBlob,
IntPtr.Zero, ref prompt, dwFlags,
ref cipherTextBlob);
if(false == retVal)
{
throw new Exception("Encryption failed. " +
GetErrorMessage(Marshal.GetLastWin32Error()));
}
}
catch(Exception ex)
{
throw new Exception("Exception encrypting. " + ex.Message);
}
byte[] cipherText = new byte[cipherTextBlob.cbData];
Marshal.Copy(cipherTextBlob.pbData, cipherText, 0, cipherTextBlob
.cbData);
return cipherText;
}
| 14. | Aggiungere alla classe il seguente metodo pubblico Decrypt: public byte[] Decrypt(byte[] cipherText, byte[] optionalEntropy)
{
bool retVal = false;
DATA_BLOB plainTextBlob = new DATA_BLOB();
DATA_BLOB cipherBlob = new DATA_BLOB();
CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT();
InitPromptstruct(ref prompt);
try
{
try
{
int cipherTextSize = cipherText.Length;
cipherBlob.pbData = Marshal.AllocHGlobal(cipherTextSize);
if(IntPtr.Zero == cipherBlob.pbData)
{
throw new Exception("Unable to allocate cipherText buffer.");
}
cipherBlob.cbData = cipherTextSize;
Marshal.Copy(cipherText, 0, cipherBlob.pbData, cipherBlob.cbData);
}
catch(Exception ex)
{
throw new Exception("Exception marshalling data. " + ex.Message);
}
DATA_BLOB entropyBlob = new DATA_BLOB();
int dwFlags;
if(Store.USE_MACHINE_STORE == store)
{//Using the machine store, should be providing entropy.
dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
//Check to see if the entropy is null
if(null == optionalEntropy)
{//Allocate something
optionalEntropy = new byte[0];
}
try
{
int bytesSize = optionalEntropy.Length;
entropyBlob.pbData = Marshal.AllocHGlobal(bytesSize);
if(IntPtr.Zero == entropyBlob.pbData)
{
throw new Exception("Unable to allocate entropy buffer.");
}
entropyBlob.cbData = bytesSize;
Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
}
catch(Exception ex)
{
throw new Exception("Exception entropy marshalling data. " +
ex.Message);
}
}
else
{//Using the user store
dwFlags = CRYPTPROTECT_UI_FORBIDDEN;
}
retVal = CryptUnprotectData(ref cipherBlob, null, ref entropyBlob,
IntPtr.Zero, ref prompt, dwFlags,
ref plainTextBlob);
if(false == retVal)
{
throw new Exception("Decryption failed. " +
GetErrorMessage(Marshal.GetLastWin32Error()));
}
//Free the blob and entropy.
if(IntPtr.Zero != cipherBlob.pbData)
{
Marshal.FreeHGlobal(cipherBlob.pbData);
}
if(IntPtr.Zero != entropyBlob.pbData)
{
Marshal.FreeHGlobal(entropyBlob.pbData);
}
}
catch(Exception ex)
{
throw new Exception("Exception decrypting. " + ex.Message);
}
byte[] plainText = new byte[plainTextBlob.cbData];
Marshal.Copy(plainTextBlob.pbData, plainText, 0, plainTextBlob.cbData);
return plainText;
}
| 15. | Aggiungere alla classe i seguenti metodi dell'helper privati: private void InitPromptstruct(ref CRYPTPROTECT_PROMPTSTRUCT ps)
{
ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT));
ps.dwPromptFlags = 0;
ps.hwndApp = NullPtr;
ps.szPrompt = null;
}
private unsafe static String GetErrorMessage(int errorCode)
{
int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
int messageSize = 255;
String lpMsgBuf = "";
int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS;
IntPtr ptrlpSource = new IntPtr();
IntPtr prtArguments = new IntPtr();
int retVal = FormatMessage(dwFlags, ref ptrlpSource, errorCode, 0,
ref lpMsgBuf, messageSize, &
prtArguments);
if(0 == retVal)
{
throw new Exception("Failed to format message for error code " +
errorCode + ". ");
}
return lpMsgBuf;
}
| 16. | Scegliere Genera soluzione dal menu Genera. |
|
Assegnazione di un nome sicuro all'assembly (facoltativo)Se è necessario che la libreria di classi gestita DPAPI venga chiamata da un'applicazione di Enterprise Services (che deve essere dotata di nome sicuro), occorre assegnare un nome sicuro anche alla libreria. Questa procedura consente di creare un nome sicuro per la libreria di classi. Se è necessario chiamare la libreria di classi gestita DPAPI direttamente da un'applicazione Web ASP.NET alla quale non è assegnato un nome sicuro, è possibile ignorare questa procedura. | • | Per assegnare un nome sicuro all'assembly 1. | Aprire una finestra di comando e modificare la directory impostandola sulla cartella del progetto DataProtection. | 2. | Mediante l'utilità sn.exe generare una coppia di chiavi da utilizzare per firmare l'assembly. sn -k dataprotection.snk
| 3. | Tornare a Visual Studio .NET e aprire Assemblyinfo.cs. | 4. | Individuare l'attributo AssemblyKeyFile e aggiungere il percorso del file di chiave all'interno della cartella del progetto. [assembly: AssemblyKeyFile(@"..\..\dataprotection.snk")]
| 5. | Scegliere Genera soluzione dal menu Genera. |
|
Altre risorsePer ulteriori informazioni su come utilizzare la libreria creata in questo modulo, vedere i seguenti moduli correlati:
| |