8

I have a program that uses System.DirectoryServices.AccountManagement.PrincipalContext to verify that the information a user entered in a setup screen is a valid user on the domain (the computer itself is not on the domain) and do some operations on the users of the domain. The issue is I do not want the user to need to enter his or her password every time they run the program so I want to save it, but I do not feel comfortable storing the password as plain-text in their app.config file. PrincipalContext needs a plain-text password so I can not do a salted hash as everyone recommends for password storing.

This is what I did

const byte[] mySalt = //It's a secret to everybody.
[global::System.Configuration.UserScopedSettingAttribute()]
public global::System.Net.NetworkCredential ServerLogin
{
    get
    {
        var tmp = ((global::System.Net.NetworkCredential)(this["ServerLogin"]));
        if(tmp != null)
            tmp.Password = new System.Text.ASCIIEncoding().GetString(ProtectedData.Unprotect(Convert.FromBase64String(tmp.Password), mySalt, DataProtectionScope.CurrentUser));
        return tmp;
    }
    set
    {
        var tmp = value;
        tmp.Password = Convert.ToBase64String(ProtectedData.Protect(new System.Text.ASCIIEncoding().GetBytes(tmp.Password), mySalt, DataProtectionScope.CurrentUser));
        this["ServerLogin"] = value;
    }
}

Was this the right thing to do or is there a better way?

EDIT -- Here is a updated version based on everyone's suggestions

private MD5 md5 = MD5.Create();

[global::System.Configuration.UserScopedSettingAttribute()]
public global::System.Net.NetworkCredential ServerLogin
{
    get
    {
        var tmp = ((global::System.Net.NetworkCredential)(this["ServerLogin"]));
        if(tmp != null)
            tmp.Password = System.Text.Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(tmp.Password), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmp.UserName.ToUpper())), DataProtectionScope.CurrentUser));
        return tmp;
    }
    set
    {
        var tmp = value;
        tmp.Password = Convert.ToBase64String(ProtectedData.Protect(System.Text.Encoding.UTF8.GetBytes(tmp.Password), md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tmp.UserName.ToUpper())), DataProtectionScope.CurrentUser));
        this["ServerLogin"] = tmp;
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 1
    Who and what are you trying to defend the password from? – SLaks Feb 16 '10 at 14:23
  • I would say 'proper' way would be keeping the Kerberos ticket, but I don't know how in this context, sorry. – mtmk Feb 16 '10 at 14:26
  • 1
    @Slaks - I am defending the password from bored coworkers who have sat down at someone else's computer. I just want to protect from the casual observer, not a determined hacker. – Scott Chamberlain Feb 16 '10 at 14:32

3 Answers3

4

For the salt, I'd do a transformation on the username (hash it) rather than share the same salt for everyone.

For something like this, I'd also look for a way to keep the existing session alive longer rather than saving the password to create new sessions.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • The session is alive the entire instance the program is run, I just need to create the session with the server when the program starts, or did you mean keep the session alive between runs? – Scott Chamberlain Feb 16 '10 at 14:38
  • You could use a service or other background program to hold the session and keep it alive longer. – Joel Coehoorn Feb 16 '10 at 14:43
2

Instead of writing new System.Text.ASCIIEncoding(), you should write System.Text.Encoding.ASCII.

Also, I recommend using UTF8 instead.

Other than that, your code looks pretty good.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
0

I like the JoelCoehoorn approach.

Use a value unique for the user machine as the password salt.

So it will be different in each deplyment ; ).

UPDATE: See this thread for ideas: How-To-Get-Unique-Machine-Signature

Community
  • 1
  • 1
SDReyes
  • 9,798
  • 16
  • 53
  • 92