.Net portable password management made easy!
A key storage class library based on DPAPI? Why not an easy portable key storage framework to be used with
Microsoft .Net 2.0 and
Mono 2.0 on any platform?
Currently supports:
The idea behind this project is to create a simple open source framework for a transparent portable key storage management in .Net, targetting
.Net Framework 2.0 and
Mono 2.0.
Many applications (web browsers, email clients, web applications, network connettivity tools, to name a few) need a way to save and retrieve passwords, connection strings and other secrets (commonly called
keys in this context), based on the user's profile.
It should not be possible for other users (and in some cases for other applications) to retrieve the stored secrets. This feature is accomplished through standard cryptographic solutions. The big challenge is how to
securely store the decryption key needed to decrypt the data when the application needs to retrieve it.
Please note that this kind of technology, although suitable for managing passwords and other similar secrets, is not suitable for more complex scenarios, requiring a stronger degree of privacy.
Well if this seems a limit for you, start thinking on a suitable way of securely store encryption keys without having to ask the user for a password (or smart card, fingerprints, retinal scans, etc.) and you will probably end up with a solution similar to those
discussed here. If not, you're probably the next crypto hero :-)
Each platform (MS Windows, Linux, Mac OS X, etc.) provides a solution for solving this problem:
Wouldn't it be nice to have a single simple framework to handle password storage without having to care on which platform your app is currently running on?
Enough theory by now! Here's the code:
// get the a provider instance for the current platform IKeyStorageProvider provider = KeyStorageManager.GetProvider(); // Store a key provider.StoreKey("key1", "thebigsecret"); // Retrieve a key string secret = provider.RetrieveKeyAsString("key1"); // Delete a key provider.DeleteKey("key1");
Just reference KeyStoragemanager.dll among your project references.
As you can see, it is really easy. Storing, retrieving and deleting keys is easy and you don't have to bother thinking about the platform your app is running on.
Ok, but what if the secret data is not a string? Here we go:
byte [] secret = Encoding.UTF8.GetBytes("theotherbigsecret"); // Store a key provider.StoreKey("key2", secret); // Retrieve a key secret = provider.RetrieveKey("key2");
That's it! More overloads can be added as needed, e.g. to handle object serialization.
If you're interested in a simple use of this class library, you don't need to read any further.
You can use it in any kind of app, even web apps. On Windows there's no problem at all, but please note that on Linux and Mac OS X user interaction might be required to unlock the key containers!
Before digging into the details, here's the class diagram:
For the architecturally minded, basically it implements an Abstract factory pattern along with an
Adapter pattern for each implemented platform. Back to the code, that's enough with it. :-)
For each platform, at least two classes need to be implemented:
Each IKeyStorageProvider, implementing class might also provide methods and properties specific to the implementation.
Finally, KeyStorageProvider is a factory with a static method to return a new instance of
IKeyStorageProvider suitable for the platform where the calling app is running on.
Ok, now to go on with this subject we need to inroduce each platform!
On MS Windows 2000 and Windows CE 4.0, a specific API is available: two functions
CryptProtectData/CryptUnprotectData, commonly called DPAPI. For details see:
http://msdn.microsoft.com/en-us/library/ms995355.aspx.
This API is used broadly in the Windows OS, for example by Internet Explorer and Windows Explorer's network password storage.
This API does not provide a way to store the encrypted data. It just provides a way to transparently encrypt and decrypt data, without the need of an encryption key. The key is stored in the user profile, encrypted with data derivated, among other things, from
the user's password. If an administrator resets the user's password the encryption key changes and previously encrypted data becomes unrecoverable!
How secure is this?
Pros:
Cons:
Note: the latter is not really a "cons", as having administrative/root access can compromise the security on
any machine, one way or the other.
DPAPIKeyStorageProvider is the class implementing the IKeyStorageProvider interface. The class handles encryption and decryption. It is marked as abstract in order to leave the actual storage handling to any subclass.
As already stated, DPAPI don't provide a way to store the encrypted data. Data can be stored anywhere, as there's no way to get the clear text back without the correct decryption key.
This framework provides am XML file based solution (DPAPIXmlKeyStorageProvider), but a different one based on the Windows Registry or a database can be easily provided by subclassing
DPAPIKeyStorageProvider.
DPAPIXmlKeyStorageProvider stores the data in a file called KeyStorage.xml in the local application data directory (e.g.:
c:\Users\username\AppData\Local\KeyStorage.xml on Vista). This can be changed with the
KeyStorageXmlPath property.
Please note that the XML is validated against an XSD schema which is embedded in the assembly. Please see the snapshot below for a look at the document structure. Encrypted data is simply encoded with Base64.
DPAPIKeyStorageProvider provides three abstract methods (pure virtual, in C++ terms) to be implemented in subclasses:
protected abstract void StoreData(string name, byte [] secret); protected abstract byte[] LoadData(string name); protected abstract bool DeleteData(string name);
Those methods just store, return and delete the encrypted data. As already stated the encryption/decryption process is left to the base class and is independent from the chosen storage.
What about the DPAPI function calls? This is just a matter of P/Invoke. I provided a wrapper assembly here, simply called
DPAPIWrapper.dll (the name is not that original, but at least you know what it is for :-) ). Sources are also available here. Please note that there are plenty of similar wrappers on the web. There's also one available on MS Patterns &
Practices.
If you want to use the DPAPIWrapper in your .Net apps, just call the following static methods, or one of the simpler overloads, from the
AlexPilotti.DPAPI.DPAPIWrapper class.
public static byte[] Encrypt(byte[] plainText, byte[] optionalEntropy, Store store); public static byte[] Decrypt(byte[] cipherText, byte[] optionalEntropy, Store store);
This is GNOME's solution for key management, available on Linux and other GNOME supported platforms. On Linux there's also
kwallet, from the KDE environment, which has not yet been added to this framework.
A keyring is a key (secrets) container. Two keyrings (login and
session) are available by default, but others can be created as needed. The design is similar to Mac OS X Keychain Services, also discussed here.
The session keyring is non persistent, this means that data will be lost when the session ends.
This solution is based on a user space daemon (called gnome-keyring-daemon). When a user or application wants to access a key inside a keyring, a password must be provided to unlock the latter. A PAM module (libpam-gnome-keyring) is available in order
to unlock automatically keyrings at login and when exiting the screen saver. In order for this to work a keyring must have the same password as the user's login.
If the user's login password changes, after the next logon a password needs to be provided to manually unlock a keyring. Although maybe a bit annoying,
this is really safe, as there's no way for someone not knowing your password to access your stored keys!
Persistent keyrings are stored in the user home under: ~/.gnome2/keyrings/
The related API reference is available here:
http://library.gnome.org/devel/gnome-keyring/
and a CLI implemenentation is available here:
here: http://andrew.jorgensenfamily.us/2008/08/gnome-keyring-sharp/, which has been used in this project.
Thanks to Andrew Jorgensen for saving me the time to P/Invoke the C API. :-)
There's also a gnome-keyring-manager to graphically manage your keyrings. In case it should not be available as a packaged binary on your Linux distro, the sources are available here:
http://ftp.acc.umu.se/pub/gnome/sources/gnome-keyring-manager/. Just run
./configure and make install to compile and install.
Alternatively a GNOME application called seahorse is available. This app is also able to change a keyring's password, useful in case it got out of sync with the user's password, which is still a problem when the user's password is changed.
Please note: gnome-keyring-daemon is not started automatically on a command line login (e.g via SSH).
The gnome-keyring-manager:
The adapter class implementing the GNOME features is called GnomeKeyringProvider.cs.
By default the default (login) keyring is employed as a container. This can be changed by setting the
Keyring property. Use "session" to employ a non persistent keyring.
As already stated we are using a managed class to access the service, so there's no need for P/Invoke.
// To store a secret: Ring.CreateItem(Keyring, ItemType.GenericSecret, name, tbl, secret, true);
// To retrieve a secret:
Ring.Find(ItemType.GenericSecret, tbl))
Please refer to the class library sources for more details.
Mac OS X includes a technology called Keychain Services API to do the job.
A Keychain is a container of keys (passwords, certificates, text, etc). A keychain called
login is available by default for each user. An application is free to create as many keychains as desired.
Keychains are protected by a password through encryption. If a keychain's password matches the user password, the keychain is unlocked at login. Otherwise it must be unlocked manually. Once unlocked, the contained keys are accessible by any application in the
current user session (executed with the user's credentials).
What happens to a keychain automatically unlocked at login if the user's login password is changed?
This means that if someone stoles your MacBook, changes your password and does a login with your username,
there's no way to retrieve your keys!
This happens of course if the auto logon feature is off (i.e. the user provides a password to login).
User keychains are stored under ~/Library/Keychains/
What about the programming model?
The API is documented here:
http://developer.apple.com/documentation/Security/Conceptual/keychainServConcepts/keychainServConcepts.pdf
The API are written in C and are part of the Security framework and thus easily available to our managed code via P/Invoke.
Keychains can be easily accesses with the Keychain Access UI, available in the standard
Utilities folder:
MacOSXKeychainProvider implements the desired service features.
Currently only the default keychain (login) is supported, but adding support for selecting a different Keychain is not a big issue.
The class includes the required P/Invoke external methods to access the Keychain services API.
Please note that the API are part of a framewok. In Mac OS X, a framework is a shared library along with headers, resources, versioning and more.
The problem is that Mono is not able to look in the default path for the library name referred in the
DllImportAttribute.
That's why we provide the full path to the shared library: "/System/Library/Frameworks/Security.framework/Security"
This is a simple app used to test the features discussed above. Its job is to store, retrieve and delete secrets using the
KeyStorageManager framework.
The command usage is really simple:
KeyStorageUtility version 1.0 Copyright (C) Alessandro Pilotti 2009 http://www.codeplex.com/KeyStorage info@pilotti.it This is free software, you may use it under the terms of the MIT license <http://www.codeplex.com/KeyStorage/license> USAGE: * To store a key: KeyStorageUtility -s <name> <value> * To retrieve a key value: KeyStorageUtility -r <name> * To delete a key: KeyStorageUtility -d <name>
Well, that's it, by now. For any suggestion, I'll be glad to meet you on the Discussions page!
Alessandro Pilotti