加密解密

.NET Security Blog

http://blogs.msdn.com/b/shawnfa/archive/2004/04/14/generating-a-key-from-a-password.aspx

If you‘re trying to encrypt data using a password, how do you convert the password into a key for symmetric encryption? The easiest way might be to simply convert the password to a byte array, and use this array as your key. However, this is a very bad idea and will lead to an easily cracked system. First of all, for a 256 bit encryption algorithm your passwords would all have to be exactly 32 bytes, or you would end up with not enough bits for the key; or worse, too many bits for the key, meaning that every password that starts with the same eight characters will work to decrypt the data.

In the English language, passwords will probably only contain the characters a-z, A-Z, 0-9, and perhaps some symbols. This means that only 70-75 of the possible 256 bit combinations for each byte will be used. In addition, not every symbol will be used with even frequency. Various estimates for the entropy in a single byte of an English language password vary from 1.3 to 4 bits. This means that in order to generate a good 256 bit key, you‘ll need a password that‘s anywhere from 64 to 197 bytes long!

Fortunately the .Net Framework has provided several ways for you to convert passwords to keys, all of which are much better alternatives than the simple method mentioned above. All of the methods I mention below will always generate the same key given the same set of inputs, so they can be use to effectively create password-based encryption in your code.

CryptDeriveKey

The PasswordDeriveBytes class is available in current releases of the framework, and contains two methods for you to generate keys.

One way of using PasswordDeriveBytes is as a simple wrapper around CAPI‘s CryptDeriveKeyfunction. This is done by calling the appropriately named CryptDeriveKey method of PasswordDeriveBytes. When calling CryptDeriveKey, you‘ll be using the password found in the class constructor, but you‘ll need to pass in the cryptographic algorithm that you‘re generating a key for, the size of the key that you‘d like to use, and the name of the hash algorithm you‘d like to use to generate the key.  When calling CryptDeriveKey, the salt and iteration count that are set on the PasswordDeriveBytes object are not used, so even having different salts and iteration counts will produce the same key given that the rest of the inputs are also the same. (I‘ll discuss the use of the salt and iteration count later.)

Some sample code might clear up the use of CryptDeriveKey.

PasswordDeriveBytes cdk = new PasswordDeriveBytes("P@$$w0rd", null); 

// generate an RC2 key 
byte[] iv = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; 
byte[] key = cdk.CryptDeriveKey("RC2", "SHA1", 128, iv); 
Console.WriteLine(key.Length * 8); 
         
// setup an RC2 object to encrypt with the derived key 
RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider(); 
rc2.Key = key; 
rc2.IV = new byte[] { 21, 22, 23, 24, 25, 26, 27, 28}; 
         
// now encrypt with it 
byte[] plaintext = Encoding.UTF8.GetBytes("Message"); 
MemoryStream ms = new MemoryStream(); 
CryptoStream cs = new CryptoStream(ms, rc2.CreateEncryptor(), CryptoStreamMode.Write) 

cs.Write(plaintext, 0, plaintext.Length); 
cs.Close(); 
byte[] encrypted = ms.ToArray()

 

The sample generates a 128 bit key to use with the RC2 algorithm. This key is generated by using the SHA1 hash algorithm. The IV parameter to CryptDeriveKey is actually an out parameter -- it‘s supposed to represent the IV that you can use with your algorithm. However, current implementations just set this to an array of zeros, so you‘ll need an IV anyway. (Obviously my IV and password aren‘t the strongest in the world.)

I occasionally get asked what algorithms you can pass to CryptDeriveKey. Since CryptDeriveKey is a wrapper around CAPI, you need to pass a parameter that CAPI can understand. This means, it must be a string that CryptoConfig will map to a *CryptoServiceProvider class. (Actually, this is simplified a bit .... any algorithm that has the same OID as a CSP class will work, but for all intents and purposes, stick with the CryptoServiceProvider classes). In v1.1 of the framework, these classes are:

Hash Algorithms
String Implementation
http://www.w3.org/2000/09/xmldsig#sha1 System.Security.Cryptography.SHA1CryptoServiceProvider
MD5 System.Security.Cryptography.MD5CryptoServiceProvider
SHA System.Security.Cryptography.SHA1CryptoServiceProvider
SHA1 System.Security.Cryptography.SHA1CryptoServiceProvider
System.Security.Cryptography.HashAlgorithm System.Security.Cryptography.SHA1CryptoServiceProvider
System.Security.Cryptography.MD5 System.Security.Cryptography.MD5CryptoServiceProvider
System.Security.Cryptography.SHA1 System.Security.Cryptography.SHA1CryptoServiceProvider

