State Street Gang
.NET, straight up

A .NET Cryptography Primer, Part Four

May 7, 2008 13:35 by Will

(You can find the previous parts here:  part 1 (background), part 2 (hashes), and part 3 (Symmetric encryption))

Background

This last part in this primer series covers asymmetric encryption, also known as public/private key encryption.  A "public" key, freely distributed, is paired with a "private" key, which is kept secret.  The public key is used to encrypt messages that, without massive computational power, only the paired private key can decrypt.  This is only secure one way--from public key to private key--so two-way encrypted communications usually involve both sender and receiver exchanging public keys in order to encrypt transmissions both ways.

Shared secret (symmetric) encryption is vulnerable to eavesdroppers, decompilers and those with sufficient computational power, each of whom have a relatively simple path to discovering what that secret is.  If that shared secret is sniffed as it is transmitted over the network, or recovered from source code, or cracked using a rainbow table, your data is now open to everyone.  Asymmetric cryptography allows for the easy storage and transmission of the public key without fear of it falling into the wrong hands, as determining the private key is practically impossible.  I'm no expert, but I believe if you started to brute-force crack a 4096 bit key today, you'd finish roughly five seconds after the universe collapses back into a singularity.

Another common use for asymmetric algorithms is to generate signatures.  This is useful for ensuring that data transmitted in cleartext isn't tampered with.  For a given message, you can use your private key to generate a signature.  This is transmitted in cleartext along with the message.  On the receiving end, you can use the public key to verify that the signature was generated by the private key and that it matches the set of bytes.  This ensures that the message is from a trusted source (the owner of the private key) and that the message hasn't been tampered with in a "man in the middle" style attack. 

Implementations

There are two different types of asymmetric algorithms available to .NET coders.  They are RSA and DSA (Digital Signature Algorithm).  Without going into details, the algorithms are essentially the same.  The difference is that DSA can only be used to generate signatures and is legal to export, whereas RSA can be used to perform encryption as well and cannot be legally exported to some countries.  Of course, that is a huge over-generalization; if you're interested in learning more you can always Google it.  For my examples, I'll be using RSA as it can do both and is more commonly used.

Like other cryptographic algorithms in the .NET framework, the RSA implementation is implemented as a provider, the RSACryptoServiceProvider, which extends an abstract base class for all algorithms of this type:  System.Security.Cryptography.RSA.  By using the static Create() method on this base class, you can get the default or a specifically named implementation of the provider.  In my examples I'll be new-ing up instances, as the constructor for the RSACryptoServiceProvider takes a CspParameters object that can be used to configure the provider.  For more information on the different types of RSA CSPs available for Microsoft developers and how to specify which to use, go here.

A word or two thousand about Key Containers

The security of asymmetric algorithms depends on the private key remaining private.  Imagine a software product that uses RSA signatures for a license key.  You compile the public key into the software and use it to verify licenses.  The company generates these a license by taking the "Licensed to" information from a user and generating a signature.  If the private key is stolen or lost, every boxed copy of the software is now trash.  The source code has to be recompiled with a new public key.  And the next software update will require users to re-register their copies of the software.  This is commonly known as "a nightmare."

Unlike shared secret keys (aka passwords), public/private key pairs are complex structures that cannot be memorized.  Because of this, Microsoft stores these keys in what is called a "Key Container".  This is implemented as a file on the hard drive and is secured by ACLs.  Key Containers can be scoped to the machine (look in Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\ MachineKeys) or to the user (Documents and Settings\[USER ACCOUNT]\Application Data\Microsoft\Crypto\RSA).  Machine level keys are accessible by the system and those with configured access rights (by default the principal that created them).  User-scoped containers are, oddly enough, accessible by the system, the user that created them, AND administrators.  Because of this, they can optionally be encrypted with a password.  Another important aspect about user-scoped key containers is that they are stored within the user's profile, which means they are lost when the user account is deleted.  You can have more than one key container of either type, each being identified by its name.

User-scoped Key Containers

