.NET MVC4 SimpleMembershipProvider - Overriding Password Encryption/Storage? - c#

I've got a production application that I'm looking to re-build (ground up) on MVC4. Usage of the SimpleMembershipProvider for authentication and authorization seems to be very suitable for my needs, except for one thing: password encryption.
The current production version of the application has a custom MembershipProvider that encrypted passwords and stored them by generating a salt, hashing the password with the salt (SHA256) and then storing the salt as the first X characters of the database-stored password:
MyApp.Security.MyAppMembershipProvider : System.Web.Security.MembershipProvider:
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) {
// ...
u.Email = email.ToLower();
string salt = GenerateSalt();
u.Password = salt + Helper.FormatPassword(salt, password, this.PasswordFormat);
u.FirstName = String.Empty;
u.LastName = String.Empty;
// ...
}
As I convert the application over to MVC4, the obvious issue is that I want my users' old passwords to continue to authenticate them. I'm willing to migrate to a new data schema, but legacy authentication information will need to continue to work.
My question is, is it possible to override the same way with SimpleMembershipProvider? Will I have to use an implementation of ExtendedMembershipProvider? Or, fingers crossed, is there some voodoo easy way I can do this without creating a custom membership provider altogether?
Thanks!

What you are looking for is to implement your own ExtendedMembershipProvider. There doesn't appear to be any way to interfere with the SimpleMembershipProvider's encryption method, so you need to write your own (such as PBKDF2). I chose to store the salt along with the PBKDF2 iterations in the PasswordSalt column of webpages_Membership, and that way you can increase this value later on when computers get faster and upgrade your old passwords on the fly.
Such a template example might look like:
using WebMatrix.Data;
using WebMatrix.WebData;
using SimpleCrypto;
public class CustomAuthenticationProvider : ExtendedMembershipProvider
{
private string applicationName = "CustomAuthenticationProvider";
private string connectionString = "";
private int HashIterations = 10000;
private int SaltSize = 64;
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
try
{
if (config["connectionStringName"] != null)
this.connectionString = ConfigurationManager.ConnectionStrings[config["connectionStringName"]].ConnectionString;
}
catch (Exception ex)
{
throw new Exception(String.Format("Connection string '{0}' was not found.", config["connectionStringName"]));
}
if (config["applicationName"] != null)
this.connectionString = ConfigurationManager.ConnectionStrings[config["applicationName"]].ConnectionString;
base.Initialize(name, config);
}
public override bool ConfirmAccount(string accountConfirmationToken)
{
return true;
}
public override bool ConfirmAccount(string userName, string accountConfirmationToken)
{
return true;
}
public override string CreateAccount(string userName, string password, bool requireConfirmationToken)
{
throw new NotImplementedException();
}
public override string CreateUserAndAccount(string userName, string password, bool requireConfirmation, IDictionary<string, object> values)
{
// Hash the password using our currently configured salt size and hash iterations
PBKDF2 crypto = new PBKDF2();
crypto.HashIterations = HashIterations;
crypto.SaltSize = SaltSize;
string hash = crypto.Compute(password);
string salt = crypto.Salt;
using (SqlConnection con = new SqlConnection(this.connectionString))
{
con.Open();
int userId = 0;
// Create the account in UserProfile
using (SqlCommand sqlCmd = new SqlCommand("INSERT INTO UserProfile (UserName) VALUES(#UserName); SELECT CAST(SCOPE_IDENTITY() AS INT);", con))
{
sqlCmd.Parameters.AddWithValue("UserName", userName);
object ouserId = sqlCmd.ExecuteScalar();
if (ouserId != null)
userId = (int)ouserId;
}
// Create the membership account and associate the password information
using (SqlCommand sqlCmd = new SqlCommand("INSERT INTO webpages_Membership (UserId, CreateDate, Password, PasswordSalt) VALUES(#UserId, GETDATE(), #Password, #PasswordSalt);", con))
{
sqlCmd.Parameters.AddWithValue("UserId", userId);
sqlCmd.Parameters.AddWithValue("Password", hash);
sqlCmd.Parameters.AddWithValue("PasswordSalt", salt);
sqlCmd.ExecuteScalar();
}
con.Close();
}
return "";
}
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
// Hash the password using our currently configured salt size and hash iterations
PBKDF2 crypto = new PBKDF2();
crypto.HashIterations = HashIterations;
crypto.SaltSize = SaltSize;
string oldHash = crypto.Compute(oldPassword);
string salt = crypto.Salt;
string newHash = crypto.Compute(oldPassword);
using (SqlConnection con = new SqlConnection(this.connectionString))
{
con.Open();
con.Close();
}
return true;
}
public override bool ValidateUser(string username, string password)
{
bool validCredentials = false;
bool rehashPasswordNeeded = false;
DataTable userTable = new DataTable();
// Grab the hashed password from the database
using (SqlConnection con = new SqlConnection(this.connectionString))
{
con.Open();
using (SqlCommand sqlCmd = new SqlCommand("SELECT m.Password, m.PasswordSalt FROM webpages_Membership m INNER JOIN UserProfile p ON p.UserId=m.UserId WHERE p.UserName=#UserName;", con))
{
sqlCmd.Parameters.AddWithValue("UserName", username);
using (SqlDataAdapter adapter = new SqlDataAdapter(sqlCmd))
{
adapter.Fill(userTable);
}
}
con.Close();
}
// If a username match was found, check the hashed password against the cleartext one provided
if (userTable.Rows.Count > 0)
{
DataRow row = userTable.Rows[0];
// Hash the cleartext password using the salt and iterations provided in the database
PBKDF2 crypto = new PBKDF2();
string hashedPassword = row["Password"].ToString();
string dbHashedPassword = crypto.Compute(password, row["PasswordSalt"].ToString());
// Check if the hashes match
if (hashedPassword.Equals(dbHashedPassword))
validCredentials = true;
// Check if the salt size or hash iterations is different than the current configuration
if (crypto.SaltSize != this.SaltSize || crypto.HashIterations != this.HashIterations)
rehashPasswordNeeded = true;
}
if (rehashPasswordNeeded)
{
// rehash and update the password in the database to match the new requirements.
// todo: update database with new password
}
return validCredentials;
}
}
And the encryption class as follows (in my case I used a PBKDF2 encryption wrapper called SimpleCrypto):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace SimpleCrypto
{
/// <summary>
///
/// </summary>
public class PBKDF2 : ICryptoService
{
/// <summary>
/// Initializes a new instance of the <see cref="PBKDF2"/> class.
/// </summary>
public PBKDF2()
{
//Set default salt size and hashiterations
HashIterations = 100000;
SaltSize = 34;
}
/// <summary>
/// Gets or sets the number of iterations the hash will go through
/// </summary>
public int HashIterations
{ get; set; }
/// <summary>
/// Gets or sets the size of salt that will be generated if no Salt was set
/// </summary>
public int SaltSize
{ get; set; }
/// <summary>
/// Gets or sets the plain text to be hashed
/// </summary>
public string PlainText
{ get; set; }
/// <summary>
/// Gets the base 64 encoded string of the hashed PlainText
/// </summary>
public string HashedText
{ get; private set; }
/// <summary>
/// Gets or sets the salt that will be used in computing the HashedText. This contains both Salt and HashIterations.
/// </summary>
public string Salt
{ get; set; }
/// <summary>
/// Compute the hash
/// </summary>
/// <returns>
/// the computed hash: HashedText
/// </returns>
/// <exception cref="System.InvalidOperationException">PlainText cannot be empty</exception>
public string Compute()
{
if (string.IsNullOrEmpty(PlainText)) throw new InvalidOperationException("PlainText cannot be empty");
//if there is no salt, generate one
if (string.IsNullOrEmpty(Salt))
GenerateSalt();
HashedText = calculateHash(HashIterations);
return HashedText;
}
/// <summary>
/// Compute the hash using default generated salt. Will Generate a salt if non was assigned
/// </summary>
/// <param name="textToHash"></param>
/// <returns></returns>
public string Compute(string textToHash)
{
PlainText = textToHash;
//compute the hash
Compute();
return HashedText;
}
/// <summary>
/// Compute the hash that will also generate a salt from parameters
/// </summary>
/// <param name="textToHash">The text to be hashed</param>
/// <param name="saltSize">The size of the salt to be generated</param>
/// <param name="hashIterations"></param>
/// <returns>
/// the computed hash: HashedText
/// </returns>
public string Compute(string textToHash, int saltSize, int hashIterations)
{
PlainText = textToHash;
//generate the salt
GenerateSalt(hashIterations, saltSize);
//compute the hash
Compute();
return HashedText;
}
/// <summary>
/// Compute the hash that will utilize the passed salt
/// </summary>
/// <param name="textToHash">The text to be hashed</param>
/// <param name="salt">The salt to be used in the computation</param>
/// <returns>
/// the computed hash: HashedText
/// </returns>
public string Compute(string textToHash, string salt)
{
PlainText = textToHash;
Salt = salt;
//expand the salt
expandSalt();
Compute();
return HashedText;
}
/// <summary>
/// Generates a salt with default salt size and iterations
/// </summary>
/// <returns>
/// the generated salt
/// </returns>
/// <exception cref="System.InvalidOperationException"></exception>
public string GenerateSalt()
{
if (SaltSize < 1) throw new InvalidOperationException(string.Format("Cannot generate a salt of size {0}, use a value greater than 1, recommended: 16", SaltSize));
var rand = RandomNumberGenerator.Create();
var ret = new byte[SaltSize];
rand.GetBytes(ret);
//assign the generated salt in the format of {iterations}.{salt}
Salt = string.Format("{0}.{1}", HashIterations, Convert.ToBase64String(ret));
return Salt;
}
/// <summary>
/// Generates a salt
/// </summary>
/// <param name="hashIterations">the hash iterations to add to the salt</param>
/// <param name="saltSize">the size of the salt</param>
/// <returns>
/// the generated salt
/// </returns>
public string GenerateSalt(int hashIterations, int saltSize)
{
HashIterations = hashIterations;
SaltSize = saltSize;
return GenerateSalt();
}
/// <summary>
/// Get the time in milliseconds it takes to complete the hash for the iterations
/// </summary>
/// <param name="iteration"></param>
/// <returns></returns>
public int GetElapsedTimeForIteration(int iteration)
{
var sw = new Stopwatch();
sw.Start();
calculateHash(iteration);
return (int)sw.ElapsedMilliseconds;
}
private string calculateHash(int iteration)
{
//convert the salt into a byte array
byte[] saltBytes = Encoding.UTF8.GetBytes(Salt);
using (var pbkdf2 = new Rfc2898DeriveBytes(PlainText, saltBytes, iteration))
{
var key = pbkdf2.GetBytes(64);
return Convert.ToBase64String(key);
}
}
private void expandSalt()
{
try
{
//get the position of the . that splits the string
var i = Salt.IndexOf('.');
//Get the hash iteration from the first index
HashIterations = int.Parse(Salt.Substring(0, i), System.Globalization.NumberStyles.Number);
}
catch (Exception)
{
throw new FormatException("The salt was not in an expected format of {int}.{string}");
}
}
}
}
and it wouldn't be complete without the interface:
public interface ICryptoService
{
/// <summary>
/// Gets or sets the number of iterations the hash will go through
/// </summary>
int HashIterations { get; set; }
/// <summary>
/// Gets or sets the size of salt that will be generated if no Salt was set
/// </summary>
int SaltSize { get; set; }
/// <summary>
/// Gets or sets the plain text to be hashed
/// </summary>
string PlainText { get; set; }
/// <summary>
/// Gets the base 64 encoded string of the hashed PlainText
/// </summary>
string HashedText { get; }
/// <summary>
/// Gets or sets the salt that will be used in computing the HashedText. This contains both Salt and HashIterations.
/// </summary>
string Salt { get; set; }
/// <summary>
/// Compute the hash
/// </summary>
/// <returns>the computed hash: HashedText</returns>
string Compute();
/// <summary>
/// Compute the hash using default generated salt. Will Generate a salt if non was assigned
/// </summary>
/// <param name="textToHash"></param>
/// <returns></returns>
string Compute(string textToHash);
/// <summary>
/// Compute the hash that will also generate a salt from parameters
/// </summary>
/// <param name="textToHash">The text to be hashed</param>
/// <param name="saltSize">The size of the salt to be generated</param>
/// <param name="hashIterations"></param>
/// <returns>the computed hash: HashedText</returns>
string Compute(string textToHash, int saltSize, int hashIterations);
/// <summary>
/// Compute the hash that will utilize the passed salt
/// </summary>
/// <param name="textToHash">The text to be hashed</param>
/// <param name="salt">The salt to be used in the computation</param>
/// <returns>the computed hash: HashedText</returns>
string Compute(string textToHash, string salt);
/// <summary>
/// Generates a salt with default salt size and iterations
/// </summary>
/// <returns>the generated salt</returns>
string GenerateSalt();
/// <summary>
/// Generates a salt
/// </summary>
/// <param name="hashIterations">the hash iterations to add to the salt</param>
/// <param name="saltSize">the size of the salt</param>
/// <returns>the generated salt</returns>
string GenerateSalt(int hashIterations, int saltSize);
/// <summary>
/// Get the time in milliseconds it takes to complete the hash for the iterations
/// </summary>
/// <param name="iteration"></param>
/// <returns></returns>
int GetElapsedTimeForIteration(int iteration);
}