Symmetric Algorithms
String Implementation
3DES System.Security.Cryptography.TripleDESCryptoServiceProvider
DES System.Security.Cryptography.DESCryptoServiceProvider
RC2 System.Security.Cryptography.RC2CryptoServiceProvider
System.Security.Cryptography.DES System.Security.Cryptography.DESCryptoServiceProvider
System.Security.Cryptography.RC2 System.Security.Cryptography.RC2CryptoServiceProvider
System.Security.Cryptography.TripleDES System.Security.Cryptography.TripleDESCryptoServiceProvider
Triple DES System.Security.Cryptography.TripleDESCryptoServiceProvider
TripleDES System.Security.Cryptography.TripleDESCryptoServiceProvider

PBKDF1

The other use of PasswordDeriveBytes is as an implementation of the PBKDF1 algorithm, specified in RFC 2898, section 5.1. (PBKDF stands for Password Based Key Derivation Function). PBKDF1 is a pretty simple algorithm:

  1. Concatenate the Password and Salt: R0 = Pwd + Salt
  2. Hash the result Iteration Count times: Rn = Hash(Rn - 1)
  3. The result is the Rn where n = Iteration Count

As you can see, PBKDF1 uses a salt to reduce the risk of a dictionary attack.  Having a large salt will reduce the risk that an attacker can create a list of the output keys for a set of given passwords. Instead of just having to calculate one key, the attacker would have to calculate one key for each salt.  RSA, who developed the algorithm as a part of PKCS #5, recommends a salt of at least 64 bits.  A 64 bit salt would mean the attacker would need to generate 2^64 keys for each password in order to use a dictionary attack.

In addition, an iteration count is required. In general, the larger the iteration count, the stronger the resulting key. Here, RSA recommends a value of at least 1000. PBKDF1 uses either MD4, MD5, or SHA1 as its underlying hash algorithm, although PasswordDeriveBytes will not care if you use another hash algorithm.

In order to generate your key, you call GetBytes passing in the number of bytes you need for a key. One neat thing about the output of GetBytes is that getting two smaller byte arrays is the same as getting one larger one. For instance, two arrays of 20 bytes put together will be the same as a call to GetBytes(40). You can call GetBytes as many times as you need. PBKDF1 will only produce the number of bytes that the hash algorithm generates, but GetBytes will extend the result further, allowing you to get a large number of bytes out of your password.

The following sample is a rewrite of the CryptDeriveKey sample, using PBKDF1. In this case, I‘m using a simple salt, an iteration count of 1000, and the SHA1 hash algorithm. I also use PasswordDeriveBytes to generate an IV for me.

// setup the password generator 
byte[] salt = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; 
PasswordDeriveBytes pwdGen = new PasswordDeriveBytes("P@$$w0rd", salt); 
pwdGen.IterationCount = 1000; 
pwdGen.HashName = "SHA1"; 
         
// generate an RC2 key 
byte[] key = pwdGen.GetBytes(16); 
byte[] iv = pwdGen.GetBytes(8); 
         
// setup an RC2 object to encrypt with the derived key 
RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider(); 
rc2.Key = key; 
rc2.IV = iv; 
         
// now encrypt with it 
byte[] plaintext = Encoding.UTF8.GetBytes("Message"); 
MemoryStream ms = new MemoryStream(); 
CryptoStream cs = new CryptoStream(ms, rc2.CreateEncryptor(), CryptoStreamMode.Write); 
         
cs.Write(plaintext, 0, plaintext.Length); 
cs.Close(); 
byte[] encrypted = ms.ToArray();

 

PBKDF2

With the release of Whidbey, the frameworks will have an implementation of PBKDF2, in the class Rfc2898DeriveBytes. PBKDF2 is also defined in RFC 2898, in section 5.2. The big advantage of PBKDF2 over PBKDF1 is that the output from PBKDF2 is not bounded to the size of a hash algorithm‘s output. Although the .Net implementation of PBKDF1 does not impose this limitation on you, in order to be more secure I‘d recommended that you move to PBKDF2 when you make the move to Whidbey. As you‘ll see, moving from PasswordDeriveBytes to Rfc2898DeriveBytes is trivial since Rfc2898DeriveBytes works in much the same way as PasswordDeriveBytes. You start by setting up a password, salt, and iteration count (PBKDF2 uses HMACSHA1 as an underlying pseudo-random generator). Then you call GetBytes repeatedly until you have enough data to encrypt with. The following code is a rewrite of the PBKDF1 code to use PBKDF2:

// Setup the password generator 
byte[] salt = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }; 
Rfc2898DeriveBytes pwdGen = new Rfc2898DeriveBytes("P@$$w0rd", salt, 1000); 
         
// generate an RC2 key 
byte[] key = pwdGen.GetBytes(16); 
byte[] iv = pwdGen.GetBytes(8); 
         
