/// <summary>
        /// Hash the given password to a Argon2 hash string.
        /// </summary>
        /// <param name="configToHash">
        /// Contains all the information used to create the hash returned.
        /// </param>
        /// <returns>
        /// The Argon2 hash of the given password.
        /// </returns>
        public static string Hash(Argon2Config configToHash)
        {
            var argon2 = new Argon2(configToHash);

            using var hash = argon2.Hash();
            return(argon2.config.EncodeString(hash.Buffer));
        }
        /// <summary>
        /// Verify the given Argon2 hash as being that of the given password.
        /// </summary>
        /// <param name="encoded">
        /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash.
        /// </param>
        /// <param name="password">
        /// The password to verify
        /// </param>
        /// <returns>
        /// True on success; false otherwise.
        /// </returns>
        public static bool Verify(
            string encoded,
            byte[] password)
        {
            SecureArray <byte> hash = null;

            try
            {
                var configToVerify = new Argon2Config {
                    Password = password
                };
                if (!configToVerify.DecodeString(encoded, out hash) || hash == null)
                {
                    return(false);
                }

                var hasherToVerify = new Argon2(configToVerify);
                var hashToVerify   = hasherToVerify.Hash();
                return(!hash.Buffer.Where((b, i) => b != hashToVerify[i]).Any());
            }
            finally
            {
                hash?.Dispose();
            }
        }
Пример #3
0
        /// <summary>
        /// Verify the given Argon2 hash as being that of the given password.
        /// </summary>
        /// <param name="encoded">
        /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash.
        /// </param>
        /// <param name="configToVerify">
        /// The configuration that contains the values used to created <paramref name="encoded"/>.
        /// </param>
        /// <returns>
        /// True on success; false otherwise.
        /// </returns>
        public static bool Verify(
            string encoded,
            Argon2Config configToVerify)
        {
            SecureArray <byte> hash = null;

            try
            {
                if (!configToVerify.DecodeString(encoded, out hash) || hash == null)
                {
                    return(false);
                }

                using (var hasherToVerify = new Argon2(configToVerify))
                {
                    using (var hashToVerify = hasherToVerify.Hash())
                    {
                        return(!hash.Buffer.Where((b, i) => b != hashToVerify[i]).Any());
                    }
                }
            }
            finally
            {
                hash?.Dispose();
            }
        }