I think I'm going to go a slightly different route after all:
http://pretzelsteelersfan.blogspot.com/2012/11/migrating-legacy-apps-to-new.html
Basically, migrating legacy user data as-is to the UserProfile table and creating a class to validate credentials against the old algorithm if the SimpleMembership validation fails. If legacy validation succeeds, updating password to new algorithm via WebSecurity.ResetToken to modernize it.
Thanks for the help.

Related

Read XML node using reflection c#

I have this function to read xml values and create an instance of a class.
Is there a way to combine linq and reflection to not specify the properties of the class and create the class with less code lines?
I try to avoid to modify this method if I add or remove some fields to Test_Class
/// <summary>
/// Get the values of a xml node and return a class of type Class_Test
/// </summary>
/// <param name="sXmlFileName">Path to xml file.</param>
/// <param name="sNodeName">Name of node to get values.</param>
/// <returns>Class_Test New class with the values set from XML.</returns>
public static Class_Test Func_ReadXMLNode(string sXmlFileName, string sNodeName)
{
//Load XML
XDocument xml_Document;
Class_Test result;
try
{
xml_Document = XDocument.Load(sXmlFileName);
//Read XML Section
var xmlValues = from r in xml_Document.Descendants(sNodeName)
select new Class_Test
{
sNetworkInterfaceName = Convert.ToString(r.Element("").Value),
iNetworkInterfacePort = Convert.ToInt32(r.Element("").Value),
sCertificateFile = Convert.ToString(r.Element("").Value),
sCertificateName = Convert.ToString(r.Element("").Value),
iKeepAliveSendingTime = Convert.ToInt32(r.Element("").Value),
iMaximumTimeWithoutKeepAlive = Convert.ToInt32(r.Element("").Value),
sSqlServer = Convert.ToString(r.Element("").Value),
sDatabase = Convert.ToString(r.Element("").Value),
iFtpRetries = Convert.ToInt32(r.Element("").Value),
sDetectionFilesDirectory = Convert.ToString(r.Element("").Value),
sImgDirectory = Convert.ToString(r.Element("").Value),
sLocalDirectory = Convert.ToString(r.Element("").Value),
sOffenceDirectory = Convert.ToString(r.Element("").Value),
sTmpDirectory = Convert.ToString(r.Element("").Value)
};
result = xmlValues.FirstOrDefault();
}
catch (IOException Exception1)
{
LogHelper.Func_WriteEventInLogFile(DateTime.Now.ToLocalTime(), enum_EventTypes.Error, "Func_ReadXMLConfig()", "Source = " + Exception1.Source.Replace("'", "''") + ", Message = " + Exception1.Message.Replace("'", "''"));
result = new Class_Test();
}
return result;
}
And this is Class_Text:
/// <summary>
/// Class of operation mode.
/// </summary>
public class Class_OperationMode
{
#region CONFIG_PARAMETERS
/// <summary>
/// Name of the network interface.
/// </summary>
public string sNetworkInterfaceName;
/// <summary>
/// Port of the network interface.
/// </summary>
public int iNetworkInterfacePort;
/// <summary>
/// Path to certificate file.
/// </summary>
public string sCertificateFile;
/// <summary>
/// Name of the certificate.
/// </summary>
public string sCertificateName;
/// <summary>
/// Time to keep alive the connection while sending data.
/// </summary>
public int iKeepAliveSendingTime;
/// <summary>
/// Time before timeout of the connection.
/// </summary>
public int iMaximumTimeWithoutKeepAlive;
/// <summary>
/// Database server instance.
/// </summary>
public string sSqlServer;
/// <summary>
/// Path to .mdf file of database.
/// </summary>
public string sDatabase;
/// <summary>
/// Max retries to try to connect to FTP Server.
/// </summary>
public int iFtpRetries;
/// <summary>
/// Path to detections files directory.
/// </summary>
public string sDetectionFilesDirectory;
/// <summary>
/// Path to images directory.
/// </summary>
public string sImgDirectory;
/// <summary>
/// Path to local directory.
/// </summary>
public string sLocalDirectory;
/// <summary>
/// Path to folder where save and retrieve offences.
/// </summary>
public string sOffenceDirectory;
/// <summary>
/// Path to temp directory.
/// </summary>
public string sTmpDirectory;
#endregion
UPDATE: Thanks to comments, I updated code to:
public static Class_Test Func_ReadXMLNode(string sXmlFileName, string sNodeName){
//Load XML
XDocument xml_Document;
Class_Test result;
try
{
xml_Document = XDocument.Load(sXmlFileName);
//Read XML Section
//Get xml values of descendants
XElement xmlValues = xml_Document.Descendants(sNodeName).FirstOrDefault();
//Create serializer
XmlSerializer serializer = new XmlSerializer(typeof(Class_Test));
//Deserialize
using (XmlReader reader = xmlValues.CreateReader())
{
result = (Class_Test)serializer.Deserialize(reader);
}
}
catch (IOException Exception1)
{
LogHelper.Func_WriteEventInLogFile(DateTime.Now.ToLocalTime(), enum_EventTypes.Error, "Func_ReadXMLConfig()", "Source = " + Exception1.Source.Replace("'", "''") + ", Message = " + Exception1.Message.Replace("'", "''"));
result = new Class_Test();
}
return result;
}
Thanks in advance. Best regards,
JoaquĆ­n
I think what youre looking for is serialization/deserialization. Theres lots of useful stuff in .net for handling xml serialization. Ive stolen this example from the docs (link underneath).
XmlSerializer serializer = new
XmlSerializer(typeof(OrderedItem));
// A FileStream is needed to read the XML document.
FileStream fs = new FileStream(filename, FileMode.Open);
XmlReader reader = XmlReader.Create(fs);
// Declare an object variable of the type to be deserialized.
OrderedItem i;
// Use the Deserialize method to restore the object's state.
i = (OrderedItem)serializer.Deserialize(reader);
fs.Close();
From https://msdn.microsoft.com/en-us/library/tz8csy73(v=vs.110).aspx

Identity Framework and Custom Password Hasher

I have added identity framework to my WebApi and followed the steps outlined here:
http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/
All of this is working fine.
The problem I have, is that my client has another system of which the API integrates with (to collect data) and that has it's own login methods. So, with that in mind, my client has asked me to use a CustomPasswordHasher to encrypt and decrypt passwords.
What they would like to do is be able to get a password hash and convert it into the actual password so that they can use it to log into the old system (both passwords / accounts will be the same).
I know this is very unothadox but I have no choice in the matter.
My question is, how easy is this to do?
I have found a few topics on how to create a custom password hasher, but none show me how to actually get the password from the hashed password, they only show how to compare.
Currently I have this:
public class PasswordHasher : IPasswordHasher
{
private readonly int _saltSize;
private readonly int _bytesRequired;
private readonly int _iterations;
public PasswordHasher()
{
this._saltSize = 128 / 8;
this._bytesRequired = 32;
this._iterations = 1000;
}
public string HashPassword(string password)
{
// Create our defaults
var array = new byte[1 + this._saltSize + this._bytesRequired];
// Try to hash our password
using (var pbkdf2 = new Rfc2898DeriveBytes(password, this._saltSize, this._iterations))
{
var salt = pbkdf2.Salt;
Buffer.BlockCopy(salt, 0, array, 1, this._saltSize);
var bytes = pbkdf2.GetBytes(this._bytesRequired);
Buffer.BlockCopy(bytes, 0, array, this._saltSize + 1, this._bytesRequired);
}
// Return the password base64 encoded
return Convert.ToBase64String(array);
}
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
// Throw an error if any of our passwords are null
ThrowIf.ArgumentIsNull(() => hashedPassword, () => providedPassword);
// Get our decoded hash
var decodedHashedPassword = Convert.FromBase64String(hashedPassword);
// If our password length is 0, return an error
if (decodedHashedPassword.Length == 0)
return PasswordVerificationResult.Failed;
var t = decodedHashedPassword[0];
// Do a switch
switch (decodedHashedPassword[0])
{
case 0x00:
return PasswordVerificationResult.Success;
default:
return PasswordVerificationResult.Failed;
}
}
private bool VerifyHashedPassword(byte[] hashedPassword, string password)
{
// If we are not matching the original byte length, then we do not match
if (hashedPassword.Length != 1 + this._saltSize + this._bytesRequired)
return false;
//// Get our salt
//var salt = pbkdf2.Salt;
//Buffer.BlockCopy(salt, 0, array, 1, this._saltSize);
//var bytes = pbkdf2.GetBytes(this._bytesRequired);
//Buffer.BlockCopy(bytes, 0, array, this._saltSize + 1, this._bytesRequired);
return true;
}
}
If I really wanted to I could just do this:
public class PasswordHasher : IPasswordHasher
{
public string HashPassword(string password)
{
// Do no hashing
return password;
}
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
// Throw an error if any of our passwords are null
ThrowIf.ArgumentIsNull(() => hashedPassword, () => providedPassword);
// Just check if the two values are the same
if (hashedPassword.Equals(providedPassword))
return PasswordVerificationResult.Success;
// Fallback
return PasswordVerificationResult.Failed;
}
}
but that would be crazy, because all the passwords would be stored as plain text. Surely there is a way to "encrypt" the password and "decrypt" it when I make a call?
So, I have tried to be as secure as possible. This is what I have done.
I created a new provider:
public class AdvancedEncryptionStandardProvider
{
// Private properties
private readonly ICryptoTransform _encryptor, _decryptor;
private UTF8Encoding _encoder;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="key">Our shared key</param>
/// <param name="secret">Our secret</param>
public AdvancedEncryptionStandardProvider(string key, string secret)
{
// Create our encoder
this._encoder = new UTF8Encoding();
// Get our bytes
var _key = _encoder.GetBytes(key);
var _secret = _encoder.GetBytes(secret);
// Create our encryptor and decryptor
var managedAlgorithm = new RijndaelManaged();
managedAlgorithm.BlockSize = 128;
managedAlgorithm.KeySize = 128;
this._encryptor = managedAlgorithm.CreateEncryptor(_key, _secret);
this._decryptor = managedAlgorithm.CreateDecryptor(_key, _secret);
}
/// <summary>
/// Encrypt a string
/// </summary>
/// <param name="unencrypted">The un-encrypted string</param>
/// <returns></returns>
public string Encrypt(string unencrypted)
{
return Convert.ToBase64String(Encrypt(this._encoder.GetBytes(unencrypted)));
}
/// <summary>
/// Decrypt a string
/// </summary>
/// <param name="encrypted">The encrypted string</param>
/// <returns></returns>
public string Decrypt(string encrypted)
{
return this._encoder.GetString(Decrypt(Convert.FromBase64String(encrypted)));
}
/// <summary>
/// Encrypt some bytes
/// </summary>
/// <param name="buffer">The bytes to encrypt</param>
/// <returns></returns>
public byte[] Encrypt(byte[] buffer)
{
return Transform(buffer, this._encryptor);
}
/// <summary>
/// Decrypt some bytes
/// </summary>
/// <param name="buffer">The bytes to decrypt</param>
/// <returns></returns>
public byte[] Decrypt(byte[] buffer)
{
return Transform(buffer, this._decryptor);
}
/// <summary>
/// Writes bytes to memory
/// </summary>
/// <param name="buffer">The bytes</param>
/// <param name="transform"></param>
/// <returns></returns>
protected byte[] Transform(byte[] buffer, ICryptoTransform transform)
{
// Create our memory stream
var stream = new MemoryStream();
// Write our bytes to the stream
using (var cs = new CryptoStream(stream, transform, CryptoStreamMode.Write))
{
cs.Write(buffer, 0, buffer.Length);
}
// Retrun the stream as an array
return stream.ToArray();
}
}
Then my PasswordHasher, I changed to this:
public class PasswordHasher : IPasswordHasher
{
// Private properties
private readonly AdvancedEncryptionStandardProvider _provider;
public PasswordHasher(AdvancedEncryptionStandardProvider provider)
{
this._provider = provider;
}
public string HashPassword(string password)
{
// Do no hashing
return this._provider.Encrypt(password);
}
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
// Throw an error if any of our passwords are null
ThrowIf.ArgumentIsNull(() => hashedPassword, () => providedPassword);
// Just check if the two values are the same
if (hashedPassword.Equals(this.HashPassword(providedPassword)))
return PasswordVerificationResult.Success;
// Fallback
return PasswordVerificationResult.Failed;
}
}
To use this PasswordHasher, you invoke it like this:
var passwordHasher = new PasswordHasher(new AdvancedEncryptionStandardProvider(ConfigurationManager.AppSettings["as:key"], ConfigurationManager.AppSettings["as:secret"]));
This seems to satisfy my conditions. Let me know if there are security risks please!

