diff --git a/CapyKit/Attributes/ValueFormatAttribute.cs b/CapyKit/Attributes/ValueFormatAttribute.cs
new file mode 100644
index 0000000..67c7ebc
--- /dev/null
+++ b/CapyKit/Attributes/ValueFormatAttribute.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CapyKit.Attributes
+{
+ ///
+ /// Custom attribute for formatting values in a specific way.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public class ValueFormatAttribute : Attribute
+ {
+ #region Properties
+
+ /// Gets or sets the format to use for formatting the value.
+ /// The format string used to format the value.
+ public string Format { get; private set; }
+
+ #endregion Properties
+
+ #region Constructors
+
+ ///
+ /// Default constructor. Initializes a new instance of the
+ /// class with an empty format string.
+ ///
+ public ValueFormatAttribute()
+ {
+ this.Format = string.Empty;
+ }
+
+ ///
+ /// Constructor. Initializes a new instance of the class with
+ /// the specified format string.
+ ///
+ /// The format string used to format the value.
+ public ValueFormatAttribute(string format)
+ {
+ this.Format = format;
+ }
+
+ #endregion Constructors
+
+ #region Methods
+
+ /// Gets a parameterized formatted string for the specified index.
+ /// (Optional) Zero-based index of the item in the string to format.
+ /// A formatted string with the specified index and format.
+ public string GetFormatParameterizedString(int index = 0)
+ {
+ return "{" + index + ":" + this.Format + "}";
+ }
+
+ #endregion Methods
+ }
+}
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
}
///