0

The following is the correct way to do encryption. I thought it useful to post for various reasons, most particular being the necessity for our companies to have good security controls. This is very important. If anybody has any other good snippets feel free to post. The user class is a little verbose, but you should be able to clearly see the hash algorithim which is essentially non reversable by anything other than a password crack. That is a HashAlgorithim. In order to create things you can decode you should use a SymmetricAlgorithim. Passwords should always be HashAlgorithims due to the fact that the company doesn't need to have large word lists.

/*Author: Cameron Block*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Configuration;

namespace NHibernateIdentity.Models {
    public class User {
        /// <summary>
        /// Unique identifier for group used by program and database layer. 
        /// </summary>
        public virtual int UserId {
            get; set;
        }

        /// <summary>
        /// User's login identity. 
        /// </summary>
        public virtual String UserName {
            get; set;
        }

        /// <summary>
        /// The user's email address. 
        /// </summary>
        public virtual String EmailAddress {
            get; set;
        }

        /// <summary>
        /// Used to turn off user accessibility for instance when there are forensic investigations going on. 
        /// </summary>
        public virtual bool IsActive {
            get; set;
        }

        /// <summary>
        /// A hash of the users password is stored in the database and used for logins. 
        /// Storing a hash is more secure than storing plaintext. 
        /// No Company should have a comprehensive plaintext wordlist of it's users. 
        /// </summary>
        public virtual byte[] PasswordHash {
            get; set;
        }

        /// <summary>
        /// The groups a user is associated with. 
        /// </summary>
        public virtual IList<Group> Groups {
            get; set;
        }

        /// <summary>
        /// The roles associated with this user. 
        /// </summary>
        public virtual IList<Role> Roles {
            get; set;
        }

        /// <summary>
        /// A list of invalid login attempts. 
        /// </summary>
        public virtual IList<InvalidLoginDomainEvent> InvalidLoginEvents {
            get; set;
        }

        /// <summary>
        /// A list of locked events associated with this user. 
        /// </summary>
        public virtual IList<LockedDomainEvent> LockedEvents {
            get; set;
        }

        /// <summary>
        /// A list of banned events associated with a user. 
        /// </summary>
        public virtual IList<BannedDomainEvent> BannedEvents {
            get; set;
        }

        /// <summary>
        /// The events that reflect the number of times a user has been unbanned. 
        /// </summary>
        public virtual IList<UnBannedDomainEvent> UnBans {
            get; set;
        }

        /// <summary>
        /// A request to unban this user which may or may not actually be fulfilled. 
        /// </summary>
        public virtual IList<UnBanRequest> UnBanRequests{
            get; set;
        }

        /// <summary>
        /// Unban denial attempts. 
        /// </summary>
        public virtual IList<UnBanDeniedDomainEvent> UnBanDenials {
            get; set;
        }

        public User() {
            IsActive = true;
        }

        /// <summary>
        /// Sets the password for the given individual. 
        /// </summary>
        /// <param name="password"></param>
        public virtual void SetPassword(String password) {
            //get salt from web config
            byte[] salt = Encoding.UTF8.GetBytes(System.Configuration.ConfigurationManager.AppSettings["salt"].ToString());
            byte[] passBytes = Encoding.UTF8.GetBytes(password);

            //perpend salt to password
            byte[] catPass = salt.Concat(passBytes).ToArray();

            //call all the hash algorithims here
            HashAlgorithm hashAlg = GetHashAlgorithim();
            this.PasswordHash = hashAlg.ComputeHash(catPass);
        }//end method

        /// <summary>
        /// Determines whether two passwords are equal. 
        /// </summary>
        /// <param name="password"></param>
        /// <returns></returns>
        public virtual bool ComparePassword(String password) {
            //get salt from web config
            byte[] salt = Encoding.UTF8.GetBytes(System.Configuration.ConfigurationManager.AppSettings["salt"].ToString());
            byte[] passBytes = Encoding.UTF8.GetBytes(password);

            //perpend salt to password
            byte[] catPass = salt.Concat(passBytes).ToArray();

            //call all the hash algorithims here
            HashAlgorithm hashAlg = GetHashAlgorithim();

            byte[] incomingHash = hashAlg.ComputeHash(catPass);

            if (incomingHash.SequenceEqual(this.PasswordHash))
                return true;

            return false;
        }//end method

        /// <summary>
        /// Gets the underlying hash algorithim for the object's password capability. 
        /// </summary>
        /// <returns></returns>
        public virtual HashAlgorithm GetHashAlgorithim() {
            //Detect the hash algorithim that the user wants employed. 
            String hashMethod = System.Configuration.ConfigurationManager.AppSettings["HashMethod"];
            Dictionary<String, HashAlgorithm> hashAlgorithims = new Dictionary<String, HashAlgorithm>();
            hashAlgorithims.Add("MD5", MD5.Create());
            hashAlgorithims.Add("RIPEMD160", RIPEMD160.Create());
            hashAlgorithims.Add("SHA1", SHA1.Create());
            hashAlgorithims.Add("SHA256", SHA256.Create());
            hashAlgorithims.Add("SHA384", SHA256.Create());
            hashAlgorithims.Add("SHA512", SHA512.Create());

            HashAlgorithm currentAlg = null;
            if (hashAlgorithims.ContainsKey(hashMethod))
                currentAlg = hashAlgorithims[hashMethod];
            else
                throw new NotImplementedException("This algorithim has not been implemented. ");

            return currentAlg;
        }//end method

        /// <summary>
        /// Determine whether user account is locked from too many invalid logins. 
        /// </summary>
        /// <returns></returns>
        public virtual bool IsLocked(TimeSpan lockDuration, TimeSpan lockDecay, int lockThreshold) {
            var invalidLogins = this.InvalidLoginEvents
                .Where(evt => evt.LoginTime > DateTime.Now - lockDecay)
                .Where(evt => evt.LoginTime + lockDuration > DateTime.Now);

            if(UnBans.Count() > 0)
                invalidLogins = invalidLogins.Where(evt => evt.LoginTime >
                       UnBans.ToList().OrderByDescending(unBan => unBan.DateUnlocked).First().DateUnlocked);

            return invalidLogins.Count() > lockThreshold;
        }//end method

        /// <summary>
        /// Determine whether a lock is already effective. 
        /// </summary>
        /// <param name="lockDuration"></param>
        /// <param name="lockDecay"></param>
        /// <param name="lockThreshold"></param>
        /// <returns></returns>
        public virtual bool LockIsEffective(TimeSpan lockDuration, TimeSpan lockDecay, int lockThreshold) {
            return this.LockedEvents
                .Where(evt => evt.LockTime > DateTime.Now - lockDecay)
                .Where(evt => evt.LockTime + lockDuration > DateTime.Now)
                .Count() > 0;
        }//end method

        /// <summary>
        /// Determine whether the user account is banned. 
        /// </summary>
        /// <returns></returns>
        public virtual bool IsBanned() {
            var query = BannedEvents.ToList();

            if (UnBans.Count() > 0) {
                UnBannedDomainEvent evt = UnBans.ToList().OrderByDescending(unban => unban.DateUnlocked).First();
                query = query.Where(ban => ban.BanDate > evt.DateUnlocked).ToList();
            }

            return query.Count() > 0;
        }//end method

        /// <summary>
        /// Determine whether a user is in a certain role. 
        /// </summary>
        /// <param name="role"></param>
        /// <returns></returns>
        public virtual bool IsInRole(String roleName) {
            return Roles.Where(role => role.Authority == roleName).Count() > 0;
        }

        /// <summary>
        /// Determine whether a user is in a certain group. 
        /// </summary>
        /// <param name="grpName"></param>
        /// <returns></returns>
        public virtual bool IsInGroup(String grpName) {
            return Groups.Where(grp => grp.GroupName == grpName).Count() > 0;
        }

    }//end class

}//end namespace

The following is an example of a symmetric algorithim.

/*Author: Cameron Block*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Configuration;

namespace GadsdenReporting.Models {
    /// <summary>
    /// FtpCredential stores passwords in the database, and uploads files to remote FTP sites. 
    /// </summary>
    public class FtpCredential {
        private const int CHARS_AVAILABLE = 32;

        public FtpCredential() {

        }

        /// <summary>
        /// The Ftp Site name. 
        /// </summary>
        public String FtpSite {
            get; set;
        }

        /// <summary>
        /// The user name to use when logging onto ftp site. 
        /// </summary>
        public String UserName {
            get; set;
        }

        /// <summary>
        /// The encrypted password of the ftp site. 
        /// </summary>
        public byte[] PasswordEncrypted {
            get; set;
        }

        /// <summary>
        /// Gets the underlying symmetric algorithim for the object's password capability. 
        /// </summary>
        /// <returns></returns>
        public SymmetricAlgorithm GetSymmetricAlgorithim() {
            return Rijndael.Create();
        }//end method

        /// <summary>
        /// Gets the key from the web config, pads it with null bytes so that it is valid. 
        /// </summary>
        /// <param name="crypt"></param>
        /// <returns></returns>
        public byte[] GetKey(SymmetricAlgorithm crypt) {
            int keySize = crypt.LegalKeySizes[0].MinSize / 8;
            return Encoding.ASCII.GetBytes(System.Configuration.ConfigurationManager.AppSettings["EncryptionKey"].PadRight(keySize, '\0').ToCharArray(), 0, keySize);
        }//end method

        /// <summary>
        /// Gets the initialization vector from the web config, pads it with null bytes so that it is valid. 
        /// </summary>
        /// <param name="crypt"></param>
        /// <returns></returns>
        public byte[] GetIV(SymmetricAlgorithm crypt) {
            int ivSize = crypt.BlockSize / 8;
            return Encoding.ASCII.GetBytes(System.Configuration.ConfigurationManager.AppSettings["EncryptionIV"].PadRight(ivSize, '\0').ToCharArray(), 0, ivSize);
        }//end method

        /// <summary>
        /// Gets the decrypted password. 
        /// </summary>
        /// <returns></returns>
        public String GetPassword() {
            MemoryStream ms = new MemoryStream();
            SymmetricAlgorithm crypt = GetSymmetricAlgorithim();
            crypt.Padding = PaddingMode.PKCS7;
            crypt.Key = GetKey(crypt);
            crypt.IV = GetIV(crypt);
            CryptoStream cs = new CryptoStream(ms,
            crypt.CreateDecryptor(), CryptoStreamMode.Write);
            cs.Write(PasswordEncrypted, 0, PasswordEncrypted.Length);
            cs.Close();
            byte[] decryptedData = ms.ToArray();
            return Encoding.ASCII.GetString(decryptedData);
        }//end method

        /// <summary>
        /// Sets the byte array containing the encrypted password. 
        /// </summary>
        public void SetPassword(String password) {
            //we only have a specific number of bytes in the database. 
            if (password.Length > CHARS_AVAILABLE)
                throw new ArgumentException("Password text is too big. ");

            byte[] passBytes = Encoding.ASCII.GetBytes(password);
            MemoryStream ms = new MemoryStream();
            SymmetricAlgorithm crypt = GetSymmetricAlgorithim();
            crypt.Padding = PaddingMode.PKCS7;
            crypt.Key = GetKey(crypt);
            crypt.IV = GetIV(crypt);
            CryptoStream cs = new CryptoStream(ms,
            crypt.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(passBytes, 0, passBytes.Length);
            cs.Close();
            byte[] encryptedData = ms.ToArray();
            PasswordEncrypted = encryptedData;
        }//end method

        /// <summary>
        /// Gets the number of bytes for the encoding used by the credential object. 
        /// </summary>
        /// <returns></returns>
        public int GetEncodingNumBytes() {
            return Encoding.ASCII.GetByteCount("1");
        }//end method

        /// <summary>
        /// Upload file to ftp site. 
        /// </summary>
        /// <param name="stream"></param>
        public void FtpUpload(Stream stream) {
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(FtpSite);
            request.Method = WebRequestMethods.Ftp.UploadFile;
            request.Credentials = new NetworkCredential(UserName, GetPassword());
            //copy the contents of the file to the request stream. 
            byte[] fileContents = new byte[stream.Length];
            stream.Read(fileContents, 0, (int)stream.Length);

            request.ContentLength = fileContents.Length;

            Stream requestStream = request.GetRequestStream();
            requestStream.Write(fileContents, 0, fileContents.Length);

            FtpWebResponse response = (FtpWebResponse)request.GetResponse();
            response.Close();
        }//end method
    }//end class

}//end namespace

Your password is your password, and your initialization vector is password number two. I typically pad this with null bytes based on what is stored in my web config. Object relational mappers seriously create good code. For C# the main two are NHibernate, and Entity Framework. If you ever find yourself converting binary crypto to text, then use a base64 layer in your crypto stream. Base 64 converts bytes to numbers which can be stored in a text file.

4
Contributors
9
Replies
44
Views
3 Weeks
Discussion Span
Last Post by overwraith
0

I have to ask. I may be misreading this but it looks like you can decrypt the password. If I'm wrong on that the following doesn't apply.

Wasn't the current method to use one way encrption and only store the encrypted value? Then when the user enters their password we encrypt and compare the stored encrypted value. This was at no time do we have access to their password.

That is, if a site can send you your old password, it's security is broken.

0

Right, you're referring to the FTP object. But how do we connect to the FTP server? We have to transmit the password's actual text via the ftp client, therefore we need to be able to reconstitue it, hence the symmetric crypto. An FTP credential is completely different from the user object. It should be correct as is.

0

For FTP, the user has to supply their password again. But isn't FTP considered grossly insecure without going through some SSH tunnel? That is, I recall demo'ing catching the user's password during FTP login over a network. Maybe it's time for FTP to die soon.

1

I believe i first saw this FTP stuff in a business application, perhaps there will be an SFTP replacement class at some point. If you have some good code shoot. The symmetric crypto could just as easily be used on a clob of text, but i have found that you typically have to create the table by hand instead of auto generating it. Blob i believe. NHibernate by default makes such table entries RAW, so you have to make the table manually.

1

The following is the correct way to do encryption.

What international authority supports this as the correct way to do encryption? What credentials do you have to make that statement?

3

SSH with keys is the way to go. Written and vetted by cryptography experts, battle tested, hugely widespread and supported by all modern deployment mechanisms.

Rolling your own encryption routine can be fun, but it probably won't be that secure.

0

You should never roll your own encryption. You are not a cryptographer. Neither am I, however you may be trained to utilize the crypto API for your day to day business which is vetted by cryptographers. As far as international, you have to make sure your country can have the crypto classes exported to it. Ultimately though you should have some analog for the SymmetricAlgorithim, HashAlgorithim, etc classes. This is for storing encrypted content in the database. As far as the FTP part, I need to find a SFTP client I can import.

0

I defer to "C# Data Security Practical .NET Cryptography Handbook" as far as credentials go. The only really novel thing I am doing is salting the passwords, which is a sound cryptographic practice since wordlists don't have your salt appended to it. They will never match.

Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.