Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Secure Memory Structures

I know the .NET library offers a way of storing a string in a protected/secure manner = SecureString.

My question is, if I would like to store a byte array, what would be the best, most secure container to hold this?

like image 804
Nick Avatar asked Jul 22 '09 17:07

Nick


4 Answers

A combination of RtlZeroMemory and VirtualLock can do what you want. VirtualLock if you want to keep the data from swapping to disk and RtlZeroMemory to ensure the memory gets zeroed (I tried to use RtlSecureZeroMemory but that doesn't seem to exist in kernel.dll) The class below will store a array of any of the built in types securely. I broke the solution up into two classes to separate out the type-agnostic code.

The first class just allocates and holds an array. It does a runtime check that the template type is a built in type. Unfortunately, I couldn't figure a way to do that at compile time.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

/// <summary>
/// Manage an array that holds sensitive information.
/// </summary>
/// <typeparam name="T">
/// The type of the array. Limited to built in types.
/// </typeparam>
public sealed class SecureArray<T> : SecureArray
{
    private readonly T[] buf;

    /// <summary>
    /// Initialize a new instance of the <see cref="SecureArray{T}"/> class.
    /// </summary>
    /// <param name="size">
    /// The number of elements in the secure array.
    /// </param>
    /// <param name="noswap">
    /// Set to true to do a Win32 VirtualLock on the allocated buffer to
    /// keep it from swapping to disk.
    /// </param>
    public SecureArray(int size, bool noswap = true)
    {
        this.buf = new T[size];
        this.Init(this.buf, ElementSize(this.buf) * size, noswap);
    }

    /// <summary>
    /// Gets the secure array.
    /// </summary>
    public T[] Buffer => this.buf;

    /// <summary>
    /// Gets or sets elements in the secure array.
    /// </summary>
    /// <param name="i">
    /// The index of the element.
    /// </param>
    /// <returns>
    /// The element.
    /// </returns>
    public T this[int i]
    {
        get
        {
            return this.buf[i];
        }

        set
        {
            this.buf[i] = value;
        }
    }
}

The next class does the real work. It tells the garbage collector to pin the array in memory. It then locks it so it doesn't swap. Upon disposal, it zeros the array and unlocks it and then tells the garbage collector to unpin it.

/// <summary>
/// Base class of all <see cref="SecureArray{T}"/> classes.
/// </summary>
public class SecureArray : IDisposable
{
    /// <summary>
    /// Cannot find a way to do a compile-time verification that the
    /// array element type is one of these so this dictionary gets
    /// used to do it at runtime.
    /// </summary>
    private static readonly Dictionary<Type, int> TypeSizes =
        new Dictionary<Type, int>
            {
                { typeof(sbyte), sizeof(sbyte) },
                { typeof(byte), sizeof(byte) },
                { typeof(short), sizeof(short) },
                { typeof(ushort), sizeof(ushort) },
                { typeof(int), sizeof(int) },
                { typeof(uint), sizeof(uint) },
                { typeof(long), sizeof(long) },
                { typeof(ulong), sizeof(ulong) },
                { typeof(char), sizeof(char) },
                { typeof(float), sizeof(float) },
                { typeof(double), sizeof(double) },
                { typeof(decimal), sizeof(decimal) },
                { typeof(bool), sizeof(bool) }
            };

    private GCHandle handle;

    private uint byteCount;

    private bool virtualLocked;

    /// <summary>
    /// Initialize a new instance of the <see cref="SecureArray"/> class.
    /// </summary>
    /// <remarks>
    /// You cannot create a <see cref="SecureArray"/> directly, you must
    /// derive from this class like <see cref="SecureArray{T}"/> does.
    /// </remarks>
    protected SecureArray()
    {
    }

    /// <summary>
    /// Gets the size of the buffer element. Will throw a 
    /// <see cref="NotSupportedException"/> if the element type is not
    /// a built in type.
    /// </summary>
    /// <typeparam name="T">
    /// The array element type to return the size of.
    /// </typeparam>
    /// <param name="buffer">
    /// The array.
    /// </param>
    /// <returns></returns>
    public static int BuiltInTypeElementSize<T>(T[] buffer)
    {
        int elementSize;
        if (!TypeSizes.TryGetValue(typeof(T), out elementSize))
        {
            throw new NotSupportedException(
              $"Type {typeof(T).Name} not a built in type. "
              + $"Valid types: {string.Join(", ", TypeSizes.Keys.Select(t => t.Name))}");
        }

        return elementSize;
    }

    /// <summary>
    /// Zero the given buffer in a way that will not be optimized away.
    /// </summary>
    /// <typeparam name="T">
    /// The type of the elements in the buffer.
    /// </typeparam>
    /// <param name="buffer">
    /// The buffer to zero.
    /// </param>
    public static void Zero<T>(T[] buffer)
        where T : struct
    {
        var bufHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            IntPtr bufPtr = bufHandle.AddrOfPinnedObject();
            UIntPtr cnt = new UIntPtr(
                 (uint)buffer.Length * (uint)BuiltInTypeElementSize(buffer));
            RtlZeroMemory(bufPtr, cnt);
        }
        finally
        {
            bufHandle.Free();
        }
    }

    /// <inheritdoc/>
    public void Dispose()
    {
        IntPtr bufPtr = this.handle.AddrOfPinnedObject();
        UIntPtr cnt = new UIntPtr(this.byteCount);
        RtlZeroMemory(bufPtr, cnt);
        if (this.virtualLocked)
        {
            VirtualUnlock(bufPtr, cnt);
        }

        this.handle.Free();
    }

    /// <summary>
    /// Call this with the array to secure and the number of bytes in that
    /// array. The buffer will be zeroed and the handle freed when the
    /// instance is disposed.
    /// </summary>
    /// <param name="buf">
    /// The array to secure.
    /// </param>
    /// <param name="sizeInBytes">
    /// The number of bytes in the buffer in the pinned object.
    /// </param>
    /// <param name="noswap">
    /// True to lock the memory so it doesn't swap.
    /// </param>
    protected void Init<T>(T[] buf, int sizeInBytes, bool noswap)
    {
        this.handle = GCHandle.Alloc(buf, GCHandleType.Pinned);
        this.byteCount = (uint)sizeInBytes;
        IntPtr bufPtr = this.handle.AddrOfPinnedObject();
        UIntPtr cnt = new UIntPtr(this.byteCount);
        if (noswap)
        {
            VirtualLock(bufPtr, cnt);
            this.virtualLocked = true;
        }
    }

    [DllImport("kernel32.dll")]
    private static extern void RtlZeroMemory(IntPtr ptr, UIntPtr cnt);

    [DllImport("kernel32.dll")]
    static extern bool VirtualLock(IntPtr lpAddress, UIntPtr dwSize);

    [DllImport("kernel32.dll")]
    static extern bool VirtualUnlock(IntPtr lpAddress, UIntPtr dwSize);
}

