DevTrain Startseite Advanced Developers Conference vom 14.-15. Februar 2011  
  
  
SUCHEN:  
ARTIKEL ONLINE: 525   

Kategorien
.NET
Datenbanken
Web
XML

Allgemein
Camp
Foren
Events
Persönliche Einstellungen
Registrieren
Prämien Shop
Kontakt
Impressum
Über DevTrain

Autoren


   Autor: Tobi Ulm Artikel Drucken
        
Verschlüsselung mit der DPAPI unter Windows XP und Windows 2003

Die DataProtecteion API ist ein Verschlüsselungs- Programmier Interface das Microsoft momentan mit Windows Server 2003 und Windows XP ausliefert. Diese API ist eine mächtige Klasse die fest im Betriebssystem verankert ist und ein Benutzerpassword verwendet um Benutzer- bzw. Computerspezifische Verschlüsselungen durchzuführen.

Die DPAPI kann benutzt werden um sensitive Daten wie Datenbankverbindungszeichenfolgen (Connection Strings) oder Benutzerkontoinformationen (Credentials) schützen. Die Api ist vollständig basiert auf Passwörtern und kann die verschlüsselten Daten entweder in einem Benutzerspezifischen (User Store) oder maschinenweiten Speicher (machine store) ablegen.
Wo Sie die Geheimnisse ablegen kann über setzen des Feldes dwFlags  auf CRYPTPROTECT_LOCAL_MACINE  erzwungen werden. Die .net DpapiLib nutzt die Features durch Übergabe eines Enums (USE_MACHINE_STORE,USE_USER_STORE)

Es gibt zwei Möglichkeiten wie die DPAPI verwendet werden kann:
Der User Store
Der User Store beschränkt den Zugriff auf die "secrets" pro Benutzer. Nur der Benutzer der die Daten verschlüsselt hat kann die Daten auch wieder entschlüsseln. Im Bereich ASP.NET muss dabei beachtet werden dass zusätzliche User Profil Aktionen stattfinden müssen, da ASP.NET nicht automatisch User Profile lädt und entlädt.

Der Machine Store
Der Machine Store ist einfacher zu entwickeln als die User Store Variante da jeder Benutzer der Maschine auf die geheimen Daten zugreifen kann. Es sollte daher ein weiterer "entropy" Parameter verwendet werden der natürlich mit einem sicheren Key Management Verfahren abgesichert werden muss. Entropy Parameter = Zufallswert.
Wichtig ist dabei das selbst wenn ein "Hacker/Cracker" die verschlüsselte Zeichenfolge in Besitz bekommt, kann er diese nicht auf seinem Rechner entschlüsseln kann, da die Ver- bzw. Entschlüsselungsvorgänge pro Maschine mit einem eigenem Key der nur auf der Maschine enthalten ist, durchgeführt werden können.

Die DPAPI .NET Lib

Die DPAPI .NET Library ist eine Library welche die Aufrufe zu den  beiden DPAPI Funktionen kapselt. Sie sollten sicherstellen das die Library auch Unmanaged Code ausführen darf, indem Sie der AssemblyInfo.* Datei das Attribute SecurityPermission hinzufügen welches Sie im System.Security.Permissions Namespace finden.


AssemblyInfo.cs

...
using System.Security;
using System.Security.Permissions;
[assembly:SecurityPermission(SecurityAction.RequestMinimum ,UnmanagedCode=true)]
...

DataProtection.cs


using System;
using System.Text;
using System.Runtime.InteropServices;

namespace dpapiLibrary{
  /// <summary>
  /// this class uses the CryptProtectData and
  /// CryptUnprotectData of the DPAPI under Win2k3
  /// and Win XP [crypt32.dll]
  /// </summary>
  public class DataProtection{
   
   #region "API Declarations"
   [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);