User scoped key containers, by default, are only available to the system, admins, and the user that created them.  You can also configure the provider to allow users to apply a password to the container to make them more secure.  I'll be using the machine store for simplicity later, but to scope the container to the user is simple:

// create a configuration object for the provider
CspParameters parms = new CspParameters();
// set the flag to use a user protected key
parms.Flags = CspProviderFlags.UseUserProtectedKey;
// name the key container 
parms.KeyContainerName = "foo";
// Instantiate the provider
RSACryptoServiceProvider rsa 
    = new RSACryptoServiceProvider(parms);

The provider creates a new public/private key pair for you the first time you try to do something that requires it (encrypt, sign, export).  When a new key is created, a dialog is presented to the user that allows the user to set the security level of the key container to high and apply a password to it.  The user can set the security level to High to password protect the key pair.

Initial dialog Set Security Level dialog Create Password Dialog

From this point on, any operation that attempts to access this key will result in a dialog being shown requesting the user's password:

Enter Password Dialog

I'll refrain from the obvious statement of how this could easily be spoofed by a malicious application on the user's system.  If you are thinking about using this as an additional security layer, I'd suggest looking into using the KeyPassword property of the CspParameters object to set this password from within your application's UI.

Creating the RSA Provider

This method shows how to create the default implementation of the RSA crypto provider that uses the machine key store.  If containerName is new, a new key pair is created prior to using or accessing it.  Every example from here on will use this method to create the provider.

private RSACryptoServiceProvider CreateRSA(
    string containerName)
{
    CspParameters parms = new CspParameters();
    parms.KeyContainerName = containerName;
    RSACryptoServiceProvider rsa = 
        new RSACryptoServiceProvider(parms);
    return rsa;
}

Deleting a key from a Key Container

Deleting a key from a key container is a little tricky, as the actual deletion takes place in the set method of the PersistKeyInCsp property, which isn't intuitive.

private void DeleteKeyInContainer
    (string containerName)
{
    // get a provider configured to the container
    RSACryptoServiceProvider rcsp = 
        CreateRSA(containerName);
    // Deletes the key entry from the container
    rcsp.PersistKeyInCsp = false;
    // clears the key from the provider
    rcsp.Clear();
}

Importing and exporting keys

Key pairs can be imported and exported from a key container.  A key pair is actually a complex structure containing the different components the algorithm needs.  This can be exported as a blob (compatible with unmanaged implementations), as an XML string, and as a strut (RSAParameters):

private void ExportImportKeys(string containerName)
{
    // get a provider configured to the container
    RSACryptoServiceProvider rcsp = 
        CreateRSA(containerName);
    // export the key pair 3 different ways
    byte[] keyBlob = rcsp.ExportCspBlob(true);
    string keyXml = rcsp.ToXmlString(true);
    RSAParameters keyParms = 
        rcsp.ExportParameters(true);
    // and import them back in
    rcsp.ImportCspBlob(keyBlob);
    rcsp.FromXmlString(keyXml);
    rcsp.ImportParameters(keyParms);
}

In these export methods, you must pass in a boolean that indicates whether you wish to export the private key as well as the public key (true to export both).  Note that when you bring a private key out of the Key Container you are greatly reducing the security of your application.  If you intend to distribute the public key, you will have to call one of these methods passing in false.  You do not have to use a different import method when importing a public key. 

Encryption and decryption

Remember, you must encrypt with the public key, and decrypt with the private key.  This seems a little backwards, but it is how the algorithm works.  The following code creates an encryptor and decryptor pointing to different key containers (I use a randomly generated Guid for the name).  The public key is extracted from the decryptor and loaded into the encryptor, and a byte array is encrypted and decrypted.  The code after the CollectionAssert demonstrates that a public key cannot be used to decrypt.

// create an encryptor and decryptor
RSACryptoServiceProvider decryptor = 
    CreateRSA(Guid.NewGuid().ToString());
RSACryptoServiceProvider encryptor = 
    CreateRSA(Guid.NewGuid().ToString());
 
// Export the public key from the decryptor
string key = decryptor.ToXmlString(false);
 
// Load the public key into the encryptor
encryptor.FromXmlString(key);
 