To use the class, simply do something like this:

using (var secret = new SecureArray<byte>(secretLength))
{
    DoSomethingSecret(secret.Buffer);
}

Now, this class does two things you shouldn't do lightly, first of all, it pins the memory. This can reduce performance because the garbage collector now must work around that memory it cannot move. Second, it can lock pages in memory that the operating system may wish to swap out. This short-changes other processes on your system because now they cannot get access to that RAM.

To minimize the detrimental effects of SecureArray<T>, don't use it a lot and use it only for short amounts of time. If you want to keep the data around for longer, then you need to encrypt it. For that, your best bet is the ProtectedData class. Unfortunately, that puts your sensitive data into a non-secure byte array. The best you can do from there is do a quick copy into a SecureArray<byte>.Buffer and then a SecureArray.Zero on the sensitive byte array.

like image 68
mheyman Avatar answered Nov 15 '22 07:11

mheyman


It is important to understand the vulnerability of the System.String type. It is impossible to make it completely secure, SecureString exists to minimize the risk of exposure. System.String is risky because:

  • Their content is visible elsewhere, without having to use a debugger. An attacker can look in the paging file (c:\pagefile.sys), it preserves the content of RAM pages that were swapped out to disk to make room for other programs that need RAM
  • System.String is immutable, you cannot scrub the content of a string after you used it
  • The garbage collected heap compacts the heap but does not reset the content of the memory that was freed-up. Which can leave a copy of the string data in memory, entirely out of reach from your program.