Пример #4
0
        /// <summary>
        /// Encodes an Argon2 instance into a string.
        /// </summary>
        /// <param name="config">
        /// To encode.
        /// </param>
        /// <param name="hash">
        /// The hash to put in the encoded string. May be null.
        /// </param>
        /// <returns>
        /// The encoded Argon2 instance.
        /// </returns>
        /// <remarks>
        /// <para>
        /// Resulting format:
        /// </para>
        /// <para>
        /// $argon2&lt;T>[$v=&lt;num>]$m=&lt;num>,t=&lt;num>,p=&lt;num>[,keyid=&lt;bin>][,data=&lt;bin>][$&lt;bin>[$&lt;bin>]].
        /// </para>
        /// <para>
        /// where &lt;T> is either 'd' or 'i', &lt;num> is a decimal integer (positive, fits in
        /// an 'unsigned long'), and &lt;bin> is Base64-encoded data (no '=' padding
        /// characters, no newline or whitespace).
        /// The "keyid" is a binary identifier for a key (up to 8 bytes);
        /// "data" is associated data (up to 32 bytes). When the 'keyid'
        /// (resp. the 'data') is empty, then it is omitted from the output.
        /// </para>
        /// <para>
        /// The last two binary chunks (encoded in Base64) are, in that order,
        /// the salt and the output. Both are optional, but you cannot have an
        /// output without a salt. The binary salt length is between 8 and 48 bytes.
        /// The output length is always exactly 32 bytes.
        /// </para>
        /// </remarks>
        public static string EncodeString(this Argon2Config config, byte[] hash)
        {
            var dst = new StringBuilder();

            if (config.Type == Argon2Type.DataIndependentAddressing)
            {
                dst.Append("$argon2i$v=");
            }
            else if (config.Type == Argon2Type.DataDependentAddressing)
            {
                dst.Append("$argon2d$v=");
            }
            else if (config.Type == Argon2Type.HybridAddressing)
            {
                dst.Append("$argon2id$v=");
            }
            else
            {
                throw new ArgumentException(
                          $"Expected one of {Argon2Type.DataDependentAddressing}, "
                          + $"{Argon2Type.DataIndependentAddressing}, or {Argon2Type.HybridAddressing}, "
                          + $"got {config.Type}", nameof(config));
            }

            dst.AppendFormat("{0:D}", (int)config.Version);
            dst.Append("$m=");
            dst.AppendFormat("{0:D}", config.MemoryCost);
            dst.Append(",t=");
            dst.AppendFormat("{0:D}", config.TimeCost);
            dst.Append(",p=");
            dst.AppendFormat("{0:D}", config.Lanes);
            if (config.AssociatedData != null && config.AssociatedData.Length > 0)
            {
                dst.Append(",data=");
                dst.Append(config.AssociatedData.ToB64String());
            }

            if (config.Salt == null || config.Salt.Length == 0)
            {
                return(dst.ToString());
            }

            dst.Append("$");
            dst.Append(config.Salt.ToB64String());

            if (hash == null || hash.Length == 0)
            {
                return(dst.ToString());
            }

            dst.Append("$");
            dst.Append(hash.ToB64String());
            return(dst.ToString());
        }
        /// <summary>
        /// Verify the given Argon2 hash as being that of the given password.
        /// </summary>
        /// <param name="encoded">
        /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash.
        /// </param>
        /// <param name="password">
        /// The password to verify.
        /// </param>
        /// <param name="secret">
        /// The secret hashed into the password.
        /// </param>
        /// <param name="secureArrayCall">
        /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>.
        /// </param>
        /// <returns>
        /// True on success; false otherwise.
        /// </returns>
        public static bool Verify(
            string encoded,
            byte[] password,
            byte[] secret,
            SecureArrayCall secureArrayCall = null)
        {
            var configToVerify = new Argon2Config
            {
                Password        = password,
                Secret          = secret,
                SecureArrayCall = secureArrayCall ?? SecureArray.DefaultCall,
            };

            return(Verify(encoded, configToVerify));
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Argon2"/> class.
        /// </summary>
        /// <param name="config">
        /// The configuration to use.
        /// </param>
        public Argon2(Argon2Config config)
        {
            this.config = config;
            uint memoryBlocks = (uint)config.MemoryCost;

            if (memoryBlocks < 2 * SyncPoints * config.Lanes)
            {
                memoryBlocks = 2 * SyncPoints * (uint)config.Lanes;
            }

            this.SegmentLength = (int)(memoryBlocks / (config.Lanes * SyncPoints));

            // ensure that all segments have equal length
            this.LaneLength       = this.SegmentLength * SyncPoints;
            this.MemoryBlockCount = this.LaneLength * this.config.Lanes;
            this.memory           = BestSecureArray <ulong>(BlockSize * this.MemoryBlockCount / 8);
            this.Memory           = new Blocks(this.memory.Buffer, this.MemoryBlockCount);
        }
Пример #7
0
        /// <summary>
        /// Decodes an Argon2 hash string into an Argon2 class instance.
        /// </summary>
        /// <param name="config">
        /// The configuration to populate with the data found in <paramref name="str"/>.
        /// </param>
        /// <param name="str">
        /// The string to decode.
        /// </param>
        /// <param name="hash">
        /// Loaded with the hash found in <paramref name="str"/>; set to null if
        /// <paramref name="str"/> does not contain a hash.
        /// </param>
        /// <returns>
        /// True on success; false otherwise. <paramref name="hash"/> set to
        /// null on failure.
        /// </returns>
        /// <remarks>
        /// <para>
        /// Expected format:
        /// </para>
        /// <para>
        /// $argon2&lt;T>[$v=&lt;num>]$m=&lt;num>,t=&lt;num>,p=&lt;num>[,keyid=&lt;bin>][,data=&lt;bin>][$&lt;bin>[$&lt;bin>]].
        /// </para>
        /// <para>
        /// where &lt;T> is either 'd' or 'i', &lt;num> is a decimal integer (positive, fits in
        /// an 'unsigned long'), and &lt;bin> is Base64-encoded data (no '=' padding
        /// characters, no newline or whitespace).
        /// The "keyid" is a binary identifier for a key (up to 8 bytes);
        /// "data" is associated data (up to 32 bytes). When the 'keyid'
        /// (resp. the 'data') is empty, then it is ommitted from the output.
        /// </para>
        /// <para>
        /// The last two binary chunks (encoded in Base64) are, in that order,
        /// the salt and the output. Both are optional, but you cannot have an
        /// output without a salt. The binary salt length is between 8 and 48 bytes.
        /// The output length is always exactly 32 bytes.
        /// </para>
        /// </remarks>
        public static bool DecodeString(this Argon2Config config, string str, out SecureArray <byte> hash)
        {
            int        pos;
            Argon2Type type;

            if (str.StartsWith("$argon2id"))
            {
                type = Argon2Type.HybridAddressing;
                pos  = 9;
            }
            else if (str.StartsWith("$argon2i"))
            {
                type = Argon2Type.DataIndependentAddressing;
                pos  = 8;
            }
            else if (str.StartsWith("$argon2d"))
            {
                type = Argon2Type.DataDependentAddressing;
                pos  = 8;
            }
            else
            {
                hash = null;
                return(false);
            }

            var version = Argon2Version.Sixteen;

            /* Reading the version number if the default is suppressed */
            {
                var check = "$v=";
                if (string.Compare(str, pos, check, 0, check.Length) == 0)
                {
                    pos += check.Length;
                    pos  = DecodeDecimal(str, pos, out var decX);
                    if (pos < 0)
                    {
                        hash = null;
                        return(false);
                    }

                    version = (Argon2Version)decX;
                }
            }

            pos = DecodeDecimal(out var memoryCost, "$m=", str, pos);
            if (pos < 0)
            {
                hash = null;
                return(false);
            }

            pos = DecodeDecimal(out var timeCost, ",t=", str, pos);
            if (pos < 0)
            {
                hash = null;
                return(false);
            }

            pos = DecodeDecimal(out var lanes, ",p=", str, pos);
            if (pos < 0)
            {
                hash = null;
                return(false);
            }

            byte[] associatedData = null;
            {
                var check = ",data=";
                if (string.Compare(str, pos, check, 0, check.Length) == 0)
                {
                    pos += check.Length;
                    pos  = FromBase64(out associatedData, str, pos);
                    if (pos < 0)
                    {
                        hash = null;
                        return(false);
                    }
                }
            }

            var validator = new Argon2Config();

            if (pos == str.Length)
            {
                try
                {
                    validator.Type           = type;
                    validator.TimeCost       = (int)timeCost;
                    validator.MemoryCost     = (int)memoryCost;
                    validator.Lanes          = (int)lanes;
                    validator.AssociatedData = associatedData;
                    validator.Version        = version;
                }
                catch (Exception)
                {
                    hash = null;
                    return(false);
                }

                config.Type           = type;
                config.TimeCost       = (int)timeCost;
                config.MemoryCost     = (int)memoryCost;
                config.Lanes          = (int)lanes;
                config.AssociatedData = associatedData;
                config.Version        = version;
                hash = null;
                return(true);
            }

            pos = DecodeBase64(out var salt, "$", str, pos);
            if (pos < 0)
            {
                hash = null;
                return(false);
            }

            if (pos == str.Length)
            {
                try
                {
                    validator.Type           = type;
                    validator.TimeCost       = (int)timeCost;
                    validator.MemoryCost     = (int)memoryCost;
                    validator.Lanes          = (int)lanes;
                    validator.Salt           = salt;
                    validator.AssociatedData = associatedData;
                    validator.Version        = version;
                }
                catch (Exception)
                {
                    hash = null;
                    return(false);
                }

                config.Type           = type;
                config.TimeCost       = (int)timeCost;
                config.MemoryCost     = (int)memoryCost;
                config.Lanes          = (int)lanes;
                config.Salt           = salt;
                config.AssociatedData = associatedData;
                config.Version        = version;
                hash = null;
                return(true);
            }

            if (str[pos] != '$')
            {
                hash = null;
                return(false);
            }

            ++pos;
            int hashlen = Base64Length(str, pos);

            if (hashlen < 0)
            {
                hash = null;
                return(false);
            }

            SecureArray <byte> output;

            try
            {
                output = new SecureArray <byte>(hashlen, SecureArrayType.ZeroedPinnedAndNoSwap, config.SecureArrayCall);
            }
            catch (LockFailException)
            {
                output = new SecureArray <byte>(hashlen, SecureArrayType.ZeroedAndPinned, config.SecureArrayCall);
            }

            bool success = false;

            try
            {
                pos = FromBase64(output.Buffer, str, pos);
                if (pos < 0)
                {
                    hash = null;
                    return(false);
                }

                if (pos != str.Length)
                {
                    hash = null;
                    return(false);
                }

                try
                {
                    validator.Type           = type;
                    validator.TimeCost       = (int)timeCost;
                    validator.MemoryCost     = (int)memoryCost;
                    validator.Lanes          = (int)lanes;
                    validator.Salt           = salt;
                    validator.AssociatedData = associatedData;
                    validator.Version        = version;
                }
                catch (Exception)
                {
                    hash = null;
                    return(false);
                }

                config.Type           = type;
                config.TimeCost       = (int)timeCost;
                config.MemoryCost     = (int)memoryCost;
                config.Lanes          = (int)lanes;
                config.Salt           = salt;
                config.AssociatedData = associatedData;
                config.Version        = version;
                hash    = output;
                success = true;
                return(true);
            }
            finally
            {
                if (!success)
                {
                    output.Dispose();
                }
            }
        }