I tried to convince a product manager that we must not store user passwords in the application's SQL Server database in any reversible form. Plain text is a suicidal risk, and encrypted passwords lead you into the murky world of dealing with secret keys that encrypt the passwords and trusting who has access to those keys. I believe that it is highly objectionable to store passwords in any reversible form, as you are then at risk from sloppy security implementations or corrupt people. What if you use the same valuable password for a magazine subscription and your Internet banking? What if your password is the name of your secret lover? (don’t laugh, this has happened). You don’t want your passwords revealed to anyone by any means.
It is preferable to store passwords as a one-way hash. In .NET you can use the
Rfc2898DeriveBytes class. I like to assign a permanent and immutable Guid to users which can conveniently provide 16 bytes of salt for the hash.
For historical reasons I was forced to reversibly encrypt user passwords, so I had to play the game called ‘hide the secret’. This tricky issue is discussed in Chapter 70 of Keith Brown’s book
The .NET Developer’s Guide to Windows Security, and in Chapter 9 of Michael Howard’s
Writing Secure Code.
I eventually decided that C# code in a CA (Custom Action) in the Visual Studio 2015 Setup Project would put the secret in a registry key under HKLM and set ACLs on the key to allow read access only by the specific account that needed it, which in this case was NETWORK SERVICE.
I had never written code to set registry permission before, so I wrote a small test program to get the C# code correct before migrating it into the installer. It took a bit of fiddling around to get the desired access rule combinations, so I have made the test program public as a reminder to myself and possible help for anyone else who wants to do this sort of thing. See:
https://dev.azure.com/orthogonal/RegKeySecurity
You must run this test program as an Administrator. Then run regedit as an Administrator and you will see the keys created under HKLM and the child key containing the secret will have special non-inherited permissions. Run regedit as a normal user and you will be denied access to the child key.
32/64-Bit Issues
The first time I migrated my test code into the CA, installed the application and ran it, I received a null argument crash. I discovered that the CA had created the registry keys under HKLM\Wow6432Node, but the application was reading the keys from HKLM\SOFTWARE.
So the installer was writing to the 32-bit view of the registry but the application was reading from the 64-bit view of the registry. Changing the TargetPlatform property of the Setup Project from x32 to x64 has no effect on the installer's registry view.
Since I know that all installations will be on 64-bit Windows servers I can force the CA to write to the 64-bit view of the registry by replacing this:
var machinekey = Registry.LocalMachine
with
var machinekey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)
Then at runtime the 'Any CPU' application running on 64-bit Windows will be able to read the secret data from the registry.