byte[] expected = new byte[] { 1, 2, 3 };
 
// encrypt with the public key
byte[] encrypted = encryptor.Encrypt(
    expected, true);
 
// decrypt with the private key
byte[] decrypted = decryptor.Decrypt(
    encrypted, true);
 
CollectionAssert.AreEqual(expected,decrypted);
 
// the following will fail
encryptor.Decrypt(encrypted, true);

Creating and Verifying Signatures

A signature is a cryptographic hash created by a private key that can be verified by its public key.  The public key can be compiled into code and be used to verify signatures that are generated by, say, a trusted employee at your company.  The following code demonstrates generating a signature and verifying it.  The private key is used to generate the signature and the public key is used to verify it.  The last line demonstrates that the public key cannot be used to generate a new license for the tampered data.

RSACryptoServiceProvider signer = 
    CreateRSA(Guid.NewGuid().ToString());
RSACryptoServiceProvider verifier = 
    CreateRSA(Guid.NewGuid().ToString());
 
// Export the public key from the signer
string publicKey = signer.ToXmlString(false);
 
// Load the public key into the verifier
verifier.FromXmlString(publicKey);
 
byte[] rawData = new byte[] { 1, 2, 3 };
 
// encrypt with the public key
byte[] signature = signer.SignData(
    rawData, "SHA1");
 
bool untampered = verifier.VerifyData(
    rawData, "SHA1", signature);
 
// Tamper!
rawData[2] = 4;
 
bool tampered = verifier.VerifyData(
    rawData, "SHA1", signature);
 
Assert.IsTrue(untampered);
Assert.IsFalse(tampered);
 
// the following will fail
signature = verifier.SignData(
    rawData, "SHA1");

Summary

Asymmetric cryptography is used to encrypt and sign data without having to rely on an insecure shared secret.  Key pairs are stored in a Key Container (a file on the hard drive) that can be scoped to the machine or to a specific user.  User-scoped containers can be secured using a password.  The public key is used to encrypt data and verify signatures.  The private key is used to decrypt data and generate signatures

Whew!  That was a long one.  I've learned a bunch writing these four parts, including how much more I don't know about the process.  I would have liked to go into more details about some of the aspects of cryptography on the .NET platform, but the more I say the more I risk being wrong. 

Most likely, if you're reading this, you care about security.  Don't just stop here; if you need cryptography in your program, you need to keep learning.  If you use it and you don't thoroughly understand it, you're probably doing it wrong.  And this is one area where you have to do it right.

kick it on DotNetKicks.com
Tags:
Categories: Cryptography | Security | Tips
Actions: E-mail | Permalink | Comments (5) | Comment RSSRSS comment feed

A NET Cryptography Primer, Part 3

April 16, 2008 12:09 by Will

Part one covered the background, part two went into detail about cryptographic hashes.  Part three covers the most easily understood type of encryption, though you wouldn't know it by its name:  symmetric encryption.

Symmetric Cryptographic Algorithms

Sometimes referred to as "shared secret" cryptography, Symmetric cryptographic algorithms encrypt and decrypt data using a trivial key.  Its name comes from the fact that encryption and decryption both rely upon the same key. 

A common example of symmetric cryptography in action is the common zip file.  You have the option to add a password to the zip file when creating it.  You must, in turn, provide the same password when the file is decrypted.  In order for your secret stash to stay hidden from your mom's eyes, you have to keep the key secret.  For example, you shouldn't write it on a post-it note pasted to your monitor.

Types of Symmetric Algorithms

There are five different types of symmetric algorithms available in System.Security.Cryptography.  They are DES (Data Encryption Standard), TripleDES, RC2, and Rijndael/AES (Advanced Encryption Standard). 

DES and TripleDES

DES is an older standard, originating from government requirements back in the 70's.  Since it was designed for the computing power available at that time, it is considered very weak today.  It can be brute-force cracked in under a day's worth of computation at today's power.  DES has been superceded by both TripleDES, which runs data through the DES encryption algorithm three times (why not QuadDES?  I dunno.), and Rijndael/AES.

RC2