// setup an RC2 object to encrypt with the derived key 
RC2CryptoServiceProvider rc2 = new RC2CryptoServiceProvider(); 
rc2.Key = key; 
rc2.IV = iv; 
         
// now encrypt with it 
byte[] plaintext = Encoding.UTF8.GetBytes("Message"); 
MemoryStream ms = new MemoryStream(); 
CryptoStream cs = new CryptoStream(ms, rc2.CreateEncryptor(), CryptoStreamMode.Write); 
         
cs.Write(plaintext, 0, plaintext.Length); 
cs.Close(); 
byte[] encrypted = ms.ToArray(); 

 

As you can see, the only change to the code is replacing the PasswordDeriveBytes class with an Rfc2898DeriveBytes class.

Other changes for Whidbey include the ability to set a byte array password (on both PasswordDeriveBytes and Rfc2898DeriveBytes), which increases security by allowing you more control over the password object. (More on that in a future post.)

Hopefully this has provided some useful examples of how to generate a good cryptographic key using a password.

 
 
posted @ 2013-09-23 22:51 DreamCatcherJH 阅读(9) 评论(0) 编辑
 
 
关于作者:

[作者]: JK_Rush从事.NET开发和热衷于开源高性能系统设计,通过博文交流和分享经验,欢迎转载,请保留原文地址,谢谢。 
[出处]: http://www.cnblogs.com/rush/ 
[本文基于]: 署名-非商业性使用 3.0 许可协议发布,欢迎转载,演绎,但是必须保留本文的署名 JK_Rush(包含链接),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系 。

1.1.1 摘要

        相信许多人都使用过.NET提供的加密算法,而且在使用的过程我们必须了解每种加密算法的特点(对称或非对称,密钥长度和初始化向量等等)。我也看到过很多人写过.NET中加密算法总结,但我发现个别存在一些问题,很多人喜欢罗列每种加密算法的具体实现,假设我们要求实现AES和Triple DES加密算法,的确可以很多地分别给出它们的具体实现。

       那我们真的有必要给出每个加密算法的具体实现吗?而且这样的设计不符合OOP设计思想,最重要的是我们要维护多个加密算法啊!OK接下来让我们实行一个可扩展和好维护的加密算法Helper。

 

1.1.2 正文

       加密解密

图1 Hash加密算法继承层次

 

       从上面的继承层次我们可以知道.NET中提供七种Hash加密算法,它们都继承于抽象类HashAlgorithm,而且我们经常使用MD5,SHA1和SHA256等加密算法。下面我们将给出MD5和SHA1的实现。

 

加密解密

图2 对称加密算法继承层次

 

       从上面的继承层次我们可以知道.NET中提供五种对称加密算法,它们都继承于抽象类SymmetricAlgorithm,下面我们将给出它们的通用实现。

 

加密解密

图3 非对称加密算法继承层次

 

       从上面的继承层次我们可以知道.NET中提供四种非对称加密算法,它们都继承于抽象类AsymmetricAlgorithm,下面我们将给出RSA实现。

       除了以上加密算法,.NET还提供了很多其他类型的加密,这里我们主要介绍一些常用的加密算法,如果大家需要了解的话可以查阅MSDN。OK接下来让我们给出Hash加密算法的实现吧。

 

Hash加密算法

      在给出具体的算法实现之前,首先让我们回忆一下什么是Hash加密算法?

      Hash加密是通过使用hash函数对要加密的信息进行加密,然后生成相应的哈希值,那么我们可以定义一个hash()函数,要加密的信息m和加密后的哈希值h。

加密解密

 

      我们对信息m1和m2进行hash加密,就可以获取相应哈希值hash(m1)和hash(m2)。

加密解密

 

       如果信息m1=m2那么,那么将得到同一的哈希地址,但是信息m1!=m2也可能得到同一哈希地址,那么就发生了哈希冲突(collision),在一般的情况下,哈希冲突只能尽可能地减少,而不能完全避免。当发生哈希冲突时,我们要使用冲突解决方法,而主要的冲突解决方法:开放地址法、再哈希法、链地址法和建立一个公共溢出区。

 

加密解密

图4 Hash加密过程(图片来源wiki)

 

       现在让我们来实现通用的hash加密方法。

 