c# BouncyCastle Exception: pad block corrupted

I've been using the code that was posted here to encrypt and decrypt, written by #nerdybeardo. However, I'm getting the error "pad block corrupted" when trying to decrypt.
The Encryptor class looks like this, which implements encrypt then MAC:
/// <summary>
/// Encrypt/decrypt + HMAC using BouncyCastle (C# Java port)
/// </summary>
/// <typeparam name="TBlockCipher">The type of the block cipher.</typeparam>
/// <typeparam name="TDigest">The type of the digest.</typeparam>
/// <see cref="https://stackoverflow.com/a/13511671/119624"/>
public sealed class Encryptor<TBlockCipher, TDigest>
where TBlockCipher : IBlockCipher, new()
where TDigest : IDigest, new()
{
private readonly Encoding encoding;
private readonly byte[] key;
private IBlockCipher blockCipher;
private BufferedBlockCipher cipher;
private HMac mac;
/// <summary>
/// Initializes a new instance of the <see cref="Encryptor{TBlockCipher, TDigest}"/> class.
/// </summary>
/// <param name="encoding">The encoding.</param>
/// <param name="key">The key.</param>
/// <param name="macKey">The mac key.</param>
public Encryptor(Encoding encoding, byte[] key, byte[] macKey)
{
this.encoding = encoding;
this.key = key;
this.Init(key, macKey, new Pkcs7Padding());
}
/// <summary>
/// Initializes a new instance of the <see cref="Encryptor{TBlockCipher, TDigest}"/> class.
/// </summary>
/// <param name="encoding">The encoding.</param>
/// <param name="key">The key.</param>
/// <param name="macKey">The mac key.</param>
/// <param name="padding">The padding.</param>
public Encryptor(Encoding encoding, byte[] key, byte[] macKey, IBlockCipherPadding padding)
{
this.encoding = encoding;
this.key = key;
this.Init(key, macKey, padding);
}
/// <summary>
/// Encrypts the specified plain.
/// </summary>
/// <param name="plain">The plain.</param>
/// <returns></returns>
public string Encrypt(string plain)
{
return Convert.ToBase64String(EncryptBytes(plain));
}
/// <summary>
/// Encrypts the bytes.
/// </summary>
/// <param name="plain">The plain.</param>
/// <returns></returns>
public byte[] EncryptBytes(string plain)
{
byte[] input = this.encoding.GetBytes(plain);
var iv = this.GenerateInitializationVector();
var cipher = this.BouncyCastleCrypto(true, input, new ParametersWithIV(new KeyParameter(key), iv));
byte[] message = CombineArrays(iv, cipher);
this.mac.Reset();
this.mac.BlockUpdate(message, 0, message.Length);
var digest = new byte[this.mac.GetUnderlyingDigest().GetDigestSize()];
this.mac.DoFinal(digest, 0);
var result = CombineArrays(digest, message);
return result;
}
/// <summary>
/// Decrypts the bytes.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <returns></returns>
/// <exception cref="CryptoException"></exception>
public byte[] DecryptBytes(byte[] bytes)
{
// split the digest into component parts
var digest = new byte[this.mac.GetUnderlyingDigest().GetDigestSize()];
var message = new byte[bytes.Length - digest.Length];
var iv = new byte[this.blockCipher.GetBlockSize()];
var cipher = new byte[message.Length - iv.Length];
Buffer.BlockCopy(bytes, 0, digest, 0, digest.Length);
Buffer.BlockCopy(bytes, digest.Length, message, 0, message.Length);
if (!IsValidHMac(digest, message))
{
throw new CryptoException();
}
Buffer.BlockCopy(message, 0, iv, 0, iv.Length);
Buffer.BlockCopy(message, iv.Length, cipher, 0, cipher.Length);
byte[] result = this.BouncyCastleCrypto(false, cipher, new ParametersWithIV(new KeyParameter(key), iv));
return result;
}
/// <summary>
/// Decrypts the specified bytes.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <returns></returns>
public string Decrypt(byte[] bytes)
{
return this.encoding.GetString(DecryptBytes(bytes));
}
/// <summary>
/// Decrypts the specified cipher.
/// </summary>
/// <param name="cipher">The cipher.</param>
/// <returns></returns>
public string Decrypt(string cipher)
{
return this.Decrypt(Convert.FromBase64String(cipher));
}
/// <summary>
/// Combines the arrays.
/// </summary>
/// <param name="source1">The source1.</param>
/// <param name="source2">The source2.</param>
/// <returns></returns>
private static byte[] CombineArrays(byte[] source1, byte[] source2)
{
var result = new byte[source1.Length + source2.Length];
Buffer.BlockCopy(source1, 0, result, 0, source1.Length);
Buffer.BlockCopy(source2, 0, result, source1.Length, source2.Length);
return result;
}
/// <summary>
/// Ares the equal.
/// </summary>
/// <param name="digest">The digest.</param>
/// <param name="computed">The computed.</param>
/// <returns></returns>
private static bool AreEqual(byte[] digest, byte[] computed)
{
if (digest.Length != computed.Length)
{
return false;
}
var result = 0;
for (var i = 0; i < digest.Length; i++)
{
result |= digest[i] ^ computed[i];
}
return result == 0;
}
/// <summary>
/// Initializes the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="macKey">The mac key.</param>
/// <param name="padding">The padding.</param>
private void Init(byte[] key, byte[] macKey, IBlockCipherPadding padding)
{
this.blockCipher = new CbcBlockCipher(new TBlockCipher());
this.cipher = new PaddedBufferedBlockCipher(this.blockCipher, padding);
this.mac = new HMac(new TDigest());
this.mac.Init(new KeyParameter(macKey));
}
/// <summary>
/// Determines whether [is valid h mac] [the specified digest].
/// </summary>
/// <param name="digest">The digest.</param>
/// <param name="message">The message.</param>
/// <returns></returns>
private bool IsValidHMac(byte[] digest, byte[] message)
{
this.mac.Reset();
this.mac.BlockUpdate(message, 0, message.Length);
var computed = new byte[this.mac.GetUnderlyingDigest().GetDigestSize()];
this.mac.DoFinal(computed, 0);
return AreEqual(digest, computed);
}
/// <summary>
/// Bouncy Castle Cryptography.
/// </summary>
/// <param name="forEncrypt">if set to <c>true</c> [for encrypt].</param>
/// <param name="input">The input.</param>
/// <param name="parameters">The parameters.</param>
/// <returns></returns>
private byte[] BouncyCastleCrypto(bool forEncrypt, byte[] input, ICipherParameters parameters)
{
try
{
cipher.Init(forEncrypt, parameters);
return this.cipher.DoFinal(input);
}
catch (CryptoException)
{
throw;
}
}
/// <summary>
/// Generates the initialization vector.
/// </summary>
/// <returns></returns>
private byte[] GenerateInitializationVector()
{
using (var provider = new RNGCryptoServiceProvider())
{
// 1st block
var result = new byte[this.blockCipher.GetBlockSize()];
provider.GetBytes(result);
return result;
}
}
}
I have a simple wrapper for the AES engine. It looks like this:
public class AesSha256Encryptor
{
private readonly Encryptor<AesEngine, Sha256Digest> provider;
/// <summary>
/// Initializes a new instance of the <see cref="AesSha256Encryptor"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="hmacKey">The HMAC key.</param>
public AesSha256Encryptor(byte[] key, byte[] hmacKey)
{
provider = new Encryptor<AesEngine, Sha256Digest>(Encoding.UTF8, key, hmacKey);
}
/// <summary>
/// Encrypts the specified plain.
/// </summary>
/// <param name="plain">The plain.</param>
/// <returns></returns>
public string Encrypt(string plain)
{
return provider.Encrypt(plain);
}
/// <summary>
/// Decrypts the specified cipher.
/// </summary>
/// <param name="cipher">The cipher.</param>
/// <returns></returns>
public string Decrypt(string cipher)
{
return provider.Decrypt(cipher);
}
}
I wanted to be able to have a different salt per database row, so I have a key manager that works like this:
public static class EncryptionKeyManager
{
/// <summary>
/// The salt length limit
/// </summary>
private const int SaltLengthLimit = 32;
/// <summary>
/// Gets the key record.
/// </summary>
/// <returns></returns>
public static KeyRecord GetKeyRecord()
{
// get the shared passphrasefrom appsettings
var sharedPassphrase = GetSharedPassphrase();
// get the client passphrase from config db to sign
var clientPassphrase = GetClientPassphrase();
// generate secure random salt
var salt = GetSalt();
// get both the encryption key and hmac key
// these will be used for Encrypt-then-Mac
var key = GetKeyFromPassphrase(sharedPassphrase, salt);
var hmacKey = GetKeyFromPassphrase(clientPassphrase, salt);
return new KeyRecord
{
SharedKey = key,
HmacKey = hmacKey,
Salt = salt
};
}
/// <summary>
/// Gets the client salt.
/// </summary>
/// <returns></returns>
private static string GetClientPassphrase()
{
var settingsService = ServiceLocator.Current.GetInstance<ISettingService>();
return settingsService.GetSetting(ConstantConfigSettings.EncryptionSettings.ClientPassphrase, defaultValue: "<removed>");
}
/// <summary>
/// Gets the shared passphrase.
/// </summary>
/// <returns></returns>
private static string GetSharedPassphrase()
{
return ConfigurationManager.AppSettings[ConstantConfigSettings.EncryptionSettings.SharedPassphrase] ?? "<removed>";
}
/// <summary>
/// Gets the key from passphrase.
/// </summary>
/// <param name="passphrase">The passphrase.</param>
/// <param name="salt">The salt.</param>
/// <returns></returns>
private static byte[] GetKeyFromPassphrase(string passphrase, string salt)
{
var saltArray = Encoding.UTF8.GetBytes(salt);
var rfcKey = new Rfc2898DeriveBytes(passphrase, saltArray, 10000);
return rfcKey.GetBytes(32); // for a 256-bit key (32*8=128)
}
/// <summary>
/// Gets the salt from a secure random generator..
/// </summary>
/// <param name="maximumSaltLength">Maximum length of the salt.</param>
/// <returns></returns>
private static string GetSalt(int maximumSaltLength = SaltLengthLimit)
{
var salt = new byte[maximumSaltLength];
using (var random = new RNGCryptoServiceProvider())
{
random.GetNonZeroBytes(salt);
}
return Convert.ToBase64String(salt);
}
}
It all gets used like this to encrypt:
// get key and salt from
var keyRecord = EncryptionKeyManager.GetKeyRecord();
var aesSha256Encryptor = new AesSha256Encryptor(keyRecord.SharedKey, keyRecord.HmacKey);
// now encrypt and store, include salt
entity.AccountNumber = aesSha256Encryptor.Encrypt(accountNumber);
entity.SortCode = aesSha256Encryptor.Encrypt(sortCode);
entity.Salt = keyRecord.Salt;
When I want to decrypt, I do the following:
public static class KeyManager
{
/// <summary>
/// Gets the key from passphrase.
/// </summary>
/// <param name="passphrase">The passphrase.</param>
/// <param name="salt">The salt.</param>
/// <returns>A byte array.</returns>
public static byte[] GetKeyFromPassphrase(string passphrase, string salt)
{
var saltArray = Encoding.UTF8.GetBytes(salt);
var rfcKey = new Rfc2898DeriveBytes(passphrase, saltArray, 10000);
return rfcKey.GetBytes(32); // for a 256-bit key (32*8=128)
}
}
var passphraseKey = KeyManager.GetKeyFromPassphrase(this.Passphrase, this.Salt);
var hmacKey = KeyManager.GetKeyFromPassphrase(this.ClientPassphrase, this.Salt);
var aesSha256Encryptor = new AesSha256Encryptor(passphraseKey, hmacKey);
var plaintext = aesSha256Encryptor.Decrypt(this.CipherText);
This is for a SAAS application. My basic idea was to have a passphrase that is core to the SAAS application that is used to encrypt/decrypt, but also have a specific client passphrase that is used to MAC. The reason for this was to spread the keys between endpoints (one in a database and one in a config setting). The salt gets saved to the database so that it can be used to decrypt using the same salt.
Can anyone see what I am doing wrong? Why am I getting the pad block error?
FYI: The passphrases are of the XKCD variety "horse-battery-stapler-correct" style, so they have hyphens in. I'm not sure if that is a red herring though.
I'm also not sure if the unique salt per row is required, or whether I could just hard code the salt? Is that overkill?
Update
For anyone who finds this, the error was simply that the passphrase that I thought was being used was incorrect. The padding error was the result.
It is not clear what code exactly causes your problem (I mean there is no minimal example I could just run and see what is wrong), but I built an example which decrypts correctly without errors, based on your code, so you can look at it and probably spot your error.
I made EncryptionKeyManager.GetSharedPassphrase() public, and it returns fixed string horse-battery-stapler-correct. I made EncryptionKeyManager.GetClientPassphrase() also public and it returns fixed horse-battery.
class Program {
static void Main(string[] args) {
// get key and salt from
var keyRecord = EncryptionKeyManager.GetKeyRecord();
var aesSha256Encryptor = new AesSha256Encryptor(keyRecord.SharedKey, keyRecord.HmacKey);
string targetData = "4343423343";
var encrypted = aesSha256Encryptor.Encrypt(targetData);
var salt = keyRecord.Salt;
var decrypted = Decrypt(encrypted, salt);
Debug.Assert(targetData == decrypted);
Console.WriteLine(decrypted);
Console.ReadKey();
}
private static string Decrypt(string data, string salt) {
var passphraseKey = KeyManager.GetKeyFromPassphrase(EncryptionKeyManager.GetSharedPassphrase(), salt);
var hmacKey = KeyManager.GetKeyFromPassphrase(EncryptionKeyManager.GetClientPassphrase(), salt);
var aesSha256Encryptor = new AesSha256Encryptor(passphraseKey, hmacKey);
var plaintext = aesSha256Encryptor.Decrypt(data);
return plaintext;
}
}
It's a bit disconcerting that you are getting a padding error, that means the mac validated or wasn't checked before decryption. I guess since you are using two different passphrases if you mac passphrase is correct and your encryption is off that could make sense.
The concern with padding errors being leaked are for chosen ciphertext attacks, so just scanning the overly complex class you borrowed it looks like the mac should be checked, however, I'd at least double check if you put in the incorrect mac passphrase that it won't give you a padding error, because if it does that means there is an issue with that class you are using.
Salt per row is the minimum if you are using passphrases, and not overkill. I prefer salt per encryption as you can see in my example.
The other thing you mentioned was spreading the keys by putting one in the database and one in the config. Splitting the storage of the encryption key from the mac key, is not a good way to do this if that is your the goal, but I'm a bit unclear to your goal. If your app was compromised in the database with an sql injection lets say, only the encryption key is needed to decrypt, the mac is there to just validate that the ciphertext hasn't been tampered with. It's much better to encrypt your ciphertext with decrypted keys stored in the database that were decrypted with keys stored in the config, if you just merely want to spread the key storage a little more.