RC2 was a follow-up to DES developed for Lotus Notes with the goal of being more secure but legal for export overseas.  There are also more secure versions with longer keys, but they are all called RC2 in .NET; only the key length is changed (this isn't exactly true; research it if you want more info).  RC2 is most famous for being the encryption used by WEP (with a 40 bit key).  If you're running your wireless access point with WEP, somebody is right now connected to your network chatting up 13 year olds on MySpace.  You should go home immediately, rip out your router by the power cord, and slam it into the wall.  RC2 in its default form is NOT considered secure and should not be used when better alternatives are available.  WEP2, by the way, uses AES for encryption.

Rijndael/AES

Rijndael is considered the most secure of the crop of symmetric algorithms available to .NET programs.  I'm not sure what the differences between them are, but supposedly AES is the official, NSA approved version of Rijndael.  They are both different enough to be implemented separately.  If you have a choice among all symmetric algorithms, you should always use one or the other as they are the most secure and are projected to be secure for another 20 or so years. 

Initialization Vectors and You

Symmetric algorithms introduce a new requirement that must be understood:  The Initialization Vector, or IV.  Very simply, the symmetric algorithm must start somewhere, and that is the job of the IV.  Symmetric algorithms usually use "block chaining" to add an additional layer of randomness to the cypher.  This is done by logically XOR-ing the current block with the previously processed block.  This process is reversible, so it works well and is fast.  However, to fully encrypt the first block of text you need a previous block to XOR it with.  That is the purpose of the IV, to be the starting point of this process.  Because it does impact the rest of the cypher, the same IV must be used to decrypt as was used to encrypt.  Usually, in order to avoid having to memorize a random series of bytes along with your password, the IV is generated from the password itself.

CODE ALREADY!

The pseudocode for encrypting is:

  1. Convert the string to encrypt into a byte array
  2. Generate our IV from our password
  3. Create our encryption provider (Rijndael in this example)
  4. Get an encryptor using our password and initialization vector
  5. Encrypt our string to encrypt
  6. Return the encrypted data as an encoded string

 

The pseudocode for decrypting is:

  1. Decode our encrypted data into a byte array
  2. Create our provider with the same configuration
  3. Generate an IV from the given password
  4. Get a decryptor using our password and IV
  5. Decrypt the encrypted bytes
  6. Convert the byte array into a string

A Word About IV Generation

The current, cryptographically secure, deterministic and non obsolete way to generate our IV from our password is to use the class Rfc2898DeriveBytes.  This is a replacement for the PasswordDeriveBytes class that you'll see in most examples on the net.  This class takes a password and a salt and provides a method called GetBytes which provides an easy way to get your key and IV for use by your symmetric algorithm provider.  If you use the same password and same salt, you are guaranteed to get the same bytes, and these bytes are generated in a secure manner. 

Generally, you don't want to require users to remember a random salt along with their password, so its best to base it off of your password.  In this example, I use the following method, which will generate a salt that's different for each password, but is always the same for the same password:

private byte[] GenerateSalt(string password)
{
    return Encoding.Default.GetBytes(
        "A salt must always be the same"
        + "for Rfc2898DeriveBytes to give you"
        + "the same password bytes and IV for"
        + "each password, or in this case: "
        + password);
}

 

Encrypting

The encryption method demonstrates how to use the Rfc2898DeriveBytes class to generate a key and IV from your password and salt:

private string Encrypt(string password, 
                       string toEncrypt)
{
    Rijndael rinedal = null;
    byte[] toEncBytes = null;
    try
    {
        toEncBytes = Encoding.Default
                             .GetBytes(toEncrypt);
 
        Rfc2898DeriveBytes pdb = new 
            Rfc2898DeriveBytes(
            password,
            GenerateSalt(password));
 
        rinedal = Rijndael.Create();
        rinedal.Padding = PaddingMode.ISO10126;
 
        ICryptoTransform tx = rinedal
            .CreateEncryptor(
            pdb.GetBytes(32), // our key
            pdb.GetBytes(16)); // our IV
        byte[] encrypted = 
            tx.TransformFinalBlock(
            toEncBytes, 
                0, 
                toEncBytes.Length);
 
        return Convert.ToBase64String(encrypted);
    }
    finally
    {
        // this clears out any secret data
        if (rinedal != null)
            rinedal.Clear();
        // zeroes out our array
        if (toEncBytes != null)
            for (int i = 0; i < toEncBytes.Length; 
                i++)
                toEncBytes[i] = 0;
    }
}

 

We use the default system encoding to convert the text to a byte array.  Next, we use our GenerateSalt method, along with the given password, to instantiate our Rfc2898blahblah object.  We get the implementation of the Rijndael algorithm configured on the system next, and specify what type of padding mode to use. 

Symmetric algorithms work on fixed sizes of data, or blocks, so when data doesn't fill up a block the remaining space must be padded somehow.  Default padding modes may differ between systems, so being specific here is better than not.

Next, we get our encryptor from the provider, passing in bytes for our key and initialization vector.  Key and IV lengths must be certain lengths, depending on the flavor of algorithm you are using.  For instance, Rijndael allows for a maximum key length of 256 bits, or 32 bytes.  Consult the docs for your algorithm to find out what sizes are acceptable.

Once encrypted, we convert the encrypted byte array into a string that can be easily stored and retrieved.  And in our finally block we do a little house cleaning, sweeping up all our secret data (or as much as we can do easily). 

Decrypting

The decryption method is just the reverse of the previous:

private string Decrypt(string password, string toDecrypt)
{
    Rijndael rinedal = null;
    byte[] toDecBytes = Convert
                        .FromBase64String(toDecrypt);
    try
    {
        Rfc2898DeriveBytes pdb = new 
            Rfc2898DeriveBytes(
                password, 
                GenerateSalt(password));
 
        rinedal = Rijndael.Create();
        rinedal.Padding = PaddingMode.ISO10126;
 
        ICryptoTransform tx = rinedal
            .CreateDecryptor(
                pdb.GetBytes(32),
                pdb.GetBytes(16));
 
        byte[] decrypted = tx.TransformFinalBlock(
            toDecBytes, 0, toDecBytes.Length);
 
        return Encoding.Default.GetString(decrypted);
    }
    finally
    {
        if (rinedal != null)
            rinedal.Clear();
    }
}

 

Summary

Symmetric cryptographic algorithms use a key, commonly referred to as a password, to encrypt and decrypt data.  There are lots of different versions available, but Rijndael and AES are the current industry standards.  You'll need to generate an initialization vector (IV) using the Rfc2898DeriveBytes object.  The bytes you use as the key and IV must be the correct length for the algorithm you use. 

Part Four Preview

In part four we go over (or maybe just start to go over; its pretty complex) asymmetric cryptographic algorithms.  These use public/private key pairs to perform one-way encrypting and data signing, and is some pretty heavy and useful stuff.  Could somebody else write that one for me, please?  This stuff is getting complex!

kick it on DotNetKicks.com
Tags:
Categories: Cryptography | Security | Tips
Actions: E-mail | Permalink | Comments (7) | Comment RSSRSS comment feed

A NET Cryptography Primer, Part Two

April 11, 2008 11:52 by Will

By now you should better understand managed vs. unmanaged classes in System.Security.Cryptography namespace and how to move from strings to byte arrays and back again.

This post covers storing passwords in a secure manner using salty hash.  Yum.

Cryptographic Hashing

A paragraph or two about cryptographic hashes before covering the secure storage of passwords.  We all know what hash functions are; every object in the .NET platform contains the method GetHashCode(), which returns an integer representation of an object.  A cryptographic hash is an algorithm that takes a string of any length and returns a fixed size, highly random yet deterministic string.  For a cryptographic hash to be considered secure, it must produce the same output for the same string, yet vastly different outputs for two very similar strings.  This makes it easy to verify yet hard to crack.

Probably the most important thing to understand is that secure cryptographic hashes are very difficult, if not impossible, to reverse.  That means, once data passes through the hash algorithm, you cannot reverse the hash function and get the original data back out.  Obviously, this limits the scenarios where cryptographic hashes are useful.  But, because cryptographic hashes are simple, secure, and don't require any kind of "shared secret" to be hidden in the source code, they are widely used.  With the application of the proper patterns, they can be extremely useful for keeping data secure.

Hash Without Salt is Bland

One of the weaknesses of cryptographic hashing is that it is deterministic, meaning that string A will always produce hash B when using the same hash algorithm.  This allows attackers an avenue to crack a hash:  Guess the original string, A.  This weakness is very evident in password storage.  A hacker, with a list of all password hashes in a database, can gain access to accounts using a lookup table.  This table has, for a given hash algorithm, pre-calculated hashes for certain combinations of characters.  Simple ones contain hashes for words in the dictionary.  That seems a bit complex and large, but considering a password of length 8 consisting of the 48 characters readily available to you on your keyboard has approximately 377 million combinations (I hope I got my stats correct), its actually the simplest thing to do.

To combat this, a random string of characters is added to the password, either to the front or the back.  This increases the overall length of the password, making random character lookup tables exponentially more difficult to use.  It also breaks dictionary attacks as, even though the salt is eventually stored in the database along with the hash, you must re-create the dictionary lookup table with each stored salt, a monumental task in and of itself.

I Thought You Were Emphasizing Code

Right.  Now we know what to do, we can generate some pseudocode to identify our password hashing code's inputs and outputs.  To store passwords securely using a cryptographic hash, we must:

  1. Take a user identifier and a password from the user
  2. Create a salt of random characters
  3. Concatenate the given password with the generated salt (in a standardized way)
  4. Use a cryptographic hash algorithm to encrypt this string
  5. Store the encrypted string, the hash, and the unencrypted user identifier

 

To verify a password, we must:

  1. Take a user identifier and a password from the user
  2. Look up the encrypted string and hash using the given user identifier
  3. Combine the retrieved hash and the given password in the standard way
  4. Use the cryptographic hash algorithm to encrypt this string
  5. Compare the newly encrypted string with the one retrieved from the database
  6. If they match, return positive, otherwise return negative

 

The Salt

Salts must be random.  More random that System.Random can provide.  The .NET platform provides a means to create a random enough salt via RNGCryptoServiceProvider.  Here's the code:

private static string CreateSalt(int saltStrength)
{
    byte[] temp = new byte[saltStrength];
    RNGCryptoServiceProvider.Create().GetBytes(temp);
    return Encoding.Default.GetString(bytes);
}

because I'm concatenating the salt to a string, I'm converting the byte array of random bytes into a Unicode string using the system's default code page.  Be careful in this step; if you convert the array into a hex string, for example, you significantly reduce the randomness of the salt (255 possibilities for n characters vs. 16 possibilities for n characters).  Keeping the salt big and encoding it as Unicode (or not encoding it at all) is the best way to go.

The Hash Method

Now that we can generate our salt, we can continue with the hash method.  The only weird thing about this method is that it takes the name of the hash algorithm implementation as a string.  I've included statics for the names of the most common hash algorithms.

public const string SHA1HashName = "SHA1";
public const string SHA256HashName = "SHA256";
public const string SHA512HashName = "SHA512";
public const string MD5HashName = "MD5";
 
public static string HashPassword(string hashName, 
            string password, 
            out string salt, 
            int saltStrength)
{
    HashAlgorithm ha = 
        HashAlgorithm.Create(hashName);
    if (ha == null)
        throw new InvalidOperationException(
            "Cannot map name " + hashName + 
            " to a hash algorithm.");
 
    salt = CreateSalt(saltStrength);
    byte[] temp = Encoding.Default.GetBytes(
                             password + salt);
    temp = ha.ComputeHash(temp);
 
    return Convert.ToBase64String(temp);
}

Pretty simple and straight forward.  This method converts the encrypted byte array into a base64 string for ease of storage.  You could, of course, store it as binary data or encode it differently.  At this point, it mostly doesn't matter; all you're doing is substituting length for complexity, resulting in equally secure crypts.

Note the salt argument is an out parameter.  This is because not only will you store the return value of this method, but also the salt generated.  Not as elegant as a F# tuple, but it works.

The Verify Method

When a user comes to us and claims to be someone known to the system, they give us a user identifier (aka user name) and password as proof.  We can use the following method to compare the two.  Please note, prior to this method being called, you must retrieve the salt and encrypted password from your repository (file, database, etc) for the given user identifier.

public static bool Verify(
    string hashName, 
    string password, 
    string encryptedPassword,
    string salt)
{
    HashAlgorithm ha = 
        HashAlgorithm.Create(hashName);
    if (ha == null)
        throw new InvalidOperationException(
            "Cannot map name " + hashName + 
            " to a hash algorithm.");
 
    byte[] temp = Encoding.Default.GetBytes(
                        password + salt);
    temp = ha.ComputeHash(temp);
 
    string toTest = Convert.ToBase64String(temp);
 
    return encryptedPassword.Equals(
        toTest, 
        StringComparison.Ordinal);
}

This method is very similar to the first, except that we return the result of an Ordinal Equals comparison.  Note that we are converting to a base64 string, just like in the encrypt method.  Also note the comparison is ordinal.  This is because Convert.ToBase64String returns a string encoding that includes only simple ASCII characters, and is not affected by the language or culture that this code is running under.  I think.  I can't guarantee this will pass the Turkey test.  If you're expecting your hashes to run in different cultures, a little more investigation into the encoding is warranted.

Summary

Hashing is a one way business.  And hash is not all that good without a healthy dose of salt.  But a nice salty hash is great for encrypting and storing passwords without having to hard-code a password into your source code, which is dumb.

Part Three Preview

In part three, we will be covering symmetric, or "shared secret", encryption.  Every time a developer hard-codes a password in their source code, God kills a kitten.  Please, think of the kittens!

kick it on DotNetKicks.com
Tags:
Categories: Cryptography | Security | Tips
Actions: E-mail | Permalink | Comments (7) | Comment RSSRSS comment feed

A NET Cryptography Primer, Part One

April 8, 2008 12:29 by Will

This post assumes the reader understands some of the basics of cryptography.  If you don't know the basics, I strongly suggest you read the following first:  cryptographic hashing, asymmetric (public/private key) and symmetric (shared secret) cryptographic algorithms, and Cryptography on MSDN.

Managed vs. Unmanaged

The .NET platform provides a suite of classes in the System.Security.Cryptography namespace that provide for industry standard encryption.  These classes are split into two main groups:  managed and unmanaged class wrappers. 

Managed encryption classes are pure .NET implementations of cryptographic algorithms.  All the code is managed.  They are typically identifiable by the word "Managed" appended to the end of the algorithm class name.  Unmanaged class wrappers are essentially proxies that provide the same interfaces as the managed algorithm classes but they call into CAPI, or the CryptoAPI provided by Windows (advapi32.DLL).  Supposedly, there is no difference between them, and can be used interchangeably.  In fact, cryptographic classes implement the provider model; the specific implementation of a particular algorithm can be changed via configuration files and your code, assuming you use the static Create() method to instantiate an algorithm, would be none the wiser.

The .NET platform also has a number of classes designed to encrypt, sign, and perform other cryptographic functions specifically on XML files.  This post doesn't cover this part of the framework.

Encoding

One of the first things you encounter when using cryptography in .NET is that, while most of the data you wish to encrypt/decrypt/hash are strings, all of the cryptography classes work on arrays of bytes.  This means you are going to have to convert your strings into arrays of bytes to encrypt or hash them, and then convert the resulting encrypted arrays of bytes into strings to make them easily storable, transmittable, and understandable to humans.  It is very important to understand how encoding works; a weak understanding here can result in hard to root out bugs that will drive you wacky down the road.

A side note; I'm not saying that you have to convert your encrypted data into a string; its just the most common scenario.  You could always store the byte arrays in your database as a blob or write them to a binary file.

From String

The most common encoding task in cryptography is to prepare string data for encryption.  Before a string can be encrypted it must be converted into an array of bytes.  This is relatively simple, as strings in .NET are Unicode and the .NET platform provides the correct encoder for the current system through the public static property Encoding.Default.  Simply call the GetBytes() method of this class, passing in your string to convert it to an array of bytes. 

From and Array of Bytes

Converting from an array of encrypted bytes to a string is much more complex.  Bytes can store numbers 0 to 255, so to convert a byte to string you must first chunk the byte array into equal sized portions, then convert these chunks into a fixed number of characters.  This sounds more complicated than necessary; It would seem that, since a byte is a number between 0 and 255, why not just represent it by the number itself?  That is doable, but answer me this:  What is the byte array length for the number-encoded string:  "123"?  It could be three ({1,2,3}), or two ({12,3} or {1,23}), or one ({123}).  There is no way to decode this string into the original byte array.

In order to be able to both encode a byte array into a string and be able to decode it back into the original byte array, each byte must be represented by a fixed number of characters.  It would be simple to encode the above example into the string "001023" and figure out that the original byte array was {1,23}.  However, you notice that we have gone from two bytes, 1 and 23, to six characters, each of which is (up to) 32 bytes.  The encoded string now takes up 192 bytes of memory.  Not exactly efficient.  And when you get to Usenet file sizes (4gb DVD rip -> 4294967296 bytes, encoded at 96 bytes per byte -> 384 gb!!!) you can see where this encoding starts to break down.

The next better alternative to this 3:1 encoding is to do 1:1 encoding.  This means, for each byte 0 to 255, pick a single character to represent that number.  ASCII seems a natural winner for this one, as the extended code page has 255 characters--one for every number!  But ASCII contains control characters, such as tab, newline and beep which make handling ASCII encoded strings troublesome to type or print, and you're still not very efficient.  You can use Encoding.ASCII to convert to and from byte arrays.  Alternatively, you can use one of the Unicode versions of the Encoding class (UTF-7, UTF-8, UTF-32, and Default) to encode the byte array into a string of Unicode characters.  Oddly enough, although UTF-32 is supposedly the most inefficient of the Unicode encodings, in .NET it is almost the most efficient, compressing a 255 byte long array into just 64 Unicode characters at just 5 bytes over ASCII.

Other common encodings are hex (00 - FF) and Base64.  The .NET platform doesn't have a hex conversion class, for some reason, so you have to roll your own, or use these methods:

/// <summary>
/// Creates a hex string from byte array.
/// </summary>
/// <param name="data">The byte data.</param>
/// <returns>A hex string (base16 encoded)
/// </returns>
public static string HexFromByteArray(byte[] data)
{
    return BitConverter
            .ToString(data)
            .Replace("-", "");
}
/// <summary>
/// Creates a byte array from a hex (base16) 
/// encoded string
/// </summary>
/// <param name="data">A hex string (base16 
/// encoded).</param>
/// <returns>A byte array</returns>
public static byte[] HexToByteArray(string data)
{
    byte[] result = new byte[data.Length / 2];
    for (int i = 0; i < result.Length; i++)
    {
        result[i] = byte.Parse(
            data.Substring(i * 2, 2), 
            NumberStyles.HexNumber);
    }
    return result;
}

 

Hex and Base64 encodes are much easier on the eyes than ASCII or Unicode encoded arrays, but they are much more inefficient.  Hex encodes bytes at a 2:1 ratio; Base64 is slightly more efficient.  Both Base64 and Hex encodings are very useful when you need to print out encrypted data or when you need users to enter encrypted data into an application, as both encoding schemes consist solely of characters that can be printed or typed.

Summary

Encryption in .NET requires you to convert strings into byte arrays and vise versa.  It is therefore important that you understand this process and the different ways it is done in order to prevent hard to debug flaws from creeping into your code.  The encoding you choose should also meet your needs for efficiency and usability.

Part Two Preview

Part two will delve into hash algorithms and include sample code for hashing passwords.  Sweet!

kick it on DotNetKicks.com
Tags:
Categories: Cryptography | Security
Actions: E-mail | Permalink | Comments (4) | Comment RSSRSS comment feed