Create Key ... Need Advice

S

shapper

Hello,

I need to create a Key from a user's username, created date and email.
I am sending that key by email so the user can click it to approve its
account.

I was creating the key as follows:

Sash sash = new Sash(new SHA256Managed(), 10);
Byte[] salt = Encoding.UTF8.GetBytes("80c2f9ce-
b8a4-4623-93b1-6771073e6a09");
Byte[] key = Encoding.UTF8.GetBytes(String.Concat(user.Username,
user.Email, user.Created.ToString()));
Byte[] hash = sash.CalculateHash(key, salt);
sash.Dispose();
return Convert.ToBase64String(hash);

Sash is a class that hashes a string with a salt.

I was doing that I get the following url:

http://domain.com/User/Approve/1/Key/WY7mDccz5Fp8kJQyocVXzubvlUp1/jNYTNTkmszFKKU=

That has two problems:

1. The "=" in the end is not considered part of the url

2. The / in the url "breaks" the url ...

So I would need something to create the key maybe only with numbers
and letters.

What do you advice me to do?

Thanks,
Miguel
 
P

Peter Duniho

shapper said:
[...]
I was doing that I get the following url:

http://domain.com/User/Approve/1/Key/WY7mDccz5Fp8kJQyocVXzubvlUp1/jNYTNTkmszFKKU=

That has two problems:

1. The "=" in the end is not considered part of the url

Why not? It shows up as part in my newsreader. If you're looking at
the output of a plain text email client, then you can blame the email
client and instruct the user to copy/paste. If you're sending the email
as HTML, then as long as the entire string is in the 'href' property of
an 'a' element, it should work fine.
2. The / in the url "breaks" the url ...

So I would need something to create the key maybe only with numbers
and letters.

What do you advice me to do? [...]

You can easily come up with a slightly different encoding, or just quote
the '/' character somehow. If you insist that the "=" is a problem, you
can easily just drop the '=', since it's essentially a
terminator/padding character. Just remember to add it back when
decoding the URL at the server (or just don't store it wherever you use
the URL...you're not clear on how this URL is used, but given that it's
a hash, presumably you're just generating it and then storing it
verbatim somewhere for the server to access later).

However, IMHO the worse problem is that you are trying to use a hash
value (which is not guaranteed unique) as a way of creating a unique
value. I believe you're reasonably safe against an intentional attack
(with SHA256), but there's still a non-zero chance of just accidentally
having a collision.

Some may (and probably will) argue that the chance is so small, it's not
worth worrying about. But I prefer code that I know _can't_ break,
rather than code that I think probably won't break.

So, what you really need is a way of encoding your input into a
completely unique byte[] that is then encoded. If you are trying to
secure against an attack, you'll want to do that with some encryption
included. Otherwise, it's possible just running the data through
GzipStream might be sufficient (with such little data, the length might
not get any shorter, but it at least would obfuscate the data).

IMHO, if it's not possible to retrieve all of the original data exactly
from the URL, the URL isn't a proper unique identifier for the user.

Finally note that all of the above assumes that you're simply using the
URL as a way of indexing the user somehow. Of course, you can address
the uniqueness problem even with a hash simply by making sure you aren't
already storing that particular index for some other user, once you've
generated it for the current user. If you find a collision, just do
something to resolve the collision, such as appending some extra text to
the URL or something. Again, if you're using a hash, you're obviously
using the result as an index somewhere, rather than decoding it again
later, so in the end it doesn't really matter exactly what the value of
the URL is, as long as it's unique.

Doing so would still meet the "retrieve all of the original data"
requirement, since you'd just use the uniquely-created string of the URL
as an index to the user data itself.

Pete
 
S

shapper

On Oct 21, 12:28 am, Peter Duniho <[email protected]>
wrote:

I got a little bit confused about your answer ...

Ok I got the = part.

I know the username is unique so the string which I am creating the
hash from is unique in the database.
I then hash that string and sent it in the URL to the user.

When the user accesses that url I use the ID in the url (example: 8),
to get the user and verify the key:

public Boolean ValidateKey(Int32 id, String key) {

User user = _userService.GetById(id);
Sash sash = new Sash(new SHA256Managed(), 10);
Byte[] salt = Encoding.UTF8.GetBytes("80c2f9ce-
b8a4-4623-93b1-6771073e6a09");
Boolean valid = sash.CheckHash(Encoding.UTF8.GetBytes(key),
salt, Encoding.UTF8.GetBytes(GetKey(user)));
sash.Dispose();
return valid;

}

So here I am generating the key the same way I did when sending it
(GetKey) and then check the hash with the key received in the url.

I use always the same sash for this functionality.

Probably I am missing something ...

About encryption I got lost ... Isn't this unique already?

And yes I am sending the email in Plain text ... I think it is a
better option then having it in HTML because of how some web email or
email software take some of the HTML emaisl or not?

Thanks,
Miguel
 
P

Peter Duniho

shapper said:
[...]
So here I am generating the key the same way I did when sending it
(GetKey) and then check the hash with the key received in the url.

I use always the same sash for this functionality.

Probably I am missing something ...

That depends. Your question is incomplete, so perhaps it's simply that
we (I) don't have all the information relevant.

In particular, if your URL encodes the user ID separately from the
hashed data (i.e. as plain-text somewhere in the URL), and the hash is
used _only_ for validation, then that's probably good enough.
About encryption I got lost ... Isn't this unique already?

The key? No. It's a hash. By definition, hashes aren't unique. But,
if you have the user ID in addition to the hash in the URL, that may be
unique. See above.
And yes I am sending the email in Plain text ... I think it is a
better option then having it in HTML because of how some web email or
email software take some of the HTML emaisl or not?

I hate HTML email. But using it does address the failure of email
clients failing to properly detect the end of the valid URL when sent by
plain text.

In any case, the main point is: if you are sending the URL as plain
text, you have no control over how the email client may interpret it.
So, don't waste time worrying about it. At most, include a little note
to the user to indicate that they may have to copy and paste the URL.
(Some email clients work well if you surround the URL with angle
brackets...definitely a "your mileage may vary" solution though).

Pete
 
S

shapper

In particular, if your URL encodes the user ID separately from the
hashed data (i.e. as plain-text somewhere in the URL), and the hash is
used _only_ for validation, then that's probably good enough.

Got it.
The key? No. It's a hash. By definition, hashes aren't unique. But,
if you have the user ID in addition to the hash in the URL, that may be
unique. See above.

Got it to.
At most, include a little note
to the user to indicate that they may have to copy and paste the URL.
(Some email clients work well if you surround the URL with angle
brackets...definitely a "your mileage may vary" solution though).

Good suggestions. Just improved them.

I have something that is confusing me ... It is a error I get.

I am using the following Key:
qg3Z6zXUYmkWV/vkCX+Bn29kRGWr6mDE57QIZJmdN8Q=

To check the key I have the following:
(10 in Sash constructor is just the number of repeats)
(For approval I use always the same salt from a predefined Guid)

Sash sash = new Sash(new SHA256Managed(), 10);
Byte[] salt = Encoding.UTF8.GetBytes("80c2f9ce-
b8a4-4623-93b1-6771073e6a09");
Boolean valid = sash.CheckHash(Encoding.UTF8.GetBytes(key), salt,
Encoding.UTF8.GetBytes(GetKey(user)));
sash.Dispose();

So basically I am checking the key using the salt and compared to the
original key which is not saved so I generated it again usng GetKey
(user):
(This was how I created the key on the first place when sending it to
the user):

public String GetKey(User user) {

Sash sash = new Sash(new SHA256Managed(), 10);
Byte[] salt = Encoding.UTF8.GetBytes("80c2f9ce-
b8a4-4623-93b1-6771073e6a09");
Byte[] key = Encoding.UTF8.GetBytes(String.Concat(user.Username,
user.Email, user.Created.ToString()));
Byte[] hash = sash.CalculateHash(key, salt);
sash.Dispose();
return Convert.ToBase64String(hash);

} // GetKey

I get an error inside the CheckHash:

public Boolean CheckHash(Byte[] data, Byte[] salt, Byte[]
expected) {

// Check parameters
if (data == null) throw new ArgumentNullException("data", "Data
cannot be null");
if (data.Length == 0) throw new ArgumentOutOfRangeException
("data", "Data cannot be empty");
if (salt == null) throw new ArgumentNullException("salt", "Salt
cannot be null");
if (salt.Length == 0) throw new ArgumentOutOfRangeException
("salt", "Salt cannot be empty");
if (expected == null) throw new ArgumentNullException
("expected", "Expected cannot be null");
if (expected.Length != algorithm.HashSize / 8) throw new
ArgumentOutOfRangeException("expected", "Expected length should be one
eight of algorithm hash size");

// CalculateHash
Byte[] actual = CalculateHash(data, salt);
for (Int32 i = 0; i < actual.Length; ++i) {
if (actual != expected) { return false; }
}
return true;

} // CheckHash

The error is:
Expected length should be one eight of algorithm hash size
Parameter name: expected

On CheckHash input the sizes of data, salt and expected are: 44, 36
and 44.

So expected should be 32 ...

I just can't figure why I end with this ...

Maybe I am missing something on my process.

Thanks,
Miguel
 
S

shapper

I created the following example and I always get the same error:

Sash sash = new Sash(new SHA256Managed(), 10);
Byte[] salt = Encoding.UTF8.GetBytes("80c2f9ce-
b8a4-4623-93b1-6771073e6a09");
Byte[] key = Encoding.UTF8.GetBytes(String.Concat("username",
"(e-mail address removed)", DateTime.UtcNow.ToString()));
Byte[] hash = sash.CalculateHash(key, salt);

String hashedKeySentByEmail = Convert.ToBase64String(hash);

Boolean valid = sash.CheckHash(Encoding.UTF8.GetBytes
(hashedKeySentByEmail), salt, hash);
sash.Dispose();

Thanks,
Miguel
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

Token Expiration Date. How? 11

Top