/// <summary>
/// Encrypts the specified hash algorithm.
/// 1. Generates a cryptographic Hash Key for the provided text data.
/// </summary>
/// <param name="hashAlgorithm">The hash algorithm.</param>
/// <param name="dataToHash">The data to hash.</param>
/// <returns></returns>
public static string Encrypt(HashAlgorithm hashAlgorithm, string dataToHash)
{

    var tabStringHex = new string[16];
    var UTF8 = new System.Text.UTF8Encoding();
    byte[] data = UTF8.GetBytes(dataToHash);
    byte[] result = hashAlgorithm.ComputeHash(data);
    var hexResult = new StringBuilder(result.Length);

    for (int i = 0; i < result.Length; i++)
    {
        //// Convert to hexadecimal
        hexResult.Append(result[i].ToString("X2"));
    }
    return hexResult.ToString();
}

 

       上面的加密方法包含一个HashAlgorithm类型的参数,我们可以传递继承于抽象类HashAlgorithm的具体hash算法(MD5,SHA1和SHA256等),通过继承多态性我们使得加密方法更加灵活、简单,最重要的是现在我们只需维护一个通用的加密方法就OK了。

       接着我们要添加判断加密后哈希值是否相等的方法,判断哈希值是否相等的方法IsHashMatch()方法。

 

/// <summary>
/// Determines whether [is hash match] [the specified hash algorithm].
/// </summary>
/// <param name="hashAlgorithm">The hash algorithm.</param>
/// <param name="hashedText">The hashed text.</param>
/// <param name="unhashedText">The unhashed text.</param>
/// <returns>
///   <c>true</c> if [is hash match] [the specified hash algorithm]; 
/// otherwise, <c>false</c>.
/// </returns>
public static bool IsHashMatch(HashAlgorithm hashAlgorithm,
    string hashedText, string unhashedText)
{
    string hashedTextToCompare = Encrypt(
        hashAlgorithm, unhashedText);
    return (String.Compare(hashedText,
        hashedTextToCompare, false) == 0);
}

对称加密算法

       现在我们完成了通用的Hash加密方法了,接下来我们继续介绍对称和非对称算法(具体Demo可以参考这里)。

       在实现对称加密算法之前,先让我们了解一下对称加密的过程,假设我们有一组数据要加密那么我们可以使用一个或一组密钥对数据进行加密解密,但存在一个问题对称加密算法的密钥长度不尽相同,如DES的密钥长度为64 bit,而AES的长度可以为128bit、192bit或256 bit,难道要我们hard code每种算法的密钥长度吗?能不能动态地产生对应算法的密钥呢?

       其实.NET已经提供我们根据不同的对称算法生成对应密钥的方法了,并且把这些方法都封装在PasswordDeriveBytes和Rfc2898DeriveBytes类中。

       首先让我们看一下PasswordDeriveBytes类包含两个方法CryptDeriveKey和GetBytes用来产生对应算法的密钥,现在让我们看一下它们如何产生密钥。

 

CryptDeriveKey:

 

// The sample function.
public void Encrypt()
{
    // The size of the IV property must be the same as the BlockSize property.
    // Due to the RC2 block size is 64 bytes, so iv size also is 64 bytes. 
    var iv = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 };

    var pdb = new PasswordDeriveBytes("pwd", null);

    // Set the encrypted algorithm and export key algorithm.
    // Then get the key base on encrypt algorithm.
    byte[] key = pdb.CryptDeriveKey("RC2", "SHA1", 128, iv);

    Console.WriteLine(key.Length * 8);
    Console.WriteLine(new RC2CryptoServiceProvider().BlockSize);
    // Creates an RC2 object to encrypt with the derived key
    var rc2 = new RC2CryptoServiceProvider
                  {
                      Key = key,
                      IV = new byte[] { 21, 22, 23, 24, 25, 26, 27, 28 }
                  };

    // now encrypt with it
    byte[] plaintext = Encoding.UTF8.GetBytes("NeedToEncryptData");
    using (var ms = new MemoryStream())
    {
        var cs = new CryptoStream(
            ms, rc2.CreateEncryptor(), CryptoStreamMode.Write);

        cs.Write(plaintext, 0, plaintext.Length);
        cs.Close();
        byte[] encrypted = ms.ToArray();
    }
}

       示意例子一:我们使用SHA1哈希算法为RC2加密算法生成128bit的密钥,这样我们就可以根据不同对称加密算法获取相应长度的密钥了,注意我们并没用动态地生成初始化向量iv,这是为了简单起见实际中不应该这样获取初始化向量。

        接下来让我们看一下通过PBKDF1和PBKDF2s算法生成密钥的实现。

 

PBKDF1

        GetBytes:PasswordDeriveBytes的GetBytes()方法实现了PBKDF1(Password Based Key Derivation Function)。

 

PBKDF1算法过程:

1.拼接密钥和盐:R0 = Pwd + Salt

2.哈希加密过程:R1 = Hash(R2-1)

……..

3.哈希加密过程:Rn = Hash(Rn - 1)

4.n是迭代的次数(参考PBKDF1规范请点这里)

 

      现在我们对PBKDF1算法的原理有了初步的了解,接下来我们将通过GetBytes()调用该算法生成密钥。

 

