/* * Copyright (c) 2006 Michael Eddington * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Authors: * Michael Eddington (meddington@phed.org) * * Date: * 11/05/2006 */ using System; using System.Text; using System.Security; using System.Collections; using System.ComponentModel; using System.Runtime.InteropServices; namespace PhedOrg.SecureStringHelper { /*** Example of usage *** class Program { static void Main(string[] args) { Guid guid = Guid.NewGuid(); string s1 = guid.ToString(); string s2 = Convert.ToString(guid); SecureString a = new SecureString(); for (int i = 0; i < 10; i++) a.AppendChar('a'); // Display our secure string Console.WriteLine(SecureStringHelper.SecureStringToString(a)); // Encrypt our secure string string enc = SecureStringHelper.EncryptSecureString(a, "happy days"); Console.WriteLine(enc); // Decrypt our encrypted secure string a = SecureStringHelper.DecryptSecureString(enc, "happy days"); Console.WriteLine(SecureStringHelper.SecureStringToString(a)); } } */ internal class SecureStringHelper { /// /// Encrypts a SecureString using AES and returns a BASE64 encoded string. /// /// /// This method uses p/invoke to call Crypto API functions. All cryptography /// occurs using unmanaged memory which is later zero'd out. /// /// SecureString to encrypt /// Encryption password /// Base64 encoded string internal static string EncryptSecureString(SecureString str, string key) { IntPtr hCryptProv = IntPtr.Zero; IntPtr hHash = IntPtr.Zero; IntPtr hKey = IntPtr.Zero; IntPtr pbBuffer = IntPtr.Zero; IntPtr hKeyString = IntPtr.Zero; string ret; byte[] buff; try { if (!CryptAcquireContext(ref hCryptProv, "SecureStringHelperContainer", IntPtr.Zero, 24, CRYPT_MACHINE_KEYSET)) { // GetLastWin32Error should return a uint. Need to turn off overflow // checking inorder to cast our overly long uints down to negative ints. unchecked { if (Marshal.GetLastWin32Error() == (int)NTE_BAD_KEYSET) { // Try and create container if (!CryptAcquireContext(ref hCryptProv, "SecureStringHelperContainer", IntPtr.Zero, 24, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) { if (Marshal.GetLastWin32Error() == (int)NTE_EXISTS) { throw new ApplicationException("EncryptSecureString failed. CryptAquireContext failed, unable to access key container. " + GetLastError()); } throw new ApplicationException("EncryptSecureString failed. CryptAcquireContext returned false. " + GetLastError()); } } else throw new ApplicationException("EncryptSecureString failed. CryptAcquireContext returned false. " + GetLastError()); } } if (!CryptCreateHash(hCryptProv, CALG_SHA1, IntPtr.Zero, 0, ref hHash)) { throw new ApplicationException("EncryptSecureString failed. CryptCreateHash returned false. " + GetLastError()); } hKeyString = Marshal.StringToHGlobalAnsi(key); if (!CryptHashData(hHash, hKeyString, (uint)key.Length, 0)) { throw new ApplicationException("EncryptSecureString failed. CryptHashData returned false. " + GetLastError()); } Marshal.ZeroFreeGlobalAllocAnsi(hKeyString); hKeyString = IntPtr.Zero; if (!CryptDeriveKey(hCryptProv, CALG_AES, hHash, 0x00800000, ref hKey)) { throw new ApplicationException("EncryptSecureString failed. CryptDeriveKey returned false. " + GetLastError()); } if (!CryptDestroyHash(hHash)) { throw new ApplicationException("EncryptSecureString failed. CryptDestroyHash returned false. " + GetLastError()); } hHash = IntPtr.Zero; uint origSize = (uint)str.Length; uint length = origSize; // Determin required buffer length if (!CryptEncryptSize(hKey, IntPtr.Zero, true, 0, IntPtr.Zero, ref length, (uint)str.Length)) { throw new ApplicationException("EncryptSecureString failed. CryptEncryptSize returned false. " + GetLastError()); } // Grow SecureString to required length while (str.Length <= length) { str.AppendChar(' '); } // Get unmanaged buffer pbBuffer = Marshal.SecureStringToGlobalAllocAnsi(str); length = origSize; // Encrypt it all up if (!CryptEncrypt(hKey, IntPtr.Zero, true, 0, pbBuffer, ref length, (uint)str.Length)) { throw new ApplicationException("EncryptSecureString failed. CryptEncrypt returned false. " + GetLastError()); } // Base64 encode the mess buff = new byte[length]; Marshal.Copy(pbBuffer, buff, 0, (int)length); ret = Convert.ToBase64String(buff); // Free the unmanaged memory Marshal.ZeroFreeGlobalAllocAnsi(pbBuffer); pbBuffer = IntPtr.Zero; if (!CryptDestroyKey(hKey)) { throw new ApplicationException("EncryptSecureString failed. CryptDestroyKey returned false. " + GetLastError()); } hKey = IntPtr.Zero; if (!CryptReleaseContext(hCryptProv, 0)) { throw new ApplicationException("EncryptSecureString failed. CryptReleaseContext returned false. " + GetLastError()); } hCryptProv = IntPtr.Zero; return ret; } finally { if (hHash != IntPtr.Zero) { CryptDestroyHash(hHash); } if (pbBuffer != IntPtr.Zero) { Marshal.ZeroFreeGlobalAllocAnsi(pbBuffer); } if (hKey != IntPtr.Zero) { CryptDestroyKey(hKey); } if (hCryptProv != IntPtr.Zero) { CryptReleaseContext(hCryptProv, 0); } if (hKeyString != IntPtr.Zero) { Marshal.ZeroFreeGlobalAllocAnsi(hKeyString); } } } /// /// Decrypts an encrypted SecureString using AES and returns it. /// /// /// This method uses p/invoke to call Crypto API functions. All cryptography /// occurs using unmanaged memory which is later zero'd out. /// /// Encrypted string returned from EncryptSecureString /// Encryption password /// Base64 encoded string internal static SecureString DecryptSecureString(string encryptedString, string key) { IntPtr hCryptProv = IntPtr.Zero; IntPtr hHash = IntPtr.Zero; IntPtr hKey = IntPtr.Zero; IntPtr pbBuffer = IntPtr.Zero; IntPtr hKeyString = IntPtr.Zero; SecureString ret = new SecureString(); byte[] buff; try { if (!CryptAcquireContext(ref hCryptProv, "SecureStringHelperContainer", IntPtr.Zero, 24, CRYPT_MACHINE_KEYSET)) { // GetLastWin32Error should return a uint. Need to turn off overflow // checking inorder to cast our overly long uints down to negative ints. unchecked { if (Marshal.GetLastWin32Error() == (int)NTE_BAD_KEYSET) { // Try and create container if (!CryptAcquireContext(ref hCryptProv, "SecureStringHelperContainer", IntPtr.Zero, 24, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) { if (Marshal.GetLastWin32Error() == (int)NTE_EXISTS) { throw new ApplicationException("DecryptSecureString failed. CryptAquireContext failed, unable to access key container. NTE_EXISTS"); } throw new ApplicationException("DecryptSecureString failed. CryptAcquireContext returned false. " + GetLastError()); } } else throw new ApplicationException("DecryptSecureString failed. CryptAcquireContext returned false. " + GetLastError()); } } if (!CryptCreateHash(hCryptProv, CALG_SHA1, IntPtr.Zero, 0, ref hHash)) { throw new ApplicationException("DecryptSecureString failed. CryptCreateHash returned false. " + GetLastError()); } hKeyString = Marshal.StringToHGlobalAnsi(key); if (!CryptHashData(hHash, hKeyString, (uint)key.Length, 0)) { throw new ApplicationException("DecryptSecureString failed. CryptHashData returned false. " + GetLastError()); } Marshal.ZeroFreeGlobalAllocAnsi(hKeyString); hKeyString = IntPtr.Zero; if (!CryptDeriveKey(hCryptProv, CALG_AES, hHash, 0x00800000, ref hKey)) { throw new ApplicationException("DecryptSecureString failed. CryptDeriveKey returned false. " + GetLastError()); } if (!CryptDestroyHash(hHash)) { throw new ApplicationException("DecryptSecureString failed. CryptDestroyHash returned false. " + GetLastError()); } hHash = IntPtr.Zero; // Base64 decode the mess buff = Convert.FromBase64String(encryptedString); pbBuffer = Marshal.AllocHGlobal(buff.Length+1); Marshal.Copy(buff, 0, pbBuffer, buff.Length); uint length = (uint)buff.Length; // Determin required buffer length if (!CryptDecrypt(hKey, IntPtr.Zero, true, 0, pbBuffer, ref length)) { throw new ApplicationException("DecryptSecureString failed. CryptDecrypt returned false. " + GetLastError()); } // Convert to SecureString somehow buff = new byte[length]; Marshal.Copy(pbBuffer, buff, 0, (int)length); // Free the unmanaged memory Marshal.ZeroFreeGlobalAllocAnsi(pbBuffer); pbBuffer = IntPtr.Zero; // Create the SecureString for (int cnt = 0; cnt < buff.Length; cnt++) { ret.AppendChar(Convert.ToChar(buff[cnt])); buff[cnt] = 0; } if (!CryptDestroyKey(hKey)) { throw new ApplicationException("DecryptSecureString failed. CryptDestroyKey returned false. " + GetLastError()); } hKey = IntPtr.Zero; if (!CryptReleaseContext(hCryptProv, 0)) { throw new ApplicationException("DecryptSecureString failed. CryptReleaseContext returned false. " + GetLastError()); } hCryptProv = IntPtr.Zero; return ret; } finally { if (hHash != IntPtr.Zero) { CryptDestroyHash(hHash); } if (pbBuffer != IntPtr.Zero) { Marshal.ZeroFreeGlobalAllocAnsi(pbBuffer); } if (hKey != IntPtr.Zero) { CryptDestroyKey(hKey); } if (hCryptProv != IntPtr.Zero) { CryptReleaseContext(hCryptProv, 0); } if (hKeyString != IntPtr.Zero) { Marshal.ZeroFreeGlobalAllocAnsi(hKeyString); } } } /// /// Convert a SecureString to regular String. Warning: NOT SECURE! /// /// /// THIS IS NOT SECURE! Using this method will defeat the purpose of using /// SecureString! This should only be used to interop with 3rd party managed /// API's that do not support SecureString yet. A bug should be reported to /// the vendor so the API can be updated. /// /// SecureString to convert /// Returns a regular string object. internal static string SecureStringToString(SecureString secureString) { IntPtr bstrTmp = IntPtr.Zero; string insecureString; try { bstrTmp = Marshal.SecureStringToBSTR(secureString); insecureString = Marshal.PtrToStringBSTR(bstrTmp); } catch { insecureString = ""; } finally { if (bstrTmp != IntPtr.Zero) { Marshal.ZeroFreeBSTR(bstrTmp); } } return insecureString; } private static string GetLastError() { int err = Marshal.GetLastWin32Error(); string errStr = ""; switch ((uint)err) { case ERROR_INVALID_HANDLE: errStr = "ERROR_INVALID_HANDLE"; break; case ERROR_INVALID_PARAMETER: errStr = "ERROR_INVALID_PARAMETER"; break; case ERROR_BUSY: errStr = "ERROR_BUSY"; break; case ERROR_FILE_NOT_FOUND: errStr = "ERROR_FILE_NOT_FOUND"; break; case ERROR_NOT_ENOUGH_MEMORY: errStr = "ERROR_NOT_ENOUGH_MEMORY"; break; case NTE_BAD_ALGID: errStr = "Bad Algid"; break; case NTE_BAD_FLAGS: errStr = "NTE_BAD_FLAGS"; break; case NTE_BAD_HASH: errStr = "NTE_BAD_HASH"; break; case NTE_BAD_HASH_STATE: errStr = "NTE_BAD_HASH_STATE"; break; case NTE_BAD_UID: errStr = "NTE_BAD_UID"; break; case NTE_FAIL: errStr = "NTE_FAIL"; break; case NTE_SILENT_CONTEXT: errStr = "NTE_SILENT_CONTEXT"; break; case NTE_BAD_DATA: errStr = "NTE_BAD_DATA"; break; case NTE_BAD_KEY: errStr = "NTE_BAD_KEY"; break; case NTE_BAD_LEN: errStr = "NTE_BAD_LEN"; break; case NTE_DOUBLE_ENCRYPT: errStr = "NTE_DOUBLE_ENCRYPT"; break; case NTE_NO_MEMORY: errStr = "NTE_NO_MEMORY"; break; case ERROR_MORE_DATA: errStr = "ERROR_MORE_DATA"; break; case NTE_BAD_KEY_STATE: errStr = "NTE_BAD_KEY_STATE"; break; case NTE_BAD_KEYSET: errStr = "NTE_BAD_KEYSET"; break; case NTE_BAD_KEYSET_PARAM: errStr = "NTE_BAD_KEYSET_PARAM"; break; case NTE_PROV_DLL_NOT_FOUND: errStr = "NTE_PROV_DLL_NOT_FOUND"; break; case NTE_PROV_TYPE_ENTRY_BAD: errStr = "NTE_PROV_TYPE_ENTRY_BAD"; break; case NTE_PROV_TYPE_NO_MATCH: errStr = "NTE_PROV_TYPE_NO_MATCH"; break; case NTE_PROV_TYPE_NOT_DEF: errStr = "NTE_PROV_TYPE_NOT_DEF"; break; case NTE_PROVIDER_DLL_FAIL: errStr = "NTE_PROVIDER_DLL_FAIL"; break; case NTE_SIGNATURE_FILE_BAD: errStr = "NTE_SIGNATURE_FILE_BAD"; break; case NTE_BAD_SIGNATURE: errStr = "NTE_BAD_SIGNATURE"; break; default: errStr = "UNKNOWN ERROR"; break; } return errStr; } const uint ALG_SID_AES_128 = 14; const uint ALG_CLASS_DATA_ENCRYPT = 3 << 13; const uint ALG_TYPE_BLOCK = 3 << 9; const uint CALG_AES = ALG_SID_AES_128 | ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK; const uint CALG_3DES = 0x00006603; const uint CALG_MD5 = 0x00008003; const uint CALG_SHA1 = 0x00008004; const uint PROV_RSA_FULL = 0x00000001; const uint CRYPT_NEWKEYSET = 8; const uint CRYPT_MACHINE_KEYSET = 32; const int ERROR_BUSY = 107; const int ERROR_FILE_NOT_FOUND = 2; const int ERROR_INVALID_HANDLE = 6; const int ERROR_INVALID_PARAMETER = 87; const int ERROR_NOT_ENOUGH_MEMORY = 8; const uint NTE_BAD_ALGID = 0x80090008; const uint NTE_BAD_FLAGS = 0x80090009; const uint NTE_BAD_HASH = 0x80090002; const uint NTE_BAD_HASH_STATE = 0x8009000C; const uint NTE_BAD_UID = 0x80090001; const uint NTE_FAIL = 0x80090020; const uint NTE_SILENT_CONTEXT = 0x80090022; const uint NTE_BAD_DATA = 0x80090005; const uint NTE_BAD_KEY = 0x80090003; const uint NTE_BAD_KEY_STATE = 0x8009000B; const uint NTE_BAD_KEYSET = 0x80090016; const uint NTE_BAD_KEYSET_PARAM = 0x8009001F; const uint NTE_BAD_LEN = 0x80090004; const uint NTE_BAD_PROV_TYPE = 0x80090014; const uint NTE_DOUBLE_ENCRYPT = 0x80090012; const uint NTE_NO_MEMORY = 0x8009000E; const uint NTE_EXISTS = 0x8009000F; const uint NTE_KEYSET_ENTRY_BAD = 0x8009001A; const uint NTE_KEYSET_NOT_DEF = 0x80090019; const uint NTE_PROV_DLL_NOT_FOUND = 0x8009001E; const uint NTE_PROV_TYPE_ENTRY_BAD = 0x80090018; const uint NTE_PROV_TYPE_NO_MATCH = 0x8009001B; const uint NTE_PROV_TYPE_NOT_DEF = 0x80090017; const uint NTE_PROVIDER_DLL_FAIL = 0x8009001D; const uint NTE_SIGNATURE_FILE_BAD = 0x8009001C; const uint NTE_BAD_SIGNATURE = 0x80090006; const uint ERROR_MORE_DATA = 234; [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool CryptAcquireContext( ref IntPtr hProv, string pszContainer, IntPtr pszProvider, uint dwProvType, uint dwFlags); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptReleaseContext( IntPtr hProv, uint dwFlags); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptEncrypt( IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, IntPtr pbData, ref uint pdwDataLen, uint dwBufLen ); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptDecrypt( IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, IntPtr pbData, ref uint pdwDataLen ); [DllImport("advapi32.dll", SetLastError = true, EntryPoint="CryptEncrypt")] private static extern bool CryptEncryptSize( IntPtr hKey, IntPtr hHash, bool Final, uint dwFlags, IntPtr pbData, ref uint pdwDataLen, uint dwBufLen ); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptCreateHash( IntPtr hProv, uint Algid, IntPtr hKey, uint dwFlags, ref IntPtr phHash ); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptHashData( IntPtr hHash, IntPtr pbData, uint dwDataLen, uint dwFlags ); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptDestroyHash( IntPtr hHash ); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptDeriveKey( IntPtr hProv, uint Algid, IntPtr hBaseData, uint dwFlags, ref IntPtr phKey ); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CryptDestroyKey( IntPtr hKey ); } }