using System; using System.Text; using System.Runtime.InteropServices; namespace AALabs.Utilities { /// /// An interface to the protected storage service in Windows. You can use this /// to store passwords, keys, codes, etc, in at least a slightly more secure /// fashion than the registry or disk. /// /// http://msdn.microsoft.com/architecture/application/default.aspx?pull=/library/en-us/dnnetsec/html/SecNetHT07.asp /// public class DataProtection { #region DLL Imports [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, ref 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); */ #endregion #region DLL Structs [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; #endregion /// /// Which data store to use /// public enum Store { /// /// Machine store does not include user info, but can't be used on another machine easily. /// USE_MACHINE_STORE = 1, /// /// Both machine and user specific data are included. /// USE_USER_STORE }; private Store store; /// /// Construct a data protector that stores data in the specified protected store. /// /// Machine specific store or user+machine specific store. public DataProtection(Store tStore) { store = tStore; } /// /// Encrypt a plaintext value given a salt. A machine specific (and /// possibly user specific if the user store is selected) key is used /// for the encryption. /// /// ASCII string /// ASCII entropy /// Base64 encoded string public string Encrypt(string plainText, string entropy) { byte[] arr = Encrypt(Encoding.ASCII.GetBytes(plainText), entropy != null ? Encoding.ASCII.GetBytes(entropy) : null); return Convert.ToBase64String(arr); } /// /// Encrypt a plaintext value given a salt. A machine specific (and /// possibly user specific if the user store is selected) key is used /// for the encryption. /// /// Original plain text /// Optional salt /// byte array of ciphertext 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("DP.PlaintextError"); } plainTextBlob.cbData = bytesSize; Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize); } catch(Exception ex) { throw new ApplicationException("DP.MarshalError",ex); } 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, "WAFData", ref entropyBlob, IntPtr.Zero, ref prompt, dwFlags, ref cipherTextBlob); if(false == retVal) { throw new Exception("Encryption failed. " + GetErrorMessage(Marshal.GetLastWin32Error())); } //Free the blob and entropy. /* if(IntPtr.Zero != cipherTextBlob.pbData) { Marshal.FreeHGlobal(cipherTextBlob.pbData); } if(IntPtr.Zero != entropyBlob.pbData) { Marshal.FreeHGlobal(entropyBlob.pbData); } */ } 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; } /// /// Decrypt a ciphertext value given a salt. A machine specific (and /// possibly user specific if the user store is selected) key was used /// for the encryption. /// /// Base64 encoding string /// Optional salt public string Decrypt(string cipherText, string optionalEntropy) { byte[] arr = Decrypt(Convert.FromBase64String(cipherText), optionalEntropy != null ? Encoding.ASCII.GetBytes(optionalEntropy) : null); return Encoding.ASCII.GetString(arr); } /// /// Decrypt a ciphertext value given a salt. A machine specific (and /// possibly user specific if the user store is selected) key was used /// for the encryption. /// /// Byte array encrypted with Encrypt /// Optional salt 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; } string desc = String.Empty; retVal = CryptUnprotectData(ref cipherBlob, ref desc, 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; } private void InitPromptstruct(ref CRYPTPROTECT_PROMPTSTRUCT ps) { ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT)); ps.dwPromptFlags = 0; ps.hwndApp = NullPtr; ps.szPrompt = null; } private static string GetErrorMessage(int errorCode) { return String.Format("DP.ErrorCode", errorCode); } } }