/// <summary> /// Compares a given (plain text) password with the (already hashed) password that is inside the hashData object. /// </summary> /// <param name="givenPassword">Plain text password to compare with</param> /// <param name="hashData"> /// The <see cref="PasswordHashingData" /> object that contains salt size, current hashed password, etc. for use in the /// comparison /// of the two passwords /// </param> public CommandResult ComparePasswords(string givenPassword, PasswordHashingData hashData) { if (String.IsNullOrWhiteSpace(givenPassword) || String.IsNullOrWhiteSpace(hashData?.HashedPassword) || String.IsNullOrWhiteSpace(hashData.Salt)) { return(CommandResultFactory.Fail("The given data to compare passwords was invalid.", false)); } var saltByteArray = hashData.Salt.ToHexBytes(); // Run initial hash at an application level var appHashedPassword = GetAppLevelPasswordHash(givenPassword); // Take the output of the initial hashing and run it through proper hashing with key stretching var hashedPasswordResult = ComputePasswordAndSaltBytes(saltByteArray, appHashedPassword, hashData.NumberOfIterations) .Then(computedBytes => { var hashedPassword = computedBytes.ToHexString(); return(CommandResultFactory.Ok <string>(hashedPassword)); }); if (hashedPasswordResult.IsFailure) { return(CommandResultFactory.Fail( $"Computing the hash for the given password was not successful due to the following:\n{hashedPasswordResult.Message}")); } return(hashedPasswordResult.Value == hashData.HashedPassword ? CommandResultFactory.Ok() : CommandResultFactory.Fail("Passwords did not match")); }
/// <summary> /// This is a way to chain a "Finally" handling callback method onto the end of the method chain. You can think of this /// as being /// similar to a "finally" call in .Net. The "Finally" will always call the callback method parameter. The callback /// itself can /// be responsible for whether or not to pass along a success or fail. There are also overloads that will simply call /// the callback /// and return the current success/fail status. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static CommandResult <TOutput> Finally <TOutput>(this CommandResult @this, Func <CommandResult, CommandResult <TOutput> > callback) { var result = @this.IsSuccessful ? callback(CommandResultFactory.Ok(@this.Message)) : callback(CommandResultFactory.Fail(@this.Message)); return(result); }
/// <summary> /// Decrypts the given encrypted text (cipher text) using the given key and key size. /// </summary> /// <param name="cipherText">The encrypted text to decrypt</param> /// <param name="key">The (HEXADECIMAL) key used to encrypt the text and that will be used to decrypt it</param> /// <param name="keySize">The size of the key for the creation of the cipher</param> public CommandResult <string> Decrypt(string cipherText, string key, AesKeySize keySize) { if (String.IsNullOrWhiteSpace(cipherText)) { return(CommandResultFactory.Fail("There was no text given to decrypt", (string)null)); } if (String.IsNullOrWhiteSpace(key)) { return(CommandResultFactory.Fail("Could not decrypt the text. The given key was null or empty.", (string)null)); } try { CommandResult <string> result; using (var aesCipher = Aes.Create()) { result = SetupCipher(aesCipher, key, keySize) .Then(aes => { var splitCipher = cipherText.Split('_'); if (splitCipher.Length != 2) { return(CommandResultFactory.Fail("The given cipher text was not in the correct encrypted format for decryption", (string)null)); } var initVector = splitCipher[0]; var encryptedString = splitCipher[1]; if (initVector.Length % 2 != 0 || encryptedString.Length % 2 != 0) { return(CommandResultFactory.Fail("The given cipher text was not in the correct encrypted format for decryption", (string)null)); } aes.IV = initVector.ToHexBytes(); var cryptoTransform = aes.CreateDecryptor(); var cipherBytes = encryptedString.ToHexBytes(); if (cipherBytes == null) { return(CommandResultFactory.Fail( "The encrypted string could not be converted into a Hexadecimal Byte Array and therefore could not be decrypted.", (string)null)); } var resultTextBytes = cryptoTransform.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); var resultText = resultTextBytes.ToUTF8String(); return(resultText == null ? CommandResultFactory.Fail("Could not convert the decrypted bytes into a string.", (string)null) : CommandResultFactory.Ok <string>(resultText)); }); } return(result); } catch (Exception e) { var message = $"An exception was thrown while trying to decrypt the text. It is as follows:\n{e.Message}"; return(CommandResultFactory.Fail(message, (string)null)); } }
/// <summary> /// This is a way to chain a error handling callback method onto the end of the method chain. The "Catch" will always /// call the callback /// method parameter. The callback itself is responsible for whether or not to pass along a success or fail. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static CommandResult Catch <TInput>(this CommandResult <TInput> @this, Func <CommandResult <TInput>, CommandResult> callback) { if (@this.IsSuccessful) { return(CommandResultFactory.Ok(@this.Message)); } var funq = callback(@this); return(funq); }
/// <summary> /// This is an async way to chain a "Finally" handling callback method onto the end of the method chain. You can think /// of this as being /// similar to a "finally" call in .Net. The "Finally" will always call the callback method parameter. The callback /// itself can /// be responsible for whether or not to pass along a success or fail. There are also overloads that will simply call /// the callback /// and return the current success/fail status. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static async Task <CommandResult> FinallyAsync(this Task <CommandResult> thisAsync, Func <CommandResult, Task <CommandResult> > callback) { var @this = await thisAsync.ConfigureAwait(false); var result = @this.IsSuccessful ? await callback(CommandResultFactory.Ok(@this)).ConfigureAwait(false) : await callback(CommandResultFactory.Fail(@this.Message, @this)).ConfigureAwait(false); return(result); }
/// <summary> /// This is a way to chain a error handling callback method onto the end of the method chain. The "Catch" will always /// call the callback /// method parameter. The callback itself is responsible for whether or not to pass along a success or fail. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static CommandResult Catch(this CommandResult @this, Func <CommandResult, CommandResult> callback) { if (@this.IsSuccessful) { return(CommandResultFactory.Ok(@this.Message)); } var errorFunq = callback(@this); return(errorFunq); }
/// <summary> /// Decrypts the cipher text by using the given key material, key size, and block size. /// </summary> /// <param name="cipherText">Encrypted text for decryption</param> /// <param name="key">The (HEXADECIMAL) key used for decryption</param> /// <param name="keySize">size of key used in the cipher creation</param> /// <param name="blockSize">block size used in the creation of the cipher</param> /// <returns></returns> public CommandResult <string> CustomDecrypt(string cipherText, string key, AesKeySize keySize, BlockSize blockSize) { if (String.IsNullOrWhiteSpace(cipherText)) { return(CommandResultFactory.Fail("There was nothing to encrypt", (string)null)); } if (String.IsNullOrWhiteSpace(key)) { return(CommandResultFactory.Fail("The given encryption key was null or empty", cipherText)); } try { var result = CreateCustomCipher(key, blockSize, keySize) .Then(cipher => { var splitCipher = cipherText.Split('_'); if (splitCipher.Length != 2) { return(CommandResultFactory.Fail("The given cipher text was not in the correct encrypted format for decryption", (string)null)); } var initVector = splitCipher[0]; var encryptedString = splitCipher[1]; if (initVector.Length % 2 != 0 || encryptedString.Length % 2 != 0) { return(CommandResultFactory.Fail("The given cipher text was not in the correct encrypted format for decryption", (string)null)); } cipher.IV = initVector.ToHexBytes(); var cryptoTransform = cipher.CreateDecryptor(); var cipherBytes = encryptedString.ToHexBytes(); if (cipherBytes == null) { return(CommandResultFactory.Fail( "The encrypted string could not be converted into a Hexadecimal Byte Array and therefore could not be decrypted.", (string)null)); } var resultTextBytes = cryptoTransform.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length); var resultText = resultTextBytes.ToUTF8String(); return(resultText == null ? CommandResultFactory.Fail("Could not convert the decrypted bytes into a string.", (string)null) : CommandResultFactory.Ok <string>(resultText)); }); return(result); } catch (Exception e) { Trace.WriteLine(e); return(CommandResultFactory.Fail($"An exception was thrown while attempting to decrypt the given text. It is as follows:\n\t{e.Message}", (string)null)); } }
/// <summary> /// This is an async way to chain a error handling callback method onto the end of the method chain. The "Catch" will /// always call the callback /// method parameter. The callback itself is responsible for whether or not to pass along a success or fail. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static async Task <CommandResult> CatchAsync(this Task <CommandResult> thisTask, Func <Task <CommandResult> > callback) { var @this = await thisTask.ConfigureAwait(false); if (@this.IsSuccessful) { return(await Task.FromResult(CommandResultFactory.Ok(@this.Message)).ConfigureAwait(false)); } var errorFunq = await callback().ConfigureAwait(false); return(errorFunq); }
/// <summary> /// This is a way to chain a "Finally" handling callback method onto the end of the method chain. You can think of this /// as being /// similar to a "finally" call in .Net. The "Finally" will always call the callback method parameter. The callback /// itself can /// be responsible for whether or not to pass along a success or fail. There are also overloads that will simply call /// the callback /// and return the current success/fail status. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static CommandResult Finally(this CommandResult @this, Action <CommandResult> callback) { if (@this.IsSuccessful) { var okResult = CommandResultFactory.Ok(@this, @this.Message); callback(okResult); return(okResult); } var failResult = CommandResultFactory.Fail(@this.Message); callback(failResult); return(failResult); }
/// <summary> /// This will compute a hash for the given password and salt using the iterationCount parameter to determine how many /// times the /// hashing will occur on the password + salt. /// </summary> /// <param name="salt">The salt that is added on to the end of the password</param> /// <param name="password">The password to be hashed</param> /// <param name="iterationCount">The number of times the password+salt will have a hash computed</param> /// <returns></returns> public CommandResult <byte[]> ComputePasswordAndSaltBytes(byte[] salt, string password, int iterationCount = MinIterationRange) { if (String.IsNullOrWhiteSpace(password)) { return(CommandResultFactory.Fail("Password was null", (byte[])null)); } if (salt == null || salt.Length < 1) { return(CommandResultFactory.Fail("The salt did not meet the minimum length", (byte[])null)); } if (salt.Length > MaxSaltSize) { return(CommandResultFactory.Fail("The salt length was greater than the maximum allowed", (byte[])null)); } var convertedPassword = password.ToUtf8Bytes(); if (convertedPassword.Length > MaxPasswordSize) { return(CommandResultFactory.Fail($"The password length was greater than the maximum allowed. Please make it less than {MaxPasswordSize}", (byte[])null)); } if (convertedPassword.Length < MinPasswordSize) { return(CommandResultFactory.Fail($"The password length was less than the minimum allowed. Please make it greater than {MinPasswordSize}", (byte[])null)); } try { var resultValue = new Rfc2898(convertedPassword, salt, iterationCount).GetDerivedKeyBytes_PBKDF2_HMACSHA512(KeyLength); return(CommandResultFactory.Ok(resultValue)); } catch (IterationsLessThanRecommended) { return(CommandResultFactory.Fail("The number of hash iterations did not meet minimum standards", (byte[])null)); } catch (SaltLessThanRecommended) { return(CommandResultFactory.Fail("The salt length is less than the minimum standards", (byte[])null)); } catch (Exception e) { return(CommandResultFactory.Fail( $"There was an unspecified error that ocurred from an exception being thrown. The exception is as follows:\n{e.Message}", (byte[])null)); } }
/// <summary> /// This is an async way to chain a "Finally" handling callback method onto the end of the method chain. You can think /// of this as being /// similar to a "finally" call in .Net. The "Finally" will always call the callback method parameter. The callback /// itself can /// be responsible for whether or not to pass along a success or fail. There are also overloads that will simply call /// the callback /// and return the current success/fail status. /// <para> /// For example: When there is an error and there is a way for the error to be corrected or an attempt to correct /// the error can be made. The Catch /// chained method can correct the error and then allow the method chain to continue in a successful manner. /// </para> /// <para> /// Another example is where the Catch method can log an error. /// </para> /// </summary> /// <param name="callback">Method to be called (no matter what).</param> public static async Task <CommandResult> Finally(this Task <CommandResult> thisAsync, Func <CommandResult, Task> callback) { var @this = await thisAsync.ConfigureAwait(false); if (@this.IsSuccessful) { var okResult = CommandResultFactory.Ok(@this, @this.Message); await callback(okResult).ConfigureAwait(false); return(okResult); } var failResult = CommandResultFactory.Fail(@this.Message); await callback(failResult).ConfigureAwait(false); return(failResult); }
/// <summary> /// Hashes the given plain-text password using the global application hash. If there is no global application hash then the given password /// is returned simply hashed without the global app salt. /// </summary> /// <param name="givenPassword"></param> public string GetAppLevelPasswordHash(string givenPassword) { if (String.IsNullOrWhiteSpace(givenPassword)) { return(givenPassword); } if (String.IsNullOrWhiteSpace(_globalApplicationSalt)) { var hash512Password = ComputeSha512HexString(givenPassword); return(hash512Password); } var appSaltByteArray = _globalApplicationSalt.ToHexBytes(); var hashedPasswordResult = ComputePasswordAndSaltBytes(appSaltByteArray, givenPassword, AppHashIterations) .Then(byteResult => { var hashedPassword = byteResult.ToHexString(); return(CommandResultFactory.Ok <string>(hashedPassword)); }); return(hashedPasswordResult.IsFailure ? givenPassword : hashedPasswordResult.Value); }
/// <summary> /// Sets up all the parameters and properties for the AES cipher used in the encryption. /// </summary> /// <param name="cipher"> /// The already created AES cipher. This should be created within a using statement and then handed /// off to this method. /// </param> /// <param name="key">The (HEXADECIMAL) key used for encryption inside the cipher</param> /// <param name="keySize">Key size used for setting up the cipher</param> public CommandResult <Aes> SetupCipher(Aes cipher, string key, AesKeySize keySize) { if (cipher == null) { return(CommandResultFactory.Fail("The given AES cipher object was null", (Aes)null)); } if (String.IsNullOrWhiteSpace(key)) { return(CommandResultFactory.Fail("The cipher creation key for the encryption was null or empty", (Aes)null)); } byte[] keyBytes; try { keyBytes = key.ToHexBytes(); if (keyBytes == null || keyBytes.Length != (int)keySize / 8) { return(CommandResultFactory.Fail($"The cipher creation key for the encryption did not match the specified key size of {keySize}", (Aes)null)); } } catch (FormatException) { return(CommandResultFactory.Fail("The key was malformed and therefore threw a Format Exception when converting to a byte array.", (Aes)null)); } catch (Exception e) { return(CommandResultFactory.Fail($"There was an exception thrown while trying to create the cipher. It is as follows:\n\t{e.Message}", (Aes)null)); } cipher.KeySize = (int)keySize; cipher.BlockSize = 128; // This is the default block size for AES encryption and apparently should not be changed according to what I'm reading cipher.Padding = PaddingMode.PKCS7; cipher.Mode = CipherMode.CBC; cipher.Key = keyBytes; cipher.GenerateIV(); return(CommandResultFactory.Ok(cipher)); }
/// <summary> /// Encrypts a string using the given key. To Decrypt you will need the proper initialization vector that gets randomly /// generated for each encryption process (i.e. different every time the encryption is run). This will happen /// automatically in /// our Decrypt method on this class because we're prefixing those initialization vectors with the encrypted text. /// </summary> /// <param name="textForEncryption">Text value to be encrypted</param> /// <param name="key"> /// MUST be a hexadecimal string that is at least 16 bytes in length. Should be more like 32 bytes in /// length /// </param> /// <param name="keySize">Size of the key used in creating the cipher</param> /// <param name="blockSize">Size of the block used in the creation of the Rijndael Managed cipher</param> public CommandResult <string> CustomEncrypt(string textForEncryption, string key, AesKeySize keySize, BlockSize blockSize) { if (String.IsNullOrWhiteSpace(textForEncryption)) { return(CommandResultFactory.Fail("There was nothing to encrypt", (string)null)); } if (String.IsNullOrWhiteSpace(key)) { return(CommandResultFactory.Fail("The given encryption key was null or empty", textForEncryption)); } try { var encryptResult = CreateCustomCipher(key, blockSize, keySize) .Then(cipher => { var initVector = cipher.IV.ToHexString(); // Create the encryptor, convert to bytes, and encrypt the plainText string var cryptoTransform = cipher.CreateEncryptor(); var plainTextBytes = textForEncryption.ToUtf8Bytes(); var cipherTextBytes = cryptoTransform.TransformFinalBlock(plainTextBytes, 0, plainTextBytes.Length); // Get the Hexadecimal string of the cipherTextBytes, hash it, and prefix the Initialization Vector to it. // We're using a hexadecimal string so that the cipherText can be used in URL's. Yes, there are other ways of doing that, but it's a style // choice. var cipherText = cipherTextBytes.ToHexString(); var encryptedText = initVector + "_" + cipherText; return(CommandResultFactory.Ok <string>(encryptedText)); }); return(encryptResult); } catch (Exception e) { Trace.WriteLine(e); return(CommandResultFactory.Fail($"An exception was thrown while attempting to encrypt the given text. It is as follows:\n\t{e.Message}", (string)null)); } }
/// <summary> /// Encrypts the given text using the given key and key size for cipher creation. /// </summary> /// <param name="textForEncryption">Text to encrypt</param> /// <param name="key">The (HEXADECIMAL) encryption key to use for creating the cipher</param> /// <param name="keySize">Size of the key used for creating the cipher</param> public CommandResult <string> Encrypt(string textForEncryption, string key, AesKeySize keySize) { if (String.IsNullOrWhiteSpace(textForEncryption)) { return(CommandResultFactory.Fail("There was nothing to encrypt", (string)null)); } if (String.IsNullOrWhiteSpace(key)) { return(CommandResultFactory.Fail("The given encryption key was null or empty", textForEncryption)); } try { CommandResult <string> result; using (var aesCipher = Aes.Create()) { result = SetupCipher(aesCipher, key, keySize) .Then(aes => { var initVector = aes.IV.ToHexString(); var textForEncryptBytes = textForEncryption.ToUtf8Bytes(); var cryptoTransform = aes.CreateEncryptor(); var cipherTextBytes = cryptoTransform.TransformFinalBlock(textForEncryptBytes, 0, textForEncryption.Length); var cipherText = cipherTextBytes.ToHexString(); var textResult = $"{initVector}_{cipherText}"; return(CommandResultFactory.Ok <string>(textResult)); }); } return(result); } catch (Exception e) { var message = $"An exception was thrown while trying to encrypt the text. It is as follows:\n{e.Message}"; return(CommandResultFactory.Fail(message, (string)null)); } }
/// <summary> /// Creates a RijndaelManaged cipher based on the given key material and the given block and key size. /// </summary> /// <param name="key">The (HEXADECIMAL) key used during the creation of the cipher for encryption</param> /// <param name="blockSize">Block size of the cipher</param> /// <param name="keySize">Key Size of the cipher</param> public CommandResult <RijndaelManaged> CreateCustomCipher(string key, BlockSize blockSize, AesKeySize keySize) { if (String.IsNullOrWhiteSpace(key)) { return(CommandResultFactory.Fail("There was no key provided for the cipher", (RijndaelManaged)null)); } byte[] byteKey; try { byteKey = key.ToHexBytes(); if (byteKey == null || byteKey.Length != (int)keySize / 8) { return(CommandResultFactory.Fail($"The cipher creation key for the encryption did not match the specified key size of {keySize}", (RijndaelManaged)null)); } } catch (FormatException) { return(CommandResultFactory.Fail("The key was malformed and therefore threw a Format Exception when converting to a byte array.", (RijndaelManaged)null)); } catch (Exception e) { return(CommandResultFactory.Fail($"There was an exception thrown while trying to create the cipher. It is as follows:\n\t{e.Message}", (RijndaelManaged)null)); } var cipher = new RijndaelManaged { KeySize = (int)keySize, BlockSize = (int)blockSize, Padding = PaddingMode.ISO10126, Mode = CipherMode.CBC, Key = byteKey }; return(CommandResultFactory.Ok(cipher)); }
/// <summary> /// Computes a hash for a given password and returns a <see cref="PasswordHashingData" /> object to hold the elements /// that made up the /// hashed password /// </summary> /// <param name="givenPassword"></param> public CommandResult <PasswordHashingData> HashPassword(string givenPassword) { if (String.IsNullOrWhiteSpace(givenPassword)) { return(CommandResultFactory.Fail("The given password was null or empty", (PasswordHashingData)null)); } var hashData = new PasswordHashingData(); var rand = new Random(); // Set the hash data hashData.NumberOfIterations = rand.Next(MinIterationRange, MaxIterationRange); hashData.SaltSize = rand.Next(MinSaltSize, MaxSaltSize); var saltByteArray = GetRandomSalt(hashData.SaltSize); hashData.Salt = saltByteArray.ToHexString(); // Run initial hash at an application level var appHashedPassword = GetAppLevelPasswordHash(givenPassword); // Take the output of the initial hashing and run it through proper hashing with key stretching var hashedPasswordResult = ComputePasswordAndSaltBytes(saltByteArray, appHashedPassword, hashData.NumberOfIterations) .Then(computedBytes => { var hashedPassword = computedBytes.ToHexString(); return(CommandResultFactory.Ok <string>(hashedPassword)); }); if (hashedPasswordResult.IsFailure) { return(CommandResultFactory.Fail(hashedPasswordResult.Message, hashData)); } hashData.HashedPassword = hashedPasswordResult.Value; return(CommandResultFactory.Ok(hashData)); }