Recipe17.8.Storing Data Securely


Recipe 17.8. Storing Data Securely

Problem

You need to store settings data about individual users for use by your application and keep this data isolated from other instances of your application run by different users.

Solution

You can use isolated storage to establish per-user data stores for your application data and then use hashed values for critical data.

To illustrate how to do this for settings data, you create the following UserSettings class. UserSettings holds only two pieces of information: the user identity (current WindowsIdentity) and the password for your application. The user identity is accessed via the User property, and the password is accessed via the Password property. Note that the password field is created the first time and is stored as a salted hashed value to keep it secure. The combination of the isolated storage and the hashing of the password value helps to strengthen the security of the password by using the defense in depth principle. The settings data is held in XML that is stored in the isolated storage scope and accessed via an XmlDocument instance.

This solution uses the following namespaces:

 using System; using System.IO; using System.IO.IsolatedStorage; using System.Xml; using System.Text; using System.Diagnostics; using System.Security.Principal; using System.Security.Cryptography; 

The UserSettings class is shown in Example 17-13.

Example 17-13. UserSettings class

 // Class to hold user settings  public class UserSettings {     isoFileStream = null;     XmlDocument settingsDoc = null;     const string storageName = "SettingsStorage.xml";     // Constructor     public UserSettings(string password)     {         // Get the isolated storage.         using (IsolatedStorageFile isoStorageFile =               IsolatedStorageFile.GetUserStoreForDomain( ))         {             // Create an internal DOM for settings.             settingsDoc = new XmlDocument( );             // If no settings, create default.             if(isoStorageFile.GetFileNames(storageName).Length == 0)             {                 using (IsolatedStorageFileStream isoFileStream =                      new IsolatedStorageFileStream(storageName,                                                FileMode.Create,                                                isoStorageFile))                 {                     using (XmlTextWriter writer = new                           XmlTextWriter(isoFileStream,Encoding.UTF8))                     {                         writer.WriteStartDocument( );                         writer.WriteStartElement("Settings");                         writer.WriteStartElement("User");                         // Get current user.                         WindowsIdentity user = WindowsIdentity.GetCurrent( );                         writer.WriteString(user.Name);                         writer.WriteEndElement( );                         writer.WriteStartElement("Password");                         // Pass null to CreateHashedPassword as the salt                         // to establish one                         // CreateHashedPassword appears shortly                         string hashedPassword =                                 CreateHashedPassword(password,null);                         writer.WriteString(hashedPassword);                         writer.WriteEndElement( );                         writer.WriteEndElement( );                         writer.WriteEndDocument( );                         Console.WriteLine("Creating settings for " + user.Name);                     }                 }             }             // Set up access to settings store.             using (IsolatedStorageFileStream isoFileStream =                  new IsolatedStorageFileStream(storageName,                                            FileMode.Open,                                            isoStorageFile))             {                 // Load settings from isolated filestream                 settingsDoc.Load(isoFileStream);                 Console.WriteLine("Loaded settings for " + User);             }         }     } 

The User property provides access to the WindowsIdentity of the user that this set of settings belongs to:

 // User property public string User {     get     {         XmlNode userNode = settingsDoc.SelectSingleNode("Settings/User");         if(userNode != null)         {             return userNode.InnerText;         }         return "";     } } 

The Password property gets the salted and hashed password value from the XML store and, when updating the password, takes the plain text of the password and creates the salted and hashed version, which is then stored:

 // Password property public string Password {     get     {         XmlNode pwdNode =                   settingsDoc.SelectSingleNode("Settings/Password");         if(pwdNode != null)         {             return pwdNode.InnerText;         }         return "";     }     set     {         XmlNode pwdNode =                   settingsDoc.SelectSingleNode("Settings/Password");         string hashedPassword = CreateHashedPassword(value,null);         if(pwdNode != null)         {             pwdNode.InnerText = hashedPassword;         }         else         {             XmlNode settingsNode =                       settingsDoc.SelectSingleNode("Settings");             XmlElement pwdElem =                          settingsDoc.CreateElement("Password");             pwdElem.InnerText=hashedPassword;             settingsNode.AppendChild(pwdElem);         }     } } 

The CreateHashedPassword method creates the salted and hashed password. The password parameter is the plain text of the password; the existingSalt parameter is the salt to use when creating the salted and hashed version. If no salt exists, such as the first time a password is stored, existingSalt should be passed as null, and a random salt will be generated.

