I am having problems with an encryption class that I made:
public static class Encryption {
public static string EncryptToString(string TextToEncrypt, byte[] Key, byte[] IV = null)
{
return ByteArrToString(EncryptStringToBytes(StrToByteArray(TextToEncrypt), Key, IV));
}
public static string EncryptToString(byte[] BytesToEncrypt, byte[] Key, byte[] IV = null)
{
return ByteArrToString(EncryptStringToBytes(BytesToEncrypt, Key, IV));
}
public static byte[] EncryptToBytes(string TextToEncrypt, byte[] Key, byte[] IV = null)
{
return EncryptStringToBytes(StrToByteArray(TextToEncrypt), Key, IV);
}
public static byte[] EncryptToBytes(byte[] BytesToEncrypt, byte[] Key, byte[] IV = null)
{
return EncryptStringToBytes(BytesToEncrypt, Key, IV);
}
public static string DecryptToString(string EncryptedText, byte[] Key,byte[] IV=null)
{
return ByteArrToString(DecryptStringFromBytes(StrToByteArray(EncryptedText), Key, IV));
}
public static string DecryptToString(byte[] EncryptedBytes, byte[] Key,byte[] IV=null)
{
return ByteArrToString(DecryptStringFromBytes(EncryptedBytes, Key, IV));
}
public static byte[] DecryptToBytes(string EncryptedText, byte[] Key,byte[] IV=null)
{
return DecryptStringFromBytes(StrToByteArray(EncryptedText), Key, IV);
}
public static byte[] DecryptToBytes(byte[] EncryptedBytes, byte[] Key,byte[] IV=null)
{
return DecryptStringFromBytes(EncryptedBytes, Key, IV);
}
private static byte[] EncryptStringToBytes(byte[] TextToEncrypt, byte[] Key, byte[] IV=null)
{
Debug.WriteLine("Password: " + ByteArrToString(Key));
Debug.WriteLine("IV: " + ByteArrToString(IV));
byte[] encrypted;
// Create an Rijndael object
// with the specified key and IV.
using (Rijndael rijAlg = Rijndael.Create())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(TextToEncrypt);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
private static byte[] DecryptStringFromBytes(byte[] EncryptedText, byte[] Key, byte[] IV)
{
Debug.WriteLine("Password: " + ByteArrToString(Key));
Debug.WriteLine("IV: " + ByteArrToString(IV));
byte[] fromEncrypt = new byte[EncryptedText.Length];
// Create a Rijndael object with the specified key and IV.
using (Rijndael rijAlg = Rijndael.Create())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(EncryptedText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
//read stream into byte array
csDecrypt.Read(fromEncrypt,0,fromEncrypt.Length);
}
}
}
return fromEncrypt;
}
public static byte[] StrToByteArray(string str)
{
if (str.Length == 0)
throw new Exception("Invalid string value in StrToByteArray");
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
public static string ByteArrToString(byte[] bytes)
{
char[] chars = new char[bytes.Length / sizeof(char)];
System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
return new string(chars);
}
public static byte[] GetIV()
{
byte[] randomArray = new byte[16];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(randomArray);
return randomArray;
}
}
I test it with the following:
byte[] iv = Encryption.GetIV();
byte[] password = Encryption.StrToByteArray("password");
string encrypted = Encryption.EncryptToString("Hello", password, iv);
Debug.WriteLine("Result: " + Encryption.DecryptToString(encrypted, password, iv));
This is the result I get in the debug window:
Password: password
IV: 䴞ㆫ튾輔
Password: password
IV: 䴞ㆫ튾輔
Result: 祓瑳浥䈮瑹孥]
I don't get any errors; just a jibberish result.
I don't know if it's a problem with the initialization vector, the stream, or something else that I'm missing.
I believe there are several issues with this code involving string to byte conversions, total crypto length, etc.
here is a piece of code I have which does effectively the same thing and may get you on your way. I have tested it and it does work as expected.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Security.Cryptography;
using System.IO;
namespace Encryption_test_app
{
class Program
{
static void Main(string[] args)
{
var encoding = new UTF8Encoding(false, true);
var cryptor = new RijndaelEncryptor();
var plainText = "Hello World!";
Debug.Print("Plain Text: [{0}]", plainText);
byte[] cypherBytes = cryptor.Encrypt(encoding.GetBytes(plainText));
string decryptedText = encoding.GetString(cryptor.Decrypt(cypherBytes));
Debug.Print("Decrypted Text: [{0}]", decryptedText);
Debug.Print("PlainText == Decrypted Text: [{0}]", plainText == decryptedText);
}
}
/// <summary>
/// Simple class to encrypt/decrypt a byte array using the <see cref="RijndaelManaged"/> cryptographic algorithm.
/// </summary>
public class RijndaelEncryptor : IDisposable
{
private RijndaelManaged _crypt = new RijndaelManaged();
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RijndaelEncryptor"/> class using a default key and initial vector (IV).
/// </summary>
public RijndaelEncryptor() : this("0nce @pon a time...", "There lived a princess who 1iked frogs...") { }
/// <summary>
/// Initializes a new instance of the <see cref="RijndaelEncryptor"/> class using the plain text key and initial vector
/// which are used to construct encrypted key and IV values using the maximum allowed key and iv sizes for
/// the <see cref="RijndaelEncryptor"/> cryptographic algorithm.
/// </summary>
/// <param name="keyPassword"></param>
/// <param name="ivPassword"></param>
public RijndaelEncryptor(string keyPassword, string ivPassword)
{
if (string.IsNullOrEmpty(keyPassword)) throw new ArgumentOutOfRangeException("keyPassword", "Cannot be null or empty");
if (string.IsNullOrEmpty(ivPassword)) throw new ArgumentOutOfRangeException("ivPassword", "Cannot be null or empty");
KeyPassword = keyPassword;
IVPassword = ivPassword;
_crypt.KeySize = _crypt.LegalKeySizes[0].MaxSize;
EncryptKey = _stringToBytes(KeyPassword, _crypt.KeySize >> 3);
EncryptIV = _stringToBytes(IVPassword, _crypt.BlockSize >> 3);
}
/// <summary>
/// Initializes a new instance of the <see cref="RijndaelEncryptor"/> class using the user supplied key and initial vector arrays.
/// NOTE: these arrays will be validated for use with the <see cref="RijndaelManaged"/> cypher.
/// </summary>
/// <param name="encryptedKey"></param>
/// <param name="encryptedIV"></param>
public RijndaelEncryptor(byte[] encryptedKey, byte[] encryptedIV)
{
if (encryptedKey == null) throw new ArgumentNullException("encryptedKey");
if (encryptedIV == null) throw new ArgumentNullException("encryptedIV");
//Verify encrypted key length is valid for this cryptor algo.
int keylen = encryptedKey.Length << 3;
if (!_crypt.ValidKeySize(keylen))
{
string errmsg = "Encryption key length(" + keylen.ToString() + ") is not for this algorithm:" + _crypt.GetType().Name;
throw new ApplicationException(errmsg);
}
//Verify encrypted iv length is valid for this cryptor algo.
int len = encryptedIV.Length << 3;
if (len != _crypt.BlockSize)
{
string errmsg = "Encryption key length(" + len.ToString() + ") is not for this algorithm:" + _crypt.GetType().Name;
throw new ApplicationException(errmsg);
}
EncryptKey = encryptedKey;
EncryptIV = encryptedIV;
}
#endregion
/// <summary>
/// Plain text encryption key. Is used to generate a encrypted key <see cref="EncryptKey"/>
/// </summary>
public string KeyPassword { get; private set; }
/// <summary>
/// Plain text encryption initial vector. Is used to generate a encrypted IV <see cref="EncryptIV"/>
/// </summary>
public string IVPassword { get; private set; }
/// <summary>
/// Encrypted encryption key. (Size must match one of the allowed sizes for this encryption method).
/// </summary>
public byte[] EncryptKey { get; private set; }
/// <summary>
/// Encrypted encryption IV. (Size must match one of the allowed sizes for this encryption method).
/// </summary>
public byte[] EncryptIV { get; private set; }
/// <summary>
/// Encrypts the given byte array using the defined <see cref="EncryptKey"/> and <see cref="EncryptIV"/> values.
/// </summary>
/// <param name="plaintext"></param>
/// <returns></returns>
public byte[] Encrypt(byte[] plaintext)
{
return(_encrypt(plaintext, EncryptKey, EncryptIV));
}
/// <summary>
/// Decrypts the given byte array using the defined <see cref="EncryptKey"/> and <see cref="EncryptIV"/> values.
/// </summary>
/// <param name="cypherBytes"></param>
/// <returns></returns>
public byte[] Decrypt(byte[] cypherBytes)
{
return (_decrypt(cypherBytes, EncryptKey, EncryptIV));
}
#region Private Encryption methods
/// <summary>
/// Used to encrypt the plain-text key and iv values to not so easy to ready byte arrays of the given size.
/// </summary>
/// <param name="password"></param>
/// <param name="KeyByteSize"></param>
/// <returns></returns>
private byte[] _stringToBytes(string password, int KeyByteSize)
{
byte[] salt = new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0xfe, 0x00, 0xa7, 0xd3, 0x02, 0x02, 0x97, 0xc4, 0xa5, 0x32 };
PasswordDeriveBytes b = new PasswordDeriveBytes(password, salt);
return (b.GetBytes(KeyByteSize));
}
/// <summary>
/// Encrypts the <paramref name="plainBytes"/> array using the given key and initial vector.
/// </summary>
/// <remarks>
/// This routine embeds the length of the plain data at the beginning of the encrypted record. This would be
/// frowed apon by crypto experts. However, if you dont do this you may get extraneous data (extra null bytes)
/// at the end of the decrypted byte array. This embedded length is used to trim the final decrypted array to size.
/// </remarks>
/// <param name="plainBytes"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] _encrypt(byte[] plainBytes, byte[] key, byte[] iv)
{
try
{
// Create a MemoryStream.
using (MemoryStream mStream = new MemoryStream())
{
// Create a CryptoStream using the MemoryStream
// and the passed key and initialization vector (IV).
using (CryptoStream cStream = new CryptoStream(mStream, _crypt.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
// Write the byte array to the crypto stream and flush it.
byte[] recordLen = BitConverter.GetBytes(plainBytes.Length);
cStream.Write(recordLen, 0, recordLen.Length);
cStream.Write(plainBytes, 0, plainBytes.Length);
if (!cStream.HasFlushedFinalBlock)
{
cStream.FlushFinalBlock();
}
// Get an array of bytes from the
// MemoryStream that holds the
// encrypted data.
return(mStream.ToArray());
}
}
}
catch (CryptographicException ex)
{
throw new ApplicationException("**ERROR** occurred during Encryption", ex);
}
}
/// <summary>
/// Decrypts the <paramref name="cryptBytes"/> array using the given key and initial vector.
/// </summary>
/// <param name="plainBytes"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] _decrypt(byte[] cryptBytes, byte[] key, byte[] iv)
{
try
{
// Create a new MemoryStream using the passed
// array of encrypted data.
using (MemoryStream msDecrypt = new MemoryStream(cryptBytes))
{
// Create a CryptoStream using the MemoryStream
// and the passed key and initialization vector (IV).
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, _crypt.CreateDecryptor(key, iv), CryptoStreamMode.Read))
{
byte[] recordLen = BitConverter.GetBytes((int)0);
csDecrypt.Read(recordLen, 0, recordLen.Length);
int length = BitConverter.ToInt32(recordLen, 0);
// Create buffer to hold the decrypted data.
byte[] fromEncrypt = new byte[cryptBytes.Length - recordLen.Length];
// Read the decrypted data out of the crypto stream
// and place it into the temporary buffer.
csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
byte[] plainBytes = new byte[length];
Array.Copy(fromEncrypt, plainBytes, length);
return (plainBytes);
}
}
}
catch (CryptographicException ex)
{
throw new ApplicationException("**ERROR** occurred during Decryption", ex);
}
}
#endregion
#region IDisposable Members
private bool disposed = false; //indicates if this instance has been disposed.
private void Dispose(bool disposing)
{
if (!this.disposed)
{
//Dispose managed objects
if (disposing)
{
if (_crypt != null)
{
try { _crypt.Clear(); }
finally { _crypt = null; }
}
}
//Dispose Unmanaged objects
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~RijndaelEncryptor() { Dispose(false); }
#endregion
}
}