Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stateless password hash derivation with header specificity #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ nuget.exe
#*.sln
#*.csproj
#*.kproj
*.lock.json
*.lock.json
*.vs
51 changes: 46 additions & 5 deletions src/Scrypt/ScryptEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,22 @@ public class ScryptEncoder
/// <summary>
/// Size of salt in bytes.
/// </summary>
private const int SaltLength = 32;
public static readonly int SaltLength = 32;

private const int DefaultIterationCount = 16384;
private const int DefaultBlockSize = 8;
private const int DefaultThreadCount = 1;

private readonly int _iterationCount;
private readonly int _blockSize;
private readonly int _threadCount;
private readonly byte[] _salt;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is _salt a class attribute? This will make Encode not thread-safe anymore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @viniciuschiele! Thank you for the comment. The Encode method wasn't thread safe to begin with because RandomNumberGenerator.GetBytes is not thread safe. A general practice most follow is to make the caller responsible for resource locking for thread safety related to individual methods, unless the class itself is running multiple threads internally or using overlapping callbacks of some kind. The exception to this rule is usually for methods marked static, which should ensure their own thread safety. Let me know if you have any other feedback. Thanks!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, based on this, RNGCryptoServiceProvider is thread-safe and RandomNumberGenerator.Create() also returns a RNGCryptoServiceProvider by default based on that.
My concern is that this change can potentially break existing apps, that's why I think this shouldn't be part of this PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarification. After some research based on your comment, I see the issue now. I agree on the point of it breaking existing apps. Even though this varies by implementation, it shouldn't cause a behavioral change that breaks existing consumers of the library who use older versions of .NET, since you still support them. I'll go ahead and remove that change and return it to the original with a new commit tomorrow.

I did want to point out that in .NET Core and .NET Framework, the implementation now just invokes Windows API's BCryptGenRandom on Windows and OpenSSL's GetRandomBytes on Unix, so new consumers should not rely on thread safety of this method in modern codebases.

I really appreciate your thoughtful response and for exercising care around breaking your users. Thank you again and you can expect the commit tomorrow.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for providing the links to the source code, for me both implementations seem thread-safe and I couldn't find anything to say otherwise.

I agree that this may vary based on the implementation, I wonder if it would be better to instantiate a new RandomNumberGenerator every time the Encode is called.

private readonly RandomNumberGenerator _saltGenerator;

#endregion



#region Constructors

/// <summary>
Expand Down Expand Up @@ -94,6 +98,7 @@ public ScryptEncoder(int iterationCount, int blockSize, int threadCount, RandomN
_iterationCount = iterationCount;
_blockSize = blockSize;
_threadCount = threadCount;
_salt = new byte[SaltLength];
_saltGenerator = saltGenerator;
}

Expand Down Expand Up @@ -147,11 +152,9 @@ public string Encode(string password)
throw new ArgumentNullException("password");
}

var saltBytes = new byte[SaltLength];
_saltGenerator.GetBytes(_salt);

_saltGenerator.GetBytes(saltBytes);

return EncodeV2(password, saltBytes, _iterationCount, _blockSize, _threadCount);
return EncodeV2(password, _salt, _iterationCount, _blockSize, _threadCount);
}

/// <summary>
Expand Down Expand Up @@ -196,6 +199,44 @@ public bool IsValid(string hashedPassword)
return true;
}

/// <summary>
/// Hashes a password using the scrypt scheme.
/// </summary>
public static string Encode(int iterationCount, int blockSize, int threadCount, byte[] salt, string password)
{
if (iterationCount < 1)
{
throw new ArgumentOutOfRangeException("iterationCount", "IterationCount must be equal or greater than 1.");
}

if (blockSize < 1)
{
throw new ArgumentOutOfRangeException("blockSize", "BlockSize must be equal or greater than 1.");
}

if (threadCount < 1)
{
throw new ArgumentOutOfRangeException("threadCount", "ThreadCount must be equal or greater than 1.");
}

if (salt == null)
{
throw new ArgumentNullException("salt");
}

if (salt.Length != SaltLength)
{
throw new ArgumentOutOfRangeException("salt", String.Format("Salt length must be equal to {0} bytes.", SaltLength));
}

if (string.IsNullOrEmpty(password))
{
throw new ArgumentNullException("password");
}

return EncodeV2(password, salt, iterationCount, blockSize, threadCount);
}

/// <summary>
/// Hash a password using the scrypt scheme.
/// </summary>
Expand Down