diff --git a/CapyKit/EncryptedValue.cs b/CapyKit/EncryptedValue.cs new file mode 100644 index 0000000..dff9016 --- /dev/null +++ b/CapyKit/EncryptedValue.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CapyKit +{ + public class EncryptedValue + { + #region Members + + // + + #endregion + + #region Properties + + public T Value { get; set; } + + #endregion + } +} diff --git a/CapyKit/Helpers/EncryptionHelper.cs b/CapyKit/Helpers/EncryptionHelper.cs new file mode 100644 index 0000000..b82dcd0 --- /dev/null +++ b/CapyKit/Helpers/EncryptionHelper.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.ObjectiveC; +using System.Text; +using System.Threading.Tasks; + +namespace CapyKit.Helpers +{ + public class EncryptionHelper + { + #region Members + + private string encryptionKey; + + #endregion + + #region Constructors + + public EncryptionHelper(string encryptionKey) + { + this.encryptionKey = encryptionKey; + } + + #endregion + } + + public interface IEncryptionAlgorithm + { + #region Properties + + string AlgorithmName { get; } + + #endregion + + #region Methods + + EncryptedValue Encrypt(object obj, params object[] args); + + T Decrypt(EncryptedValue encryptedValue, params object[] args); + + #endregion + } +} diff --git a/CapyKit/Helpers/SecurityHelper.cs b/CapyKit/Helpers/SecurityHelper.cs index 0ecfc64..9f58541 100644 --- a/CapyKit/Helpers/SecurityHelper.cs +++ b/CapyKit/Helpers/SecurityHelper.cs @@ -15,8 +15,8 @@ namespace CapyKit.Helpers { #region Members - /// Default size of the generated salt. - private const int saltSize = 32; + /// Default size to use when generating a new salt. + private const int SALT_SIZE = 32; /// A string of all the lower case characters. internal const string LOWER_CASE_CHARACTERS = "abcdefghijklmnopqrstuvwxyz"; @@ -35,25 +35,131 @@ namespace CapyKit.Helpers #region Methods /// - /// Compares an unencrypted with a stored, encrypted - /// . + /// Compares an unencrypted with a stored, encrypted . + /// This method uses the specified password algorithm type to retrieve the hashed version + /// of the and then compares it with the . /// - /// The provided password, unencrypted. - /// The existing, encrypted password. + /// The type of the password hashing algorithm. + /// The existing, encrypted password. + /// The unencrypted password to be compared. + /// The salt value used in password hashing. + /// Additional arguments required for constructing the password algorithm instance. /// /// if hash comparison succeeds, if it fails. /// - public static bool CompareHashedPassword(string providedPassword, string existingPassword) + public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt, params object[] args) { - throw new NotImplementedException(); + var providedPassword = typeof(SecurityHelper) + .GetMethod("GetPassword") + ?.MakeGenericMethod(typeof(T)) + ?.Invoke(null, new object[] { password, salt, args }); + + return existingPassword.Equals(providedPassword); } - /// Hashes an unencrypted password. - /// The password. - /// The hashed password. - public static string HashPassword(string password) + /// + /// Compares an unencrypted with a stored, encrypted . + /// This method uses the specified to retrieve the hashed version + /// of the and then compares it with the . + /// + /// The existing, encrypted password. + /// The unencrypted password to be compared. + /// The salt value used in password hashing. + /// The password hashing algorithm. + /// Additional arguments required for constructing the password algorithm instance. + /// + /// if hash comparison succeeds, if it fails. + /// + public static bool CompareHashedPassword(Password existingPassword, string password, byte[] salt, IPasswordAlgorithm algorithm, params object[] args) { - throw new NotImplementedException(); + var algorithmType = algorithm.GetType(); + + var providedPassword = typeof(SecurityHelper) + .GetMethod("GetPassword") + ?.MakeGenericMethod(algorithmType) + ?.Invoke(null, new object[] { password, salt, args }); + + return existingPassword.Equals(providedPassword); + } + + /// + /// Retrieves a object using the specified password and generates a random salt value. + /// Then it uses that salt to call the overloaded method with the given password and + /// the generated salt as arguments. + /// + /// The type of implementation to use. + /// The plaintext password to be hashed. + /// + /// Optional constructor arguments for the implementation + /// instance. + /// + /// + /// A new object with the given password and a randomly generated salt, as well as an + /// instance of created using any optional constructor arguments provided. + /// + /// + public static Password GetPassword(string password, params object[] args) + { + var salt = SecurityHelper.GetRandomBytes(SecurityHelper.SALT_SIZE); + return GetPassword(password, salt, args); + } + + /// + /// Retrieves a object using the specified password, salt, and optional + /// constructor arguments. + /// + /// + /// This method uses reflection to find a constructor for the specified password algorithm type (). + /// It emits an error event if a suitable constructor is not found or if there is an error invoking the constructor. + /// + /// + /// The type of implementation to use. + /// + /// The plaintext password to be hashed. + /// + /// A random value used as an additional input to the one-way function that hashes data, a + /// password or passphrase. This is used to make each output different for the same input + /// thus adding security. + /// + /// + /// Optional constructor arguments for the implementation + /// instance. + /// + /// + /// A new object with the given password and salt, as well as an instance + /// of created using the provided constructor arguments. + /// + public static Password GetPassword(string password, byte[] salt, params object[] args) where T : IPasswordAlgorithm + { + var allArgs = args.Prepend(salt).Prepend(password).ToArray(); // Prepend in reverse order so that password precedes salt. + var argTypes = allArgs.Select(arg => arg.GetType()).ToArray(); + var algorithmConstructor = typeof(T).GetConstructor(argTypes); + + if (algorithmConstructor == null) + { + CapyEventReporter.EmitEvent(EventLevel.Error, "Cannot find a constructor for {0} that matches the given arguments: {1}", + args: new[] + { + typeof(T).Name, + string.Join(",", argTypes.Select(arg => arg.Name)) + }); + return default(Password); + } + + var passwordInstance = (T)algorithmConstructor.Invoke(allArgs); + + if (passwordInstance == null) + { + CapyEventReporter.EmitEvent(EventLevel.Error, "There was an error invoking the constructor for {0} with the given arguments: {1}", + args: new[] + { + typeof(T).Name, + string.Join(",", allArgs) + }); + return default(Password); + } + + return new Password(password, salt, passwordInstance, args); } /// @@ -95,7 +201,7 @@ namespace CapyKit.Helpers /// public static Password Pbkdf2(string password) { - var salt = SecurityHelper.GetRandomBytes(saltSize); + var salt = SecurityHelper.GetRandomBytes(SALT_SIZE); var pwd = new Password(password, salt, Password.Pbkdf2Algorithm); return pwd; @@ -186,6 +292,11 @@ namespace CapyKit.Helpers return buffer; } + /// + /// Static method that returns a valid character composition based on the given ValidCharacterCollection parameters. + /// + /// An array of ValidCharacterCollection enumeration values representing the desired character sets. + /// A string containing all the characters from the specified character sets. private static string GetValidCharacterComposition(params ValidCharacterCollection[] validCharacters) { var composition = new StringBuilder(); diff --git a/CapyKit/Password.cs b/CapyKit/Password.cs index 209b18d..179413d 100644 --- a/CapyKit/Password.cs +++ b/CapyKit/Password.cs @@ -16,7 +16,7 @@ namespace CapyKit { #region Members - private static Lazy algorithm = new Lazy(() => new Pbkdf2Algorithm()); + private static Lazy pbkdf2Algorithm = new Lazy(() => new Pbkdf2Algorithm()); #endregion @@ -37,13 +37,19 @@ namespace CapyKit /// public IPasswordAlgorithm Algorithm { get; private set; } + #region Preconfigued Password Algorithms + + /// Gets the preconfigured PBKDF2 algorithm. + /// The preconfigured PBKDF2 algorithm. public static Pbkdf2Algorithm Pbkdf2Algorithm { get { - return algorithm.Value; + return pbkdf2Algorithm.Value; } - } + } + + #endregion #endregion @@ -64,6 +70,25 @@ namespace CapyKit #region Methods + /// + public override bool Equals(object? obj) + { + if(obj == null) return false; // The object is null, and this object is not. + + if(ReferenceEquals(this, obj)) return true; // The object is literally this object. + + var objPassword = obj as Password; + + if (objPassword == null) + { + return base.Equals(obj); // Objects aren't the same type. We can fall back to the default comparison. + } + + return this.Algorithm.AlgorithmName == objPassword.Algorithm.AlgorithmName + && this.Hash == objPassword.Hash + && this.Salt == objPassword.Salt; + } + /// public override string ToString() { @@ -71,6 +96,24 @@ namespace CapyKit } #endregion + + #region Operators + + /// + public static bool operator ==(Password a, Password b) + { + return ReferenceEquals(a, b) // Literally the same object. + || (ReferenceEquals(a, null) && ReferenceEquals(b,null)) // Both are null + || a.Equals(b); // Both are not null but not the same object. + } + + /// + public static bool operator !=(Password a, Password b) + { + return !(a == b); + } + + #endregion } ///