The clear risk here is that the string content can be visible long after the string was used, thus greatly increasing the odds that an attacker can see it. SecureString provides a workaround by storing the string in unmanaged memory, where it is not subject to the garbage collector leaving stray copies of the string content.

It should be clear now how you can create your own version of secure array with the same kind of guarantees that SecureString provides. You do not have the immutability problem, scrubbing the array after you use it is not a problem. Which in itself is almost always good enough, implicit in reducing the likelihood of exposure is that you don't keep a reference to the array for very long either. So the odds of the non-scrubbed copy of the array data surviving after a garbage collection should already be low. You can reduce that risk as well, present only for arrays less than 85,000 bytes. Either by doing it the way SecureString does it and using Marshal.AllocHGlobal(). Or much easier by pinning the array, GCHandle.Alloc().

like image 22
Hans Passant Avatar answered Nov 15 '22 08:11

Hans Passant


as of .Net 2.0 use the ProtectedData.Protect Method, looks like setting the scope to DataProtectionScope.CurrentUser should give the same desired effect as secure string

example usage taken from here

http://msdn.microsoft.com/en-us/library/system.security.cryptography.protecteddata.protect.aspx

using System;
using System.Security.Cryptography;

public class DataProtectionSample
{
// Create byte array for additional entropy when using Protect method. 
    static byte [] s_aditionalEntropy = { 9, 8, 7, 6, 5 };

    public static void Main()
    {
// Create a simple byte array containing data to be encrypted. 

byte [] secret = { 0, 1, 2, 3, 4, 1, 2, 3, 4 };

//Encrypt the data. 
        byte [] encryptedSecret = Protect( secret );
        Console.WriteLine("The encrypted byte array is:");
        PrintValues(encryptedSecret);

// Decrypt the data and store in a byte array. 
        byte [] originalData = Unprotect( encryptedSecret );
        Console.WriteLine("{0}The original data is:", Environment.NewLine);
        PrintValues(originalData);

    }

    public static byte [] Protect( byte [] data )
    {
        try
        {
            // Encrypt the data using DataProtectionScope.CurrentUser. The result can be decrypted 
            //  only by the same current user. 
            return ProtectedData.Protect( data, s_aditionalEntropy, DataProtectionScope.CurrentUser );
        } 
        catch (CryptographicException e)
        {
            Console.WriteLine("Data was not encrypted. An error occurred.");
            Console.WriteLine(e.ToString());
            return null;
        }
    }

    public static byte [] Unprotect( byte [] data )
    {
        try
        {
            //Decrypt the data using DataProtectionScope.CurrentUser. 
            return ProtectedData.Unprotect( data, s_aditionalEntropy, DataProtectionScope.CurrentUser );
        } 
        catch (CryptographicException e)
        {
            Console.WriteLine("Data was not decrypted. An error occurred.");
            Console.WriteLine(e.ToString());
            return null;
        }
    }

    public static void PrintValues( Byte[] myArr )  
    {
          foreach ( Byte i in myArr )  
            {
                 Console.Write( "\t{0}", i );
             }
      Console.WriteLine();
     }

}
like image 3
artfulhacker Avatar answered Nov 15 '22 06:11

artfulhacker


There is no "best" way to do this - you need to identify the threat you are trying to protect against in order to decide what to do or indeed if anything needs to be done.

One point to note is that, unlike a string which is immutable, you can zero out the bytes in a byte array after you've finished with them, so you won't have the same set of problems that SecureString is designed to solve.

Encrypting data could be appropriate for some set of problems, but then you will need to identify how to protect the key from unauthorized access.

I find it difficult to imagine a situation where encrypting a byte array in this way would be useful. More details of exactly what you're trying to do would help.

like image 2
Joe Avatar answered Nov 15 '22 07:11

Joe