/// <summary>
/// Uses the PBKDF1 to genernate key, 
/// then use it to encrypt plain text.
/// </summary>
public void PBKDF1()
{
    byte[] salt = new byte[] { 8, 7, 6, 5, 4, 3, 2, 1 };

    // Creates an RC2 object to encrypt with the derived key
    var pdb = new PasswordDeriveBytes("pwd", salt) 
    {IterationCount = 23, HashName = "SHA1"};

    // Gets the key and iv.
    byte[] key = pdb.GetBytes(16);
    byte[] iv = pdb.GetBytes(8);

    var rc2 = new RC2CryptoServiceProvider { Key = key, IV = iv };

    byte[] plaintext = Encoding.UTF8.GetBytes("NeedToEncryptData");

    using (var ms = new MemoryStream())
    {
        // Encrypts data.
        var cs = new CryptoStream(
            ms, rc2.CreateEncryptor(), CryptoStreamMode.Write);

        cs.Write(plaintext, 0, plaintext.Length);
        cs.Close();
        byte[] encrypted = ms.ToArray();
    }

}

 

       示意例子二:我们使用PBKDF1算法为RC2加密算法生成128 bit的密钥和64 bit的初始化向量,要注意的是PasswordDeriveBytes的GetBytes()方法已经过时了,而它的替代项就是接下来要介绍的Rfc2898DeriveBytes的GetBytes()方法。

 

PBKDF2

       GetBytes:由于Rfc2898DeriveBytes的GetBytes()方法实现了PBKDF2算法,而且它也替代了PBKDF1过时的GetBytes()方法,所以我们推荐使用Rfc2898DeriveBytes的GetBytes()方法。

 

/// <summary>
/// Uses the PBKDF2 to genernate key, 
/// then use it to encrypt plain text.
/// </summary>
public void PBKDF2()
{
    byte[] salt = new byte[] { 23, 21, 32, 33, 46, 59, 60, 74 };
    var rfc = new Rfc2898DeriveBytes("pwd", salt, 23);

    // generate key and iv.
    byte[] key = rfc.GetBytes(16);
    byte[] iv = rfc.GetBytes(8);

    // Creates an RC2 object to encrypt with the derived key
    var rc2 = new RC2CryptoServiceProvider { Key = key, IV = iv };

    // Encrypts the data.
    byte[] plaintext = Encoding.UTF8.GetBytes("NeedToEncryptData");
    using (var ms = new MemoryStream())
    {
        var cs = new CryptoStream(
            ms, rc2.CreateEncryptor(), CryptoStreamMode.Write);

        cs.Write(plaintext, 0, plaintext.Length);
        cs.Close();
        byte[] encrypted = ms.ToArray();
    }
}

 

       示意例子三:我们发现PBKDF2()方法和之前的PBKDF1()方法没有什么区别,就是无需指定加密密钥的哈希算法(参考PBKDF2规范请点这里)。

      前面通过三种方法来动态的生成加密密钥,而且我们将使用Rfc2898DeriveBytes的GetBytes()方法来获取密钥,那么接下来让我们使用该方法实现通用的对称加密算法吧!

 加密解密

图5 对称算法加密过程

 

      首先我们对加密的平文进行编码,这里默认使用UTF8对平文进行编码,也可以使用其他编码方式,接着使用相应加密算法对编码后的平文进行加密,最后把加密后的Byte数组转换为Base64格式字符串返回。

 

/// <summary>
/// Encrypts with specified symmetric algorithm.
/// Can be Aes, DES, RC2, Rijndael and TripleDES.
/// </summary>
/// <param name="algorithm">The symmertric algorithm (Aes, DES, RC2, Rijndael and TripleDES).</param>
/// <param name="plainText">The plain text need to be encrypted.</param>
/// <param name="key">The secret key to encrypt plain text.</param>
/// <param name="iv">The iv should be 16 bytes.</param>
/// <param name="salt">Salt to encrypt with.</param>
/// <param name="pwdIterations">The number of iterations for plain text.</param>
/// <param name="keySize">Size of the key.</param>
/// <param name="cipherMode">The cipher mode.</param>
/// <param name="paddingMode">The padding mode.</param>
/// <returns></returns>
public static byte[] Encrypt(SymmetricAlgorithm algorithm, byte[] plainText, string key, string iv,
    string salt, int pwdIterations, int keySize, CipherMode cipherMode, PaddingMode paddingMode)
{

    if (null == plainText)
        throw new ArgumentNullException("plainText");
    if (null == algorithm)
        throw new ArgumentNullException("algorithm");
    if (String.IsNullOrEmpty(key))
        throw new ArgumentNullException("key");
    if (String.IsNullOrEmpty(iv))
        throw new ArgumentNullException("iv");
    if (String.IsNullOrEmpty(salt))
        throw new ArgumentNullException("salt");

    // Note the salt should be equal or greater that 64bit (8 byte).
    var rfc = new Rfc2898DeriveBytes(key, salt.ToByteArray(), pwdIterations);
    using (SymmetricAlgorithm symmAlgo = algorithm)
    {
        symmAlgo.Mode = cipherMode;
        //symmAlgo.Padding = paddingMode;
        byte[] cipherTextBytes = null;
        using (var encryptor = symmAlgo.CreateEncryptor(
            rfc.GetBytes(keySize / 8), iv.ToByteArray()))
        {
            using (var ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(
                    ms, encryptor, CryptoStreamMode.Write))
                {
                    cs.Write(plainText, 0, plainText.Length);
                    cs.FlushFinalBlock();
                    cipherTextBytes = ms.ToArray();
                    ms.Close();
                    cs.Close();
                }
            }
            symmAlgo.Clear();
            return cipherTextBytes;
        }
    }
}

 