Once you have the salt, it is combined with the plain text password and hashed using the SHA512Managed class. The salt value is then appended to the end of the hashed value and returned. The salt is appended so that when you attempt to validate the password, you know what salt was used to create the hashed value. The entire value is then base64-encoded and returned:

 // Make a hashed password. private string CreateHashedPassword(string password,                                     byte[] existingSalt) {     byte [] salt = null;     if(existingSalt == null)     {         // Make a salt of random size.         // Create a stronger hash code using RNGCryptoServiceProvider.         byte[] random = new byte[1];         RNGCryptoServiceProvider rngSize = new RNGCryptoServiceProvider();         // Populate with random bytes.         rngSize.GetBytes(random);         // Convert random bytes to string.         int size = Convert.ToInt32(random);         // Create salt array.         salt = new byte[size];         // Use the better random number generator to get         // bytes for the salt.         RNGCryptoServiceProvider rngSalt =             new RNGCryptoServiceProvider();         rngSalt.GetNonZeroBytes(salt);     }     else         salt = existingSalt;         // Turn string into bytes.         byte[] pwd = Encoding.UTF8.GetBytes(password);         // Make storage for both password and salt.         byte[] saltedPwd = new byte[pwd.Length + salt.Length];         // Add pwd bytes first.         pwd.CopyTo(saltedPwd,0);         // now add salt         salt.CopyTo(saltedPwd,pwd.Length);         // Use SHA512 as the hashing algorithm.         byte[] hashWithSalt = null;         using (SHA512Managed sha512 = new SHA512Managed( ))         {             // Get hash of salted password.             byte[] hash = sha512.ComputeHash(saltedPwd);             // Append salt to hash so we have it.             hashWithSalt = new byte[hash.Length + salt.Length];             // Copy in bytes.             hash.CopyTo(hashWithSalt,0);             salt.CopyTo(hashWithSalt,hash.Length);         }         // Return base64-encoded hash with salt.         return Convert.ToBase64String(hashWithSalt);     } 

To check a given password against the stored value (which is salted and hashed), you call CheckPassword and pass in the plain text password to check. First, the stored value is retrieved using the Password property and converted from base64. Since you know you used SHA512, there are 512 bits in the hash. But you need the byte size, so you do the math and get that size in bytes. This allows you to figure out where to get the salt from in the value, so you copy it out of the value and call CreateHashedPassword using that salt and the plain text password parameter. This gives you the hashed value for the password that was passed in to verify. Once you have that, you just compare it to the Password property to see whether you have a match and return TRue or false as appropriate:

     // Check the password against our storage.     public bool CheckPassword(string password)     {         // Get bytes for password.         // This is the hash of the salted password and the salt.         byte[] hashWithSalt = Convert.FromBase64String(Password);         // We used 512 bits as the hash size (SHA512).         int hashSizeInBytes = 512 / 8;         // Make holder for original salt.         int saltSize = hashWithSalt.Length - hashSizeInBytes;         byte[] salt = new byte[saltSize];         // Copy out the salt.         Array.Copy(hashWithSalt,hashSizeInBytes,salt,0,saltSize);         // Figure out hash for this password.         string passwordHash = CreateHashedPassword(password,salt);         // If the computed hash matches the specified hash,         // the plain text value must be correct.         // See if Password (stored) matched password passed in.         return (Password == passwordHash);     } } 

Code that uses the UserSettings class is shown here:

 class IsoApplication {   tatic void Main(string[] args)   {        if(args.Length > 0)        {            UserSettings settings = new UserSettings(args[0]);            if(settings.CheckPassword(args[0]))             {                Console.WriteLine("Welcome");                return;             }         }         Console.WriteLine("The system could not validate your credentials");     } } 

The way to use this application is to pass the password on the command line as the first argument. This password is then checked against the UserSettings, which is stored in the isolated storage for this particular user. If the password is correct, the user is welcomed; if not, the user is shown the door.

Discussion

Isolated storage allows an application to store data that is unique to the application and the user running it. This storage allows the application to write out state information that is not visible to other applications or even other users of the same application. Isolated storage is based on the code identity as determined by the CLR, and it stores the information either directly on the client machine or in isolated stores that can be opened and roam with the user. The storage space available to the application is directly controllable by the administrator of the machine on which the application operates.

The Solution uses isolation by User, appdomain, and Assembly by calling IsolatedStorageFile.GetUserStoreForDomain. This creates an isolated store that is accessible by only this user in the current assembly in the current appdomain:

 // Get the isolated storage. isoStorageFile = IsolatedStorageFile.GetUserStoreForDomain( ); 

The Storeadm.exe utility will allow you to see which isolated-storage stores have been set up on the machine by running the utility with the /LIST command-line switch. Storeadm.exe is part of the .NET Framework SDK and can be located in your Visual Studio installation directory under the \SDK\v2.0\Bin subdirectory.

The output after using the UserSettings class would look like this:

 C:\>storeadm /LIST Microsoft (R) .NET Framework Store Admin 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Record #1 [Domain] <System.Security.Policy.Url version="1">    <Url>file://D:/PRJ32/Book/IsolatedStorage/bin/Debug/IsolatedStorage.exe</Url> </System.Security.Policy.Url> [Assembly] <System.Security.Policy.Url version="1">    <Url>file://D:/PRJ32/Book/IsolatedStorage/bin/Debug/IsolatedStorage.exe</Url> </System.Security.Policy.Url>            Size : 1024 

Passwords should never be stored in plain text, period. It is a bad habit to get into, so in the UserSettings class, you have added the salting and hashing of the password value via the CreateHashedPassword method and verification through the CheckPassword method. Adding a salt to the hash helps to strengthen the protection on the value being hashed so that the isolated storage, the hash, and the salt now protect the password you are storing.

See Also

See the "IsolatedStorageFile Class," "IsolatedStorageStream Class," "About Isolated Storage," and "ComputeHash Method" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

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