Protecting Secret Data in Managed Code

Protecting Secret Data in Managed Code

Currently the .NET common language runtime and .NET Framework offer no service for storing secret information in a secure manner, and storing a password in plaintext in an XML file is not raising the bar very high! Part of the reason for not adding this support is the .NET philosophy of XCOPY deployment. In other words, any application can be written and then deployed using simple file-copying tools. There should be no need to register DLLs or controls or to set any settings in the registry. You copy the files, and the application is live. With that in mind, you might realize that storing secrets defeats this noble goal. You cannot store secret data without the aid of tools, because encryption uses complex algorithms and keys. However, there's no reason why, as an application developer, you cannot deploy an application after using tools to configure secret data. Or your application could use secrets but not store them. What I mean is this: your application can use and cache secret data but not persist the data, in which case XCOPY deployment is still a valid option.

If you see code like the following encryption code, file a bug and have it fixed as soon as possible. This is a great example instead of encraption :

public static char[] EncryptAndDecrypt(string data) { //SSsshh!! Don't tell anyone. string key = "yeKterceS"; char[] text = data.ToCharArray(); for (int i = 0; i < text.Length; i++) text[i] ^= key[i % key.Length]; return text; }

Today, the only way to protect secret data from managed code is to call unmanaged code, which means you can call LSA or DPAPI from a managed application.

The following sample code outlines how you can use C# to create a class that interfaces with DPAPI. Note that there's another file that goes with this file, named NativeMethods.cs, that contains platform invoke (PInvoke) definitions, data structures, and constants necessary to call DPAPI. You can find all of these files with the book's sample files in the folder Secureco2\Chapter09\DataProtection. The System.Runtime.InteropServices namespace provides a collection of classes useful for accessing COM objects and native APIs from .NET-based applications.

//DataProtection.cs namespace Microsoft.Samples.DPAPI { using System; using System.Runtime.InteropServices; using System.Text; public class DataProtection { // Protect string and return base64-encoded data. public static string ProtectData(string data, string name, int flags) { byte[] dataIn = Encoding.Unicode.GetBytes(data); byte[] dataOut = ProtectData(dataIn, name, flags); return (null != dataOut) ? Convert.ToBase64String(dataOut) : null; } // Unprotect base64-encoded data and return string. public static string UnprotectData(string data) { byte[] dataIn = Convert.FromBase64String(data); byte[] dataOut = UnprotectData(dataIn, NativeMethods.UIForbidden NativeMethods.VerifyProtection); return (null != dataOut) ? Encoding.Unicode.GetString(dataOut) : null; } //////////////////////// // Internal functions // //////////////////////// internal static byte[] ProtectData(byte[] data, string name, int dwFlags) { byte[] cipherText = null; // Copy data into unmanaged memory. NativeMethods.DATA_BLOB din = new NativeMethods.DATA_BLOB(); din.cbData = data.Length; din.pbData = Marshal.AllocHGlobal(din.cbData); Marshal.Copy(data, 0, din.pbData, din.cbData); NativeMethods.DATA_BLOB dout = new NativeMethods.DATA_BLOB(); NativeMethods.CRYPTPROTECT_PROMPTSTRUCT ps = new NativeMethods.CRYPTPROTECT_PROMPTSTRUCT(); //Fill the DPAPI prompt structure. InitPromptstruct(ref ps); try { bool ret = NativeMethods.CryptProtectData( ref din, name, NativeMethods.NullPtr, NativeMethods.NullPtr, ref ps, dwFlags, ref dout); if (ret) { cipherText = new byte[dout.cbData]; Marshal.Copy(dout.pbData, cipherText, 0, dout.cbData); NativeMethods.LocalFree(dout.pbData); } else { #if (DEBUG) Console.WriteLine("Encryption failed: " + Marshal.GetLastWin32Error().ToString()); #endif } } finally { if ( din.pbData != IntPtr.Zero ) Marshal.FreeHGlobal(din.pbData); } return cipherText; } internal static byte[] UnprotectData(byte[] data, int dwFlags) { byte[] clearText = null; //Copy data into unmanaged memory. NativeMethods.DATA_BLOB din = new NativeMethods.DATA_BLOB(); din.cbData = data.Length; din.pbData = Marshal.AllocHGlobal(din.cbData); Marshal.Copy(data, 0, din.pbData, din.cbData); NativeMethods.CRYPTPROTECT_PROMPTSTRUCT ps = new NativeMethods.CRYPTPROTECT_PROMPTSTRUCT(); InitPromptstruct(ref ps); NativeMethods.DATA_BLOB dout = new NativeMethods.DATA_BLOB(); try { bool ret = NativeMethods.CryptUnprotectData( ref din, null, NativeMethods.NullPtr, NativeMethods.NullPtr, ref ps, dwFlags, ref dout); if (ret) { clearText = new byte[ dout.cbData ] ; Marshal.Copy(dout.pbData, clearText, 0, dout.cbData); NativeMethods.LocalFree(dout.pbData); } else { #if (DEBUG) Console.WriteLine("Decryption failed: " + Marshal.GetLastWin32Error().ToString()); #endif } } finally { if ( din.pbData != IntPtr.Zero ) Marshal.FreeHGlobal(din.pbData); } return clearText; } static internal void InitPromptstruct( ref NativeMethods.CRYPTPROTECT_PROMPTSTRUCT ps) { ps.cbSize = Marshal.SizeOf( typeof(NativeMethods.CRYPTPROTECT_PROMPTSTRUCT)); ps.dwPromptFlags = 0; ps.hwndApp = NativeMethods.NullPtr; ps.szPrompt = null; } } }

The following C# driver code shows how to use the DataProtection class:

using Microsoft.Samples.DPAPI; using System; using System.Text; class TestStub { public static void Main(string[] args) { string data = "Gandalf, beware of the Balrog in Moria."; string name="MySecret"; Console.WriteLine("String is: " + data); string s = DataProtection.ProtectData(data, name, NativeMethods.UIForbidden); if (null == s) { Console.WriteLine("Failure to encrypt"); return; } Console.WriteLine("Encrypted Data: " + s); s = DataProtection.UnprotectData(s); Console.WriteLine("Cleartext: " + s); } }

You can also use COM+ construction strings. COM+ object construction enables you to specify an initialization string stored in the COM+ metadata, thereby eliminating the need to hard-code configuration information within a class. You can use functions in the System.EnterpriseServices namespace to access a construction string. You should use this option only for protecting data used in server-based applications. The following code shows how you can create a COM+ component in C# that manages the constructor string. This component performs no other task than act as a conduit for the construct string. Note, you will need to create your own private/public key pair using the SN.exe tool when giving this a strong name. You will also need to replace the reference to c:\keys\DemoSrv.snk with the reference to your key data. Refer to Chapter 18, Writing Secure .NET Code, for information about strong named assemblies.

using System; using System.Reflection; using System.Security.Principal; using System.EnterpriseServices; [assembly: ApplicationName("ConstructDemo")] [assembly: ApplicationActivation(ActivationOption.Library)] [assembly: ApplicationAccessControl] [assembly: AssemblyKeyFile(@"c:\keys\DemoSrv.snk")] namespace DemoSrv { [ComponentAccessControl] [SecurityRole("DemoRole", SetEveryoneAccess = true)] // Enable object construct strings. [ConstructionEnabled(Default="Set new data.")] public class DemoComp : ServicedComponent { private string _construct; override protected void Construct(string s) { _construct = s; } public string GetConstructString() { return _construct; } } } 

And the following Microsoft ASP.NET code shows how you can access the data in the constructor string:

Function SomeFunc() As String ' Create a new instance of the ServicedComponent class ' and access our method that exposes the construct string. Dim obj As DemoComp = New DemoComp SomeFunc = obj.GetConstructString() End Sub

Administration of the constructor string data is performed through the Component Services MMC tool, as shown in Figure 9-2. You can find out more about System.EnterpriseServices at http://msdn.microsoft.com/msdnmag/issues/01/10/complus/complus.asp.

figure 9-2 setting a new constructor string for a com+ component.

Figure 9-2. Setting a new constructor string for a COM+ component.

Managing Secrets in Memory in Managed Code

Managing secret data in managed code is no different than doing so in unmanaged code. You should acquire the secret data, use it, and discard it. However, here's one small caveat: .NET strings are immutable. If the secret data is held in a string, it cannot be overwritten,. Therefore, it's crucial that secret data be stored in byte arrays and not strings. The following simple C# class, ErasableData, could be used instead of strings to store passwords and keys. Included is a driver program that takes a command-line argument and encrypts it with a key from the user. The key is then erased from memory when the work is done.

class ErasableData : IDisposable { private byte[] _rbSecret; private GCHandle _ph; public ErasableData(int size) { _rbSecret = new byte [size]; } public void Dispose() { Array.Clear(_rbSecret, 0, _rbSecret.Length); _ph.Free(); } // Accessors public byte[] Data { set { //Allocate a pinned data blob _ph = GCHandle.Alloc(_rbSecret, GCHandleType.Pinned); //Copy the secret into the array byte[] Data = value; Array.Copy (Data, _rbSecret, Data.Length); } get { return _rbSecret; } } } class DriverClass { static void Main(string[] args) { if (args.Length == 0) { // error! return; } //Get bytes from the argument. byte [] plaintext = new UTF8Encoding().GetBytes(args[0]); //Encrypt data in memory. using (ErasableData key = new ErasableData(16)) { key.Data = GetSecretFromUser(); Rijndael aes = Rijndael.Create(); aes.Key = key.Data; MemoryStream cipherTextStream = new MemoryStream(); CryptoStream cryptoStream = new CryptoStream( cipherTextStream, aes.CreateEncryptor(), CryptoStreamMode.Write); cryptoStream.Write(plaintext, 0, plaintext.Length); cryptoStream.FlushFinalBlock(); cryptoStream.Close(); //Get ciphertext and Initialization Vector (IV). byte [] ciphertext = cipherTextStream.ToArray(); byte [] IV = aes.IV; //Scrub data maintained by the crypto class. aes.Clear(); cryptoStream.Clear(); } } }

Notice that this code takes advantage of the IDisposable interface to automatically erase the object when it's no longer needed. The C# using statement obtains one or more resources, executes statements, and then disposes of the resource through the Dispose method. Also note the explicit call to aes.Clear and cryptoStream.Clear; the Clear method clears all secret data maintained by the encryption and streams classes.

A more complete sample C# class, named Password, is available with the sample code for this book.



Writing Secure Code
Writing Secure Code, Second Edition
ISBN: 0735617228
EAN: 2147483647
Year: 2001
Pages: 286

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net