/*
* Olbert.Utilities.CryptorLib
* a simple interface for encrypting and decrypting strings using the Rijndael algorithm
* Copyright (C) 2011 Mark A. Olbert
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace Olbert.Utilities
{
/// <summary>
/// A class of static methods for encrypting and decrypting strings via the Rijndael algorithm.
/// Key and salt management is included. Keys and salts can be generated as needed, or supplied
/// by the user.
/// </summary>
public class Cryptor
{
/// <summary>
/// Gets the required size of the encryption/decryption salt parameter
/// </summary>
public static int SaltSize
{
get
{
RijndaelManaged rijn = new RijndaelManaged();
return rijn.BlockSize / 8;
}
}
/// <summary>
/// Gets the required size of the encryption/decryption key parameter
/// </summary>
public static int KeySize
{
get
{
RijndaelManaged rijn = new RijndaelManaged();
return rijn.KeySize / 8;
}
}
/// <summary>
/// Generates a byte array of random bytes of the specified length
/// </summary>
/// <param name="numBytes">the number of bytes to return</param>
/// <returns>a byte array of random bytes</returns>
public static byte[] GenerateRandomBytes( int numBytes )
{
if( numBytes < 0 )
throw new CryptorLibException("requested less than zero random bytes");
byte[] data = new byte[numBytes];
if( numBytes == 0 ) return data;
RNGCryptoServiceProvider rngSalt = new RNGCryptoServiceProvider();
rngSalt.GetBytes(data);
return data;
}
/// <summary>
/// Tests the supplied string to see if it is a valid salt
/// </summary>
/// <param name="salt">the string to test</param>
/// <returns>true if the supplied string is a valid salt, false otherwise</returns>
public static bool IsValidSalt( string salt )
{
RijndaelManaged rijn = new RijndaelManaged();
EncodedString encString = new EncodedString(rijn.BlockSize / 8);
return (encString.SetText(salt, false) == EncodedString.ConversionResult.Okay);
}
/// <summary>
/// Tests the supplied byte array to see if it is a valid salt
/// </summary>
/// <param name="salt">the byte array to test</param>
/// <returns>true if the supplied byte array is a valid salt, false otherwise</returns>
public static bool IsValidSalt( byte[] salt )
{
RijndaelManaged rijn = new RijndaelManaged();
EncodedString encString = new EncodedString(rijn.BlockSize / 8);
return encString.SetBytes(salt, false);
}
/// <summary>
/// Tests the supplied string to see if it is a valid key
/// </summary>
/// <param name="key">the string to test</param>
/// <returns>true if the supplied string is a valid key, false otherwise</returns>
public static bool IsValidKey( string key )
{
RijndaelManaged rijn = new RijndaelManaged();
EncodedString encString = new EncodedString(rijn.KeySize / 8);
return ( encString.SetText(key, false) == EncodedString.ConversionResult.Okay );
}
/// <summary>
/// Tests the supplied byte array to see if it is a valid key
/// </summary>
/// <param name="key">the byte array to test</param>
/// <returns>true if the supplied byte array is a valid key, false otherwise</returns>
public static bool IsValidKey( byte[] key )
{
RijndaelManaged rijn = new RijndaelManaged();
EncodedString encString = new EncodedString(rijn.KeySize / 8);
return encString.SetBytes(key, false);
}
private RijndaelManaged theRijn = new RijndaelManaged();
private EncodedString encSalt;
private EncodedString encKey;
/// <summary>
/// Initializes an instance with empty salt and key parameters
/// </summary>
public Cryptor()
{
encSalt = new EncodedString(theRijn.BlockSize / 8);
encKey = new EncodedString(theRijn.KeySize / 8);
}
/// <summary>
/// Initializes an instance using the provided salt and key
/// </summary>
/// <param name="salt">the salt to use in encryption/decryption</param>
/// <param name="key">the key to use in encryption/decryption</param>
public Cryptor( string salt, string key )
: this()
{
encSalt.SetText(salt);
encKey.SetText(key);
}
/// <summary>
/// Initializes an instance using the provided salt and key
/// </summary>
/// <param name="salt">the salt to use in encryption/decryption</param>
/// <param name="key">the key to use in encryption/decryption</param>
public Cryptor( byte[] salt, byte[] key )
: this()
{
encSalt.SetBytes(salt);
encKey.SetBytes(key);
}
/// <summary>
/// Gets the current salt for the Rijndael method as an EncodedString, which can be
/// accessed as either an array of bytes or a normal/base64 encoded string
/// </summary>
public EncodedString Salt
{
get { return encSalt; }
}
/// <summary>
/// Gets the current key for the Rijndael method as an EncodedString, which can be
/// accessed as either an array of bytes or a normal/base64 encoded string
/// </summary>
public EncodedString Key
{
get { return encKey; }
}
/// <summary>
/// Generates a new random salt
/// </summary>
public void GenerateSalt()
{
Salt.SetBytes(GenerateRandomBytes(theRijn.BlockSize / 8));
}
/// <summary>
/// Generates a new random key
/// </summary>
public void GenerateKey()
{
theRijn.GenerateKey();
Key.SetBytes(theRijn.Key);
}
/// <summary>
/// Generates a new random salt and key
/// </summary>
public void GenerateSaltAndKey()
{
GenerateSalt();
GenerateKey();
}
/// <summary>
/// Encrypts the provided string using the Rijndael method and the current
/// salt and key values
/// </summary>
/// <param name="input">the string to encrypt</param>
/// <returns>the encrypted string</returns>
public string Encrypt( string input )
{
if( !encSalt.HasEnoughBytes ) throw new CryptorLibException("invalid salt");
if( !encKey.HasEnoughBytes ) throw new CryptorLibException("invalid key");
UTF8Encoding textConverter = new UTF8Encoding();
byte[] toEncrypt = textConverter.GetBytes(input);
ICryptoTransform encryptor = theRijn.CreateEncryptor(encKey.GetBytes(), encSalt.GetBytes());
MemoryStream msEncrypt = new MemoryStream();
CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
// Read the byteData out of the crypto stream.
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
// Convert the byte array into a base64 string.
return Convert.ToBase64String(msEncrypt.ToArray());
}
///// <summary>
///// Encrypts the provided string using Rijndael method and the provided salt and key strings.
///// The supplied salt and key become the new default salt and key values if they are valid.
///// A CryptoLibException is thrown if either the provided salt or key are invalid.
///// </summary>
///// <param name="salt">the salt to use in encrypting the string</param>
///// <param name="key">the key to use in encrypting the string</param>
///// <param name="input">the string to encrypt</param>
///// <returns>the encrypted string</returns>
//public static string Encrypt( string salt, string key, string input )
//{
// EncodedString newSalt = new EncodedString(encSalt.RequiredLength, false);
// newSalt.Text = salt;
// if( !newSalt.HasEnoughBytes )
// throw new CryptorLibException(String.Format("invalid salt value ({0})", newSalt.Status.ToString()));
// encSalt = newSalt;
// EncodedString newKey = new EncodedString(encKey.RequiredLength, false);
// newKey.Text = key;
// if( !newKey.HasEnoughBytes )
// throw new CryptorLibException(String.Format("invalid key value ({0})", newKey.Status.ToString()));
// encKey = newKey;
// return Encrypt(input);
//}
/// <summary>
/// Decrypts the supplied string using the Rijndael method and the current salt and key.
/// </summary>
/// <param name="input">the string to decrypt</param>
/// <returns>the decrypted string</returns>
public string Decrypt( string input )
{
if( !encSalt.HasEnoughBytes ) throw new CryptorLibException("invalid salt");
if( !encKey.HasEnoughBytes ) throw new CryptorLibException("invalid key");
EncodedString encInput = new EncodedString(0);
encInput.SetText(input);
if( !encInput.HasEnoughBytes )
throw new CryptorLibException("unable to parse the supplied input string; it should be base 64 encoded");
ICryptoTransform decryptor = theRijn.CreateDecryptor(encKey.GetBytes(), encSalt.GetBytes());
MemoryStream msDecrypt = new MemoryStream(encInput.GetBytes());
CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
// Read the byteData out of the crypto stream.
byte[] bufDecrypt = new byte[encInput.BytesAvailable];
csDecrypt.Read(bufDecrypt, 0, bufDecrypt.Length);
//Convert the byte array back into a string, removing any padding characters
UTF8Encoding textConverter = new UTF8Encoding();
StringBuilder sb = new StringBuilder(textConverter.GetString(bufDecrypt));
int charIdx = sb.Length - 1;
while( ( charIdx > 0 ) && ( sb[charIdx] == 0x0 ) )
{
charIdx--;
}
if( charIdx < ( sb.Length - 1 ) ) sb.Remove(charIdx + 1, sb.Length - charIdx - 1);
return sb.ToString();
}
///// <summary>
///// Decrypts the provided string using Rijndael method and the provided salt and key strings.
///// The supplied salt and key become the new default salt and key values if they are valid.
///// A CryptoLibException is thrown if either the provided salt or key are invalid.
///// </summary>
///// <param name="salt">the salt to use in decrypting the string</param>
///// <param name="key">the key to use in decrypting the string</param>
///// <param name="input">the string to decrypt</param>
///// <returns>the decrypted string</returns>
//public static string Decrypt( string salt, string key, string input )
//{
// EncodedString newSalt = new EncodedString(encSalt.RequiredLength, false);
// newSalt.Text = salt;
// if( !newSalt.HasEnoughBytes )
// throw new CryptorLibException(String.Format("invalid salt value ({0})", newSalt.Status.ToString()));
// encSalt = newSalt;
// EncodedString newKey = new EncodedString(encKey.RequiredLength, false);
// newKey.Text = key;
// if( !newKey.HasEnoughBytes )
// throw new CryptorLibException(String.Format("invalid key value ({0})", newKey.Status.ToString()));
// encKey = newKey;
// return Decrypt(input);
//}
}
}