    [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;

    public enum Store {USE_MACHINE_STORE = 1, USE_USER_STORE};
   #endregion
 
   #region "datamember"

    private Store store;
   #endregion

   #region "ctor"
    public DataProtection(Store tempStore)
    {
      store = tempStore;
    }
   #endregion

   #region "Operations"
    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 ApplicationException("Unable to allocate plaintext buffer.");
          }
          plainTextBlob.cbData = bytesSize;
          Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize);
        }
        catch(Exception ex)
        {
          throw new ApplicationException("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 ApplicationException("Unable to allocate entropy data buffer.");
            }
            Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
            entropyBlob.cbData = bytesSize;
          }
          catch(Exception ex)
          {
            throw new ApplicationException("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 ApplicationException("Encryption failed. " +
            GetErrorMessage(Marshal.GetLastWin32Error()));
        }
      }
      catch(Exception ex)
      {
        throw new ApplicationException("Exception encrypting. " + ex.Message);
      }
      byte[] cipherText = new byte[cipherTextBlob.cbData];
      Marshal.Copy(cipherTextBlob.pbData, cipherText, 0, cipherTextBlob.cbData);
      return cipherText;
    }

    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 ApplicationException("Unable to allocate cipherText buffer.");
          }
          cipherBlob.cbData = cipherTextSize;
          Marshal.Copy(cipherText, 0, cipherBlob.pbData, cipherBlob.cbData);
        }
        catch(Exception ex)
        {
          throw new ApplicationException("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 ApplicationException("Unable to allocate entropy buffer.");
            }
            entropyBlob.cbData = bytesSize;
            Marshal.Copy(optionalEntropy, 0, entropyBlob.pbData, bytesSize);
          }
          catch(Exception ex)
          {
            throw new ApplicationException("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 ApplicationException("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 ApplicationException("Exception decrypting. " + ex.Message);
      }
      byte[] plainText = new byte[plainTextBlob.cbData];
      Marshal.Copy(plainTextBlob.pbData, plainText, 0, plainTextBlob.cbData);
      return plainText;
    }

    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 ApplicationException("Failed to format message for error code " +
          errorCode + ". ");
      }
      return lpMsgBuf;
    }
   #endregion
  }
}

Kompilieren Sie die Lib.

Erzeugen eines verschlüsselten Connection String Eintrags in der Web.Config einer ASP.NET Anwendung:


1. Selektieren Sie im Designer die xxxConnection und wählen Sie im Eigenschaftengrid Dynamische Properties und fügen Sie die ConnectionString Eigenschaft hinzu Dadurch wird automatisch in der Web.Config ein zugehöriger Eintrag erzeugt.
2. Erzeugen Sie eine neue Konsolen Anwendung namens EncryptString:
Setzen Sie einen Verweis auf die dpapiLib


EncryptionString.exe

using System;
using System.IO;
using dpapiLibrary;

namespace EncryptString{
    class Class1{

        [STAThread]
        static void Main(string[] args){
            //
            // Read in a string
            //
            Console.Write("Input: ");
            String input = Console.ReadLine();

            //
            // Encrypt it
            //
            DataProtection dp = new DataProtection( DataProtection.Store.USE_MACHINE_STORE );
            try
            {
                byte[] dataToEncrypt = System.Text.Encoding.ASCII.GetBytes(input);
                // Not passing optional entropy in this example
                // Could pass random value (stored by the application) for added security
                // when using DPAPI with the machine store.
                String result = Convert.ToBase64String(dp.Encrypt(dataToEncrypt,null));
                StreamWriter wtr = new StreamWriter(@"..\..\Encrypted.txt");
  wtr.Write(result);
  wtr.Close();
                Console.WriteLine("Result written to file Encrypted.txt.");
            }
            catch(Exception ex)
            {
                Console.WriteLine("Exception: " + ex.Message);
            }

            Console.Write("Hit return to exit ");
            Console.ReadLine();

        }
    }
}

}

starten Sie die Konsolenanwendung und übergeben Sie der Anwendung den ConnectionString aus der Web.Config;
Der Verschlüsselte Wert wird in eine Textdatei geschrieben. Öffnen Sie diese Textdatei und kopieren sie den Wert Anstelle des Original Connection Strings ein.
3. Ändern Sie die ASP.NET CodeBehind Seite:
Setzen Sie einen Verweis auf die dpapiLib
fügen Sie folgenden Code ein:
//Achtung ich füge diesen Code in den "Designer generierten Block" ein!

*.cs

//
 // sqlConnection1
 //
 DataProtection dp = new
    DataProtection(DataProtection.Store.USE_MACHINE_STORE);
 string appSettingValue =
   ((string)(configurationAppSettings.GetValue
            ("sqlConnection1.ConnectionString", typeof(string))));
 byte[] dataToDecrypt = Convert.FromBase64String(appSettingValue);
 string connStr = 
    Encoding.ASCII.GetString(dp.Decrypt(dataToDecrypt,null));
 this.sqlConnection1.ConnectionString = connStr;


DevTrain Camp - Schneller zum .NET 3.5 Developer
 
Verwandte Artikel      Verlinkte Dokumente
    Keine verknüpften Dokumente
    Keine Links vorhanden

  Erfasst am: 24.06.2004
  Gültig bis: 22.09.2004
4 Ratings
Bewertung: 75,0%
schlecht    sehr gut  

 
© Copyright 2007 ppedv AG