加密解密

图5 对称算法解密过程

 

        通过上图的解密过程,我们发现解密过程恰恰是加密的反向,首先把Base64格式的密文转换为Byte数组,接着使用对应的解密算法解密密文,最后对解密后的数据进行编码返回平文(默认使用UTF8)。

 

/// <summary>
/// Decrypts the specified algorithm.
/// Can be Aes, DES, RC2, Rijndael and TripleDES.
/// </summary>
/// <param name="algorithm">The symmertric algorithm (Aes, DES, RC2, Rijndael and TripleDES).</param>
/// <param name="cipherText">The cipher text.</param>
/// <param name="key">The secret key to decrypt plain text.</param>
/// <param name="iv">The iv should be 16 bytes.</param>
/// <param name="salt">Salt to decrypt with.</param>
/// <param name="pwdIterations">The number of iterations for plain text.</param>
/// <param name="keySize">Size of the key.</param>
/// <param name="cipherMode">The cipher mode.</param>
/// <param name="paddingMode">The padding mode.</param>
/// <returns></returns>
public static byte[] Decrypt(SymmetricAlgorithm algorithm, byte[] cipherText,
    string key, string iv, string salt, int pwdIterations, int keySize,
    CipherMode cipherMode, PaddingMode paddingMode)
{
    if (null == cipherText)
        throw new ArgumentNullException("cipherText");
    if (null == algorithm)
        throw new ArgumentNullException("algorithm");
    if (String.IsNullOrEmpty(key))
        throw new ArgumentNullException("key");
    if (String.IsNullOrEmpty(iv))
        throw new ArgumentNullException("iv");
    if (String.IsNullOrEmpty(salt))
        throw new ArgumentNullException("salt");

    // Note the salt should be equal or greater that 64bit (8 byte).
    var rfc = new Rfc2898DeriveBytes(key, salt.ToByteArray(), pwdIterations);

    using (SymmetricAlgorithm symmAlgo = algorithm)
    {
        symmAlgo.Mode = cipherMode;
        //symmAlgo.Padding = paddingMode;
        byte[] plainTextBytes = new byte[cipherText.Length];
        int cnt = -1;
        using (var encryptor = symmAlgo.CreateDecryptor(
            rfc.GetBytes(keySize / 8), iv.ToByteArray()))
        {
            using (var ms = new MemoryStream(cipherText))
            {
                using (var cs = new CryptoStream(
                    ms, encryptor, CryptoStreamMode.Read))
                {
                    cnt = cs.Read(plainTextBytes, 0, plainTextBytes.Length);
                    ms.Close();
                    cs.Close();

                }
            }
        }
        symmAlgo.Clear();
        Array.Resize(ref plainTextBytes, cnt);
        return plainTextBytes;
    }
}

      在前面的加密和解密方法,我们通过Rfc2898DeriveBytes获取密码、salt 值和迭代次数,然后通过调用GetBytes方法生成密钥。

       现在我们已经完成了通用的对称加密算法,我们只需一组加密和解密方法就可以随意的使用任意一种对称加密算法了,而不是为每个加密和解密算法编写相应的加密和解密方法。

 

非对称加密算法

      .NET Framework中提供四种非对称加密算法(DSAECDiffieHellman ECDsaRSA),它们都继承于抽象类AsymmetricAlgorithm,接下来我们将提供RSA算法的实现。

      RSA加密算法是一种非对称和双钥加密算法,在公钥加密标准和电子商业中RSA被广泛使用。

      在双钥加密的情况下,密钥有两把,一把是公开的公钥,还有一把是不公开的私钥。

 

双钥加密的原理如下:

