Partager via


Secure String in .Net - Part II

Hi Gaurav Sharma here with more information about SecureStrings. This time I'll cover following topics:

  1. SecureString internals
  2. Performance

Let us start with our first topic,

  • SECURE STRING INTERNALS

    • BASICS
      • Class Name: SecureString
      • Assembly: mscorlib.dll
      • Latest Version: 2.0.0.0
      • Namespace: System.Security
      • Implements: IDisposable
      • Inherits: CriticalFinalizerObject
      • Access Specifier: Public
      • Can be inherited: No, it is a sealed class
    • INTERNALS
      • Constructors - SecureString type contains four constructor implementations out of which one is a type constructor and other three are instance constructors.
        • static SecureString()
          • As this is a static constructor, this is called before any member of SecureString type is accessed. Internally, only thing it does is, it sets a flag supportedOnCurrentPlatform to true or false.
          • To set this boolean value a call is made to Win32 native RtlDecryptMemory method. This method is available as a resource named SystemFunction041 in Advapi32.dll. This method call is made to check wether current operating system supports encryption and decryption.
          • If this method entry is not found then supportedOnCurrentPlatform is set to false otherwise it is set to true.
          • All other constructors first check for this boolean value and if it is set to false then a NotSupportedException is thrown.
        • public SecureString()
          • Checks supportedOnCurrentPlatform variable and throws NotSupportedException exception if it is false.
          • If supportedOnCurrentPlatform  is true then buffer of 8 bytes is allocated. Methods used to allocate space are decorated with [ReliabilityContract(Consistency.WillNotCorruptState)] attribute. This means that in the face of exceptional conditions, the method/s is guaranteed not to corrupt state.
          • In case of invalid buffer state or invalid memory allocation request, OutOfMemoryException() will be thrown.
        • internal SecureString(SecureString str)
          • This constructor takes an existing SecureString object as a parameter and creates a new SecureString object with length and data of existing object.
          • This time there is no check for supportedOnCurrentPlatform  as already there is an active secure string object (passed as parameter) which indirectly means that SecureStrings are supported on this platform.
        • public unsafe SecureString(char* value, int length)
          • This is non CLS compliant as it uses pointers.
          • Contents of memory area specified by char* are copied to a new byte* .
          • Data content is then encrypted by making a call to Win32 native RtlEncryptMemory method. This method is available as a resource named SystemFunction040 in Advapi32.dll. This process is called Protecting Memory and is implemented in the ProtectMemory() method explained in next section.
          • In case of error while protecting memory a CryptographicException is thrown.
      • Encryption and Decryption - SecureString class implements encryption and decryption using ProtectMemory() and UnProtectMemory() methods respectively
        • ProtectMemory()

          • private method

          • Some of the SecureString methods that use ProtectMemory() are,

            • Constructor - public unsafe SecureString(char* value, int length)
            • AppendChar(Char)
            • InsertAt(Int32, Char)
            • RemoveAt(Int32)
            • SetAt(Int32,Char)
          • This method is decorated with [ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)] attribute. Here Consistency.MayCorruptInstance means that in the face of exceptional conditions, the method is guaranteed to limit state corruption to the current instance. Whereas, Cer.MayFail means that in the face of exceptional conditions, the method might fail. In this case, the method will report back to the calling method whether it succeeded or failed.

          • Pseudocode 

             [ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
            
             ProtectMemory()
            
             {
            
                IF ((Length OF SecureStringObj IS NOT 0) AND (SecureStringObj IS NOT Encrypted))
            
                 {
            
                    BEGIN Constrained Execution Region
            
                    {            
            
                      CALL Win32Native.RtlEncryptMemory method AND Store Result IN @RES 
            
                      IF (@RES Shows Error)
            
                         THROW CRYPTOGRAPHIC_EXCEPTION
            
                      ELSE
            
                         SET SecureStringObj.IsEncrypted to TRUE;
            
                     }
            
                 }
            
        • UnProtectMemory()

          • private method

          • Some of the SecureString methods that use UnProtectMemory() are,

            • Constructor - public unsafe SecureString(char* value, int length)
            • AppendChar(Char)
            • InsertAt(Int32, Char)
            • RemoveAt(Int32)
            • SetAt(Int32,Char)
          • This method is decorated with [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] attribute. Here Consistency.WillNotCorruptState means that in the face of exceptional conditions, the method is guaranteed not to corrupt state. Whereas, Cer.Success means that in the face of exceptional conditions, the method is guaranteed to succeed.

          • Pseudo

             [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            
             UnProtectMemory()
            
             {
            
                IF ((Length OF SecureStringObj IS NOT 0) AND (SecureStringObj IS Encrypted))
            
                 {
            
                    BEGIN Constrained Execution Region
            
                    {            
            
                      CALL Win32Native.RtlDecryptMemory method AND Store Result IN @RES 
            
                      IF (@RES Shows Error)
            
                         THROW CRYPTOGRAPHIC_EXCEPTION
            
                      ELSE
            
                         SET SecureStringObj.IsEncrypted to FALSE;
            
                     }
            
                 }
            
             }
            
        • Both these methods are so implemented that in no case they can create instability in an application.

        • You can get more details about constraint execution region from MSDN

  • PERFORMANCE

I think, comparing String and SecureString is not completely justified. SecureString was created taking in mind the shortcomings of String class. The way in which SecureString works like managing pointers, creating  constrained execution regions, encryption and decryption, it is bound to be on the slower side of performance. In this section I'll try to show you exactly how much slower SecureString is as compared to tradition String class.

    • STRING CREATION

      • Code Snippet

            1:  Int32 loopCounter = 0;
        
            2:  Int32 loopMaxCounter = 100000;
        
            3:  List<SecureString> secStrList = new List<SecureString>();
        
            4:  Stopwatch sw = new Stopwatch();
        
            5:  sw.Start();
        
            6:   
        
            7:  sw.Reset();
        
            8:  List<String> StrList = new List<String>();
        
            9:  sw.Start();
        
           10:  Console.WriteLine("-----------------------------------------------------------");
        
           11:  Console.WriteLine("Creating 100000 instances of strings");
        
           12:  for (loopCounter = 0; loopCounter < loopMaxCounter; loopCounter++)
        
           13:  {
        
           14:      String str = new string(new Char[] { 'a' });
        
           15:      StrList.Add(str);
        
           16:  }
        
           17:  Console.WriteLine("Created 100000 instances of string. Elapsed time (in milliseconds) " +
        
           18:  sw.Elapsed.Milliseconds.ToString());
        
           19:  Console.WriteLine("-----------------------------------------------------------");
        
           20:  sw.Stop();
        
           21:  sw.Reset();
        
           22:  sw.Start();
        
           23:  Console.WriteLine("-----------------------------------------------------------");
        
           24:  Console.WriteLine("Creating 100000 instances of secure strings");
        
           25:  for (loopCounter = 0; loopCounter < loopMaxCounter; loopCounter++)
        
           26:  {
        
           27:      SecureString obj = new SecureString();
        
           28:      obj.AppendChar('a');
        
           29:      secStrList.Add(obj);
        
           30:  }
        
           31:  Console.WriteLine("Created 100000 instances of secure string. Elapsed time (in milliseconds)" +
        
           32:      sw.Elapsed.Milliseconds.ToString());
        
           33:  sw.Stop();
        
           34:  Console.WriteLine("-----------------------------------------------------------");
        
      • Result

image

    • STRING MANIPULATION - Appending characters using AppendChar() method

      • Code Snippet

            1:  SecureString secString = new SecureString();
        
            2:   
        
            3:  String str=String.Empty;
        
            4:  Stopwatch sw = new Stopwatch();
        
            5:   
        
            6:  Int32 loopCounter = 0;
        
            7:  Int32 loopMaxCounter = 10000;
        
            8:  Console.WriteLine("---------------------------------------------------------");
        
            9:  Console.WriteLine("Loop will run {0} times",loopMaxCounter);
        
           10:  Console.WriteLine("Current String Length  is {0}", str.Length.ToString());
        
           11:  sw.Start();
        
           12:  for (loopCounter = 0; loopCounter < loopMaxCounter; loopCounter++)
        
           13:  {
        
           14:      str = str + "a";
        
           15:  }
        
           16:  Console.WriteLine("String insertion completed in {0} milliseconds",sw.Elapsed.Milliseconds.ToString());
        
           17:  Console.WriteLine("Current length of String is {0}.", str.Length.ToString());
        
           18:  Console.WriteLine("----------------------------------------------------------");
        
           19:  sw.Stop();
        
           20:  sw.Reset();
        
           21:  sw.Start();
        
           22:  Console.WriteLine("----------------------------------------------------------");
        
           23:  Console.WriteLine("Loop will run {0} times", loopMaxCounter);
        
           24:  Console.WriteLine("Current Secure String Length  is {0}", str.Length.ToString());
        
           25:  for (loopCounter = 0; loopCounter < loopMaxCounter; loopCounter++)
        
           26:  {
        
           27:      try
        
           28:      {
        
           29:          secString.AppendChar('a');
        
           30:      }
        
           31:      catch (Exception ex)
        
           32:      {
        
           33:          Console.WriteLine(ex.ToString());
        
           34:      }
        
           35:  }
        
           36:  Console.WriteLine("Secure String insertion completed in {0} milliseconds", sw.Elapsed.Milliseconds.ToString());
        
           37:  Console.WriteLine("Current length of Secure String is {0}.", secString.Length.ToString());
        
           38:  Console.WriteLine("-----------------------------------------------------------");
        
           39:  sw.Stop();
        
           40:  Console.Read();
        
      • Result

image

If we search for SecureString on web we can find a lot of questions going around related to pros and cons of SecureString usage. I'll compile a list of all of such type of questions in my next post.

Below are some reference sources that I used to get information related to secure string internals.

  • REFERENCES
Subject Link
ReliabilityContract https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.reliabilitycontractattribute.aspx
Consistency Enum https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.consistency.aspx
Cer https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.cer.aspx
Constrained Execution Region https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspx
Win32Native.RtlDecryptMemory https://msdn.microsoft.com/en-us/library/aa387692(VS.85).aspx
Win32Native.RtlEncryptMemory https://msdn.microsoft.com/en-us/library/aa387693(VS.85).aspx

Comments