Google Adwords API C# Client Library

I downloaded the Adwords API client library for DOTNET. I am trying to use the GetAccountHierarchy.cs file example to get the account client list from Adwords Account. The code is appended as below:
using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201209;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Google.Api.Ads.AdWords.Examples.CSharp.v201209 {
/// <summary>
/// This code example illustrates how to retrieve the account hierarchy under
/// an account. This code example won't work with Test Accounts. See
/// https://developers.google.com/adwords/api/docs/test-accounts
///
/// Tags: ManagedCustomerService.get
/// </summary>
public class GetAccountHierarchy : ExampleBase {
/// <summary>
/// Main method, to run this code example as a standalone application.
/// </summary>
/// <param name="args">The command line arguments.</param>
public static void Main(string[] args) {
GetAccountHierarchy codeExample = new GetAccountHierarchy();
Console.WriteLine(codeExample.Description);
try {
codeExample.Run(new AdWordsUser());
} catch (Exception ex) {
Console.WriteLine("An exception occurred while running this code example. {0}",
ExampleUtilities.FormatException(ex));
}
}
/// <summary>
/// Returns a description about the code example.
/// </summary>
public override string Description {
get {
return "This code example illustrates how to retrieve the account hierarchy under" +
" an account. This code example won't work with Test Accounts. See " +
"https://developers.google.com/adwords/api/docs/test-accounts";
}
}
/// <summary>
/// Runs the code example.
/// </summary>
/// <param name="user">The AdWords user.</param>
public void Run(AdWordsUser user) {
// Get the ManagedCustomerService.
ManagedCustomerService managedCustomerService = (ManagedCustomerService) user.GetService(
AdWordsService.v201209.ManagedCustomerService);
managedCustomerService.RequestHeader.clientCustomerId = null;
// Create selector.
Selector selector = new Selector();
selector.fields = new String[] {"Login", "CustomerId", "Name"};
try {
// Get results.
ManagedCustomerPage page = managedCustomerService.get(selector);
// Display serviced account graph.
if (page.entries != null) {
// Create map from customerId to customer node.
Dictionary<long, ManagedCustomerTreeNode> customerIdToCustomerNode =
new Dictionary<long, ManagedCustomerTreeNode>();
// Create account tree nodes for each customer.
foreach (ManagedCustomer customer in page.entries) {
ManagedCustomerTreeNode node = new ManagedCustomerTreeNode();
node.Account = customer;
customerIdToCustomerNode.Add(customer.customerId, node);
}
// For each link, connect nodes in tree.
if (page.links != null) {
foreach (ManagedCustomerLink link in page.links) {
ManagedCustomerTreeNode managerNode =
customerIdToCustomerNode[link.managerCustomerId];
ManagedCustomerTreeNode childNode = customerIdToCustomerNode[link.clientCustomerId];
childNode.ParentNode = managerNode;
if (managerNode != null) {
managerNode.ChildAccounts.Add(childNode);
}
}
}
// Find the root account node in the tree.
ManagedCustomerTreeNode rootNode = null;
foreach (ManagedCustomer account in page.entries) {
if (customerIdToCustomerNode[account.customerId].ParentNode == null) {
rootNode = customerIdToCustomerNode[account.customerId];
break;
}
}
// Display account tree.
Console.WriteLine("Login, CustomerId, Name");
Console.WriteLine(rootNode.ToTreeString(0, new StringBuilder()));
} else {
Console.WriteLine("No serviced accounts were found.");
}
} catch (Exception ex) {
throw new System.ApplicationException("Failed to create ad groups.", ex);
}
}
/**
* Example implementation of a node that would exist in an account tree.
*/
class ManagedCustomerTreeNode {
/// <summary>
/// The parent node.
/// </summary>
private ManagedCustomerTreeNode parentNode;
/// <summary>
/// The account associated with this node.
/// </summary>
private ManagedCustomer account;
/// <summary>
/// The list of child accounts.
/// </summary>
private List<ManagedCustomerTreeNode> childAccounts = new List<ManagedCustomerTreeNode>();
/// <summary>
/// Gets or sets the parent node.
/// </summary>
public ManagedCustomerTreeNode ParentNode {
get { return parentNode; }
set { parentNode = value; }
}
/// <summary>
/// Gets or sets the account.
/// </summary>
public ManagedCustomer Account {
get { return account; }
set { account = value; }
}
/// <summary>
/// Gets the child accounts.
/// </summary>
public List<ManagedCustomerTreeNode> ChildAccounts {
get { return childAccounts; }
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override String ToString() {
String login = String.IsNullOrEmpty(account.login) ? "(no login)" : account.login;
return String.Format("{0}, {1}, {2}", login, account.customerId, account.name);
}
/// <summary>
/// Returns a string representation of the current level of the tree and
/// recursively returns the string representation of the levels below it.
/// </summary>
/// <param name="depth">The depth of the node.</param>
/// <param name="sb">The String Builder containing the tree
/// representation.</param>
/// <returns>The tree string representation.</returns>
public StringBuilder ToTreeString(int depth, StringBuilder sb) {
sb.Append(new String('-', depth * 2));
sb.Append(this);
sb.Append("\n");
foreach (ManagedCustomerTreeNode childAccount in childAccounts) {
childAccount.ToTreeString(depth + 1, sb);
}
return sb;
}
}
}
}
I am trying to figure out where i can pass the parameter like my account username, password, developer token and access token to this function? Does anybody know how i can pass those info to this code? Thanks.
It seems to me, wildly guessing, the account, username and password are present in some secondary resource files not passed in as arguments, or typed into the program as input, but loaded automatically from some 'special' location by the Google API.
Do you, perhaps, have a PGP type keys downloaded from the AdWords program?
I know this getting old but you can pass parameter in your app.config/web.config
<add key="ClientCustomerId" value="insert your client customer id"/>
<add key="AuthorizationMethod" value="OAuth2"/>
<add key="OAuth2ClientId" value="insert oauth2 client id"/>
<add key="OAuth2ClientSecret" value="insert oauth client scret"/>
<add key="OAuth2RefreshToken" value="insert oauth2 refresh token"/>
all oauth2 value you can get from google API Manager

AesCryptoServiceProvider, performance problems

I have a two very simple methods to encrypt/decrypt a byte[] using the AesCryptoServiceProvider. But I'm very surprised about the performance of the method.
I tried to encrypt and decrypt as many bytes[] as my computer can and in the first 4 seconds I can encrypt about 2000 times, but in the next 4 seconds, about 1000, in the next four seconds, about 500... at the end i can have about 80 operations per 4 seconds. Why?
Look at the code.
namespace Encrypttest
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Globalization;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("AES provider");
var key = GenerateKey();
DateTime now = DateTime.Now.AddSeconds(2);
var toStop = now.AddSeconds(120);
long operations = 0;
byte[] buffer = new byte[16];
byte[] result = new byte[buffer.Length];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = 2;
}
for (long i = 0; i < 10000000; i++)
{
result = Encrypt(buffer, key);
Decrypt(result, key);
buffer = result;
operations++;
if (DateTime.Now > now)
{
Console.WriteLine(now.ToLongTimeString() + ";" + operations + ";" + System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64);
operations = 0;
now = DateTime.Now.AddSeconds(2);
}
if (toStop < DateTime.Now)
{
break;
}
}
}
/// <summary>
/// Encrypts the specified data.
/// </summary>
/// <param name="data">The data to encrypt</param>
/// <param name="key">The key to encrypt data.</param>
/// <returns>
/// The data encrypted.
/// </returns>
public static byte[] Encrypt(byte[] data, SymmetricKey key)
{
if (data == null || data.Length == 0)
{
throw new ArgumentNullException("data");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
using (AesCryptoServiceProvider providerInLine = new AesCryptoServiceProvider())
{
using (MemoryStream stream = new MemoryStream())
{
using (ICryptoTransform cryptoEncryptor = providerInLine.CreateEncryptor(key.Key, key.IV))
{
using (CryptoStream writerStream = new CryptoStream(stream, cryptoEncryptor, CryptoStreamMode.Write))
{
writerStream.Write(data, 0, data.Length);
writerStream.FlushFinalBlock();
return stream.ToArray();
}
}
}
}
}
/// <summary>
/// Decrypts the specified data.
/// </summary>
/// <param name="data">The data to decrypt</param>
/// <param name="key">The key to decrypt data.</param>
/// <returns>
/// The data encrypted.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static byte[] Decrypt(byte[] data, SymmetricKey key)
{
if (data == null || data.Length == 0)
{
throw new ArgumentNullException("data");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
using (AesCryptoServiceProvider provider = new AesCryptoServiceProvider())
{
using (MemoryStream memStreamEncryptData = new MemoryStream(data))
{
using (ICryptoTransform cryptoDecryptor = provider.CreateDecryptor(key.Key, key.IV))
{
using (CryptoStream stream = new CryptoStream(memStreamEncryptData, cryptoDecryptor, CryptoStreamMode.Read))
{
byte[] plainTextBytes = new byte[data.Length];
stream.Read(plainTextBytes, 0, plainTextBytes.Length);
return plainTextBytes;
}
}
}
}
}
/// <summary>
/// Generates a random key and initialization vector
/// </summary>
/// <returns>
/// The key and initialization vector.
/// </returns>
public static SymmetricKey GenerateKey()
{
using (AesCryptoServiceProvider provider = new AesCryptoServiceProvider())
{
provider.GenerateKey();
SymmetricKey key = new SymmetricKey(provider.Key, provider.IV);
return key;
}
}
}
public class SymmetricKey
{
/// <summary>
/// The key.
/// </summary>
private byte[] key;
/// <summary>
/// The initialization vector.
/// </summary>
private byte[] iv;
/// <summary>
/// Initializes a new instance of the <see cref="SymmetricKey"/> class.
/// </summary>
public SymmetricKey()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SymmetricKey"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="iv">The iv.</param>
public SymmetricKey(byte[] key, byte[] iv)
{
this.Init(key, iv);
}
/// <summary>
/// Gets the key.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Byte[] is what the providers need")]
public byte[] Key
{
get { return this.key; }
}
/// <summary>
/// Gets the iv.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Byte[] is what the providers need")]
public byte[] IV
{
get { return this.iv; }
}
/// <summary>
/// Loads the specified key and iv
/// </summary>
/// <param name="newKey">The key.</param>
/// <param name="newIV">The iv.</param>
public void Init(byte[] newKey, byte[] newIV)
{
this.key = newKey;
this.iv = newIV;
}
}
}
The ciphertext is 16 bytes larger than the plaintext due to padding. Since you're using the ciphertext of of one iteration as the plaintext of the next, the length of the buffer grows linearly with the number of iterations so far. This explains the slowdown.
After 2000 iterations it needs to encrypt 30kB per iteration, after 6000 iterations it needs to encrypt 100kB per iteration...

Categories