a) 公钥和私钥是一一对应的关系,有一把公钥就必然有一把与之对应的、独一无二的私钥,反之亦成立。

b) 所有的(公钥, 私钥)对都是不同的。

c) 用公钥可以解开私钥加密的信息,反之亦成立。

d) 同时生成公钥和私钥应该相对比较容易,但是从公钥推算出私钥,应该是很困难或者是不可能的。

 

       现在的数字签名加密主要是使用RSA算法,什么是数字签名大家请点这里(中文)和这里(英文)。

       现在我们知道RSA算法是使用公钥和密钥进行加密和解密,所以我们先定义一个方法来生成公钥和密钥。

 

/// <summary>
/// Generates the RSA public and private key.
/// </summary>
/// <param name="algorithm">The algorithm to creates key.</param>
/// <returns></returns>
public static void GenerateRSAKey(RSACryptoServiceProvider algorithm)
{
    // Contains public and private key.
    RSAPrivateKey = algorithm.ToXmlString(true);

    using (var streamWriter = new StreamWriter("PublicPrivateKey.xml"))
    {
        streamWriter.Write(RSAPrivateKey);
    }

    // Only contains public key.
    RSAPubicKey = algorithm.ToXmlString(false);
    using (var streamWriter = new StreamWriter("PublicOnlyKey.xml"))
    {
        streamWriter.Write(RSAPubicKey);
    }

}

 

       通过RSACryptoServiceProvider的ToXmlString()方法我们生成了一对公钥和密钥,当参数为true 表示同时包含 RSA 公钥和私钥,反之表示仅包含公钥。

 

/// <summary>
/// Encrypts with the specified RSA algorithm.
/// </summary>
/// <param name="rsa">A RSA object.</param>
/// <param name="plainText">The plain text to decrypt.</param>
/// <param name="key">The key.</param>
/// <param name="encoding">The encoding.</param>
/// <returns></returns>
public static string Encrypt(RSACryptoServiceProvider rsa,
    string plainText, string key, Encoding encoding)
{
    if (null == rsa)
        throw new ArgumentNullException("rsa");
    if (String.IsNullOrEmpty(plainText))
        throw new ArgumentNullException("plainText");
    if (String.IsNullOrEmpty(key))
        throw new ArgumentNullException("key");
    if (null == encoding)
        throw new ArgumentNullException("encoding");

    string publicKey;

    // Reads public key.
    using (var streamReader = new StreamReader("PublicOnlyKey.xml"))
    {
        publicKey = streamReader.ReadToEnd();
    }
    rsa.FromXmlString(publicKey);
    byte[] cipherBytes = rsa.Encrypt(plainText.ToBytesEncoding(encoding), true);
    rsa.Clear();
    return cipherBytes.ToBase64String();
}

 

       接着我们定义RSA的加密方法,首先我们从流中读取密钥和公钥,然后传递给FromXmlString()方法,最后对平文进行加密。

 

/// <summary>
/// Decrypts with the specified RSA algorithm.
/// </summary>
/// <param name="rsa">a RSA object.</param>
/// <param name="cipherText">The cipher text to encrypt.</param>
/// <param name="key">The key.</param>
/// <param name="encoding">The encoding.</param>
/// <returns></returns>
public static string Decrypt(RSACryptoServiceProvider rsa,
    string cipherText, string key, Encoding encoding)
{
    string privateKey;
    // Reads the private key.
    using (var streamReader = new StreamReader("PublicPrivateKey.xml"))
    {
        privateKey = streamReader.ReadToEnd();
    }

    rsa.FromXmlString(privateKey);
    byte[] plainBytes = rsa.Decrypt(cipherText.FromBase64String(), true);
    rsa.Clear();
    return plainBytes.FromByteToString(encoding);
}

 

       参照加密方法我们很快的实现RSA的解密方法,同样我们从流中读取密钥,然后传递给FromXmlString()方法,最后对密文进行解密,注意调用的是RSA算法的Decrypt()方法,在使用胶水代码时千万别忘记修改了。

       现在我们终于完成了通用的加密解密方法,接下来肯定是要测试一下这些方法的效果如何,这次我使用单元测试,如果大家要参考应用程序效果可以点这里

 

