/*
* 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
);
}
}