6

I'm using the .net port of libsodium. The hash generation function has two forms, one that accepts byte arrays and one that accepts strings:

public static byte[] ArgonHashBinary(string password, string salt, long opsLimit, int memLimit, long outputLength = ARGON_SALTBYTES)  

public static byte[] ArgonHashBinary(byte[] password, byte[] salt, long opsLimit, int memLimit, long outputLength = ARGON_SALTBYTES)

What i'm having an issue with is both forms producing the same hash when the input values are identical.

var saltAsBytes = PasswordHash.ArgonGenerateSalt();
var saltAsString = Encoding.UTF8.GetString(saltAsBytes);
var tmp = Encoding.UTF8.GetBytes(saltAsString);

var hash1 = PasswordHash.ArgonHashBinary(password, saltAsString, 6, 134217728, 16);
var hash2 = PasswordHash.ArgonHashBinary( Encoding.UTF8.GetBytes(password), saltAsBytes, 6, 134217728, 16);

Anything with "PasswordHash." is libsodium and not my code.

From the code above when i convert it from a string and then back to a byte array the byte array. The byte array array is always a different length. ArgonGenerateSalt() produces a byte array with a length of 16. When i convert it back from a string above its generally ~30 (different every time because of different salts produced).

Why am i converting to UTF8? Because thats what they are doing internally: https://github.com/adamcaudill/libsodium-net/blob/master/libsodium-net/PasswordHash.cs

public static byte[] ArgonHashBinary(string password, string salt, StrengthArgon limit = StrengthArgon.Interactive, long outputLength = ARGON_SALTBYTES)
    {
      return ArgonHashBinary(Encoding.UTF8.GetBytes(password), Encoding.UTF8.GetBytes(salt), limit, outputLength);
    }

When i convert the salt to a UTF8 string the hashing function will fail because they are checking the length of the byte array to make sure its 16 bytes. If i convert it to a ASCII string it works but produces a different hash (which is expected).

To clarify the hashing piece in this code is not the issue. Figuring out why tmp is different then saltAsBytes is the key.

11
  • Have you single-stepped the code in PasswordHash to see where things are going different? Commented Dec 22, 2016 at 16:44
  • @JimMischel Which Part? The most significant piece above is when i generate the salt and then covert it to a string and then back again to a byte array. Most of that code of course is my own. Only thing the library is doing is producing the random salt. What is most concerning the 'tmp' variable is not the same as 'saltAsBytes'. So i don't think this has anything to do with the hashing piece. Once i figure out why that's happening the hashing piece will probably work. Commented Dec 22, 2016 at 16:50
  • Are you sure the byte array contains only valid UTF-8 codes? I am not sure how GetBytes() works, when the array contains invalid code points. Commented Dec 22, 2016 at 17:01
  • 1
    @Theo: Where is the money? :-) ` var bytes = new byte[] { 255, 255, 255 }; var buf = Encoding.Unicode.GetString(bytes); var newbytes = Encoding.Unicode.GetBytes(buf); var n = String.Join(", ", newbytes); ` returns 255, 255, 253, 255 Commented Dec 22, 2016 at 17:21
  • 1
    @Theo See the test case in my answer and give the money to one homeless. Commented Dec 22, 2016 at 17:54

2 Answers 2

3

I think the problem here is that the ArgonGenerateSalt method doesn't return a UTF8 encoded string, it returns completely random bytes.

You can't decode random bytes as a UTF8 string and expect it to round trip. A trivial example to see where this blows up is to do the following:

var data = new byte[] { 128 };
var dataAsString = Encoding.UTF8.GetString( data );
var dataAsBytes = Encoding.UTF8.GetBytes( dataAsString );

After this, dataAsBytes will be 3 bytes (specifically 239, 191, 189).

Sign up to request clarification or add additional context in comments.

2 Comments

Which leads you to think that the 'salt' parameter for string version of `ArgonHashBinary' can never be generated by the library. You have to produce it yourself. If true seems like a bad design.
@coding4fun That certainly does seem to be a significant oversight.
3

Converting a byte array to string and then back again produced different results

A binary data may not be converted to string and then back to byte array using Encoding.[AnyEncoding].GetBytes and Encoding.[AnyEncoding].GetString

Instead use Convert.ToBase64String and Convert.FromBase64String

You can easily test...

var bytes = new byte[] { 255, 255, 255 }; 
var buf = Encoding.UTF8.GetString(bytes);
var newbytes = Encoding.UTF8.GetBytes(buf);

newbytes's length will be 9.....

Edit: This is the test case for @Theo

var bytes = new byte[] { 0, 216 }; //any new byte[] { X, 216 };
var buf = Encoding.Unicode.GetString(bytes);
var newbytes = Encoding.Unicode.GetBytes(buf); //253,255

4 Comments

Actually had that thought too before asking the question but unfortunately it doesn't work. When passing the string to the hashing function the length will be != 16 which causes a 'SaltOutOfRangeException' exception to happen
@coding4fun I don't get what you mean but you will need a reversable function between string and arbitray binary data.... Using encodings is not the correct way... You have other options like BitConverter.ToString or SoapHexBinary But base64 encoding is the most natural...
@coding4fun BTW: You should encode a byte array of length 16 and convert to Base64 string... Reverse of it will be again a 16 byte byte array
The salt you pass into the 'string' version of `ArgonGenerateSalt' after they convert it internally into a 'UTF8' must be 16 bytes long. Taking the salt the library produces and converting that to a Base64 String causes it to > 16 which causes an exception to be thrown.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.