[TestMethod]
public void TestStart()
{
    try
    {
        string cipherText;
        string plainText;

        #region Hash Algo

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateHashAlgoMd5(), @"您们好(Hello everyone).");
        Assert.IsTrue(CryptographyUtils.IsHashMatch(
            CryptographyUtils.CreateHashAlgoMd5(),
            cipherText, @"您们好(Hello everyone)."));

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateHashAlgoSHA1(),
            @"您们好(Hello everyone).");
        Assert.IsTrue(CryptographyUtils.IsHashMatch(
            CryptographyUtils.CreateHashAlgoSHA1(),
            cipherText, @"您们好(Hello everyone)."));

        #endregion

        #region Asymm Algo

        CryptographyUtils.GenerateRSAKey(CryptographyUtils.CreateAsymmAlgoRSA());
        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateAsymmAlgoRSA(), @"%dk>JK.RusH", @"c579D-E>?$)_");
        plainText = CryptographyUtils.Decrypt(
            CryptographyUtils.CreateAsymmAlgoRSA(), cipherText, @"c579D-E>?$)_");
        Assert.AreEqual<string>(@"%dk>JK.RusH", plainText);

        #endregion

        #region Symm Algo

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateSymmAlgoAes(),
            "JK_huangJK_huangJK_huang黄钧航",
            "JK_huangJK_huang", 256);

        plainText = CryptographyUtils.Decrypt(
            CryptographyUtils.CreateSymmAlgoAes(),
       cipherText, "JK_huangJK_huang", 256);

        Assert.AreEqual<string>("JK_huangJK_huangJK_huang黄钧航",
            plainText);

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateSymmAlgoDES(),
       "JK_huangJK_huangJK_huang黄钧航",
       "JK_huangJK_huang", 64);

        plainText = CryptographyUtils.Decrypt(
            CryptographyUtils.CreateSymmAlgoDES(),
       cipherText, "JK_huangJK_huang", 64);

        Assert.AreEqual<string>("JK_huangJK_huangJK_huang黄钧航", 
            plainText);

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateSymmAlgoRC2(),
            "JK_huangJK_huangJK_huang黄钧航",
            "JK_huangJK_huang", 128);

        plainText = CryptographyUtils.Decrypt(CryptographyUtils.CreateSymmAlgoRC2(),
       cipherText, "JK_huangJK_huang", 128);

        Assert.AreEqual<string>("JK_huangJK_huangJK_huang黄钧航", plainText);

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateSymmAlgoRijndael(),
            "JK_huangJK_huangJK_huang黄钧航",
            "JK_huangJK_huang", 256);

        plainText = CryptographyUtils.Decrypt(
            CryptographyUtils.CreateSymmAlgoRijndael(),
       cipherText, "JK_huangJK_huang", 256);

        Assert.AreEqual<string>("JK_huangJK_huangJK_huang黄钧航", plainText);

        cipherText = CryptographyUtils.Encrypt(
            CryptographyUtils.CreateSymmAlgoTripleDes(),
            "JK_huangJK_huangJK_huang黄钧航",
            "JK_huangJK_huang", 192);

        plainText = CryptographyUtils.Decrypt(
            CryptographyUtils.CreateSymmAlgoTripleDes(),
       cipherText, "JK_huangJK_huang", 192);

        Assert.AreEqual<string>("JK_huangJK_huangJK_huang黄钧航", plainText);

        #endregion
    }
    catch (Exception ex)
    {
        Debug.Assert(false, ex.Message);
    }
}

 

加密解密

图6 单元测试结果

 

1.1.3 总结

       本文给出了.NET中的一些加密算法的通用实现(哈希加密,对称加密和非对称加密算法),由于加密和解密的方法都是比较固定,而且每中算法有具有其特性,所以这里我们给出了它们的实现,而且我们可以把上的方法封装在一个加密Helper类,这样可以大大提高我们开发效率,而且它充分的利用多态性使得加密算法灵活性和维护性大大提高。

 
posted @ 2013-09-23 21:18 DreamCatcherJH 阅读(11) 评论(0) 编辑
 
 

http://blog.csdn.net/jiht594/article/details/6954155

对称加密算法(加解密密钥相同)

名称

密钥长度

运算速度

安全性

资源消耗

DES

56位

较快

3DES

112位或168位

AES

128、192、256位

 

非对称算法(加密密钥和解密密钥不同)

名称

成熟度

安全性(取决于密钥长度)

运算速度

资源消耗

RSA

DSA

只能用于数字签名

ECC

低(计算量小,存储空间占用小,带宽要求低)

 

散列算法比较

名称

安全性

速度

SHA-1

MD5

 

对称与非对称算法比较

名称

密钥管理

安全性

速度

对称算法

比较难,不适合互联网,一般用于内部系统

快好几个数量级(软件加解密速度至少快100倍,每秒可以加解密数M比特数据),适合大数据量的加解密处理

非对称算法

密钥容易管理

慢,适合小数据量加解密或数据签名

 

算法选择(从性能和安全性综合)

对称加密: AES(128位),

非对称加密: ECC(160位)RSA(1024),

消息摘要: MD5

数字签名:DSA

 

加密解密

上一篇:DateTime季度的计算


下一篇:VS 2008快捷命令