Take the 2-minute tour ×
Information Security Stack Exchange is a question and answer site for Information security professionals. It's 100% free, no registration required.

My site has a news subscription system. When an item is updated, a notification email goes out to everyone subscribed to that item.

You can un/subscribe via a form on each item's page.

I wanted to include a direct unsubscribe link in the notification emails. Then I figured people might be logged-out, or viewing the email on a different machine from their login (we don't allow users to be simultaneously logged-in on different machines), so I wanted to create a one-click unsubscribe function that does not require someone to be logged-in.

Naturally, this meant a (hopefully) unique token.

My test solution is this:

  1. when a user subscribes to an item, a token is generated via sha512 on a concatenated string of various things (username, datetime, itemid, etc.)
  2. the unsubscribe link includes the item ID, item type, and token as GET parameters
  3. if and only if all three things are matched on one row in the user_subscriptions database, the row is removed thereby ending the subscription

This works, and I suspect it is adequate for this non-security sensitive task of subscribing and unsubscribing to news posts.

What leads me to Security.SE is the larger question of how secure is such a token system for other actions?

sha512 yields a 128 character hash, plus you'd have to nail the ID and Type of the action you wish to perform...

share|improve this question
    
"for other actions" - the answer to your question (e.g., the security risks, the suitability of this approach) likely depends upon what those other activities may be. –  D.W. Feb 6 '12 at 7:18
add comment

4 Answers

One security problem with your approach is that everything that you are hashing may potentially be guessable. As a result, tokens may be guessable.

A simple fix is to generate the token randomly: use a random 128-bit string from a cryptographically random source (e.g., /dev/urandom or CryptGenRandom) to form a single-use token. Store the token in the database along with information about the desired action, and when you receive a request, if it contains a token found in the database, take the action suggested by that database entry and delete the token. If you take this approach, don't include anything like item ID, item type, etc., in the URL. Instead, store that in the database.

Alternatively, you could include some unguessable secret (e.g., a 128-bit cryptographic key) as part of the input to the hash function. When you receive the request, you'll need to recompute the hash function (using the parameters in the request) and make sure the provided token is correct. Make sure that all of the parameters in the URL are included as part of the input to the hash function (otherwise someone could change that parameter without invalidating the token).

As @AaronS notes, if someone forwards the email, then the recipient of the forwarded email learns the token. Thus, this scheme is not perfect. For some uses (e.g., unsubscribing), it may be good enough. For others (e.g., managing access to an online banking account), it is not a good choice.

share|improve this answer
add comment

You want a MAC. That's what your custom hash is about: a way to make sure that the URL on which the user clicks is a "genuine" URL for that user, i.e. a URL that your system produced, not something computed by someone else. This leads to two solutions:

  1. Produce a per-user random token, and store it in the user table. When sending an email to a user, include the "unsubscribe" URL which includes the random token and the name of the target item (the one from which the user shall be unsubscribed). Don't bother putting any other information in the URL, the random token is sufficient. On your server, you maintain an index on the column which contains the random tokens, so that you can easily map tokens to user ID. When the Web server receives a request for a URL-with-token, unsubscribe the user.

    This will work as long as you produce your random tokens with a cryptographically strong PRNG (e.g. /dev/urandom on a Linux system) and make them large enough to lower "lucky attempts" success rate to negligibly small values. 16 bytes ought to be enough.

  2. Keep a server-side secret key. To compute an unsubscribe URL for an item and a user, include in the URL three elements: the target item, the user ID, and a MAC computed over the two other values (HMAC/SHA-256 will be fine for that), using the server key as MAC key. When the Web server receives an unsubscribe request, extract the elements and recompute the MAC: if it matches the one in the URL, then proceed to the unsubscription.

    Compared to the previous method, this one requires no extra storage in the database. On the other hand, you now have a key to manage; this can be a bit awkward in some setups, especially when there are several frontend servers.

Of course, the whole concept of the one-click unsubscribe URL assumes that the email contents are sufficiently confidential, with regards to what is at stake (since unsubscription to news items is a rather trivial matter, email "security" is probably sufficient).

share|improve this answer
add comment

People can forward your email to their friends and then friend can click on the link. So, especially if you don't change this hash for each email, it's not really a secret.

If you need “non-repudiation” – this is not solution.

A

share|improve this answer
    
It's only meant to be secret in that the odds someone could guess and unsubscribe another person are vanishingly small. If I forward the email to you and you unsubscribe me, that's not violating the original goal of the system. –  Andrew Heath Feb 6 '12 at 6:17
    
And the hash is user/item/time specific to each subscription. If I subscribed, unsubscribed, and subscribed again to the same item then the resulting hash would not match that of the first subscription. –  Andrew Heath Feb 6 '12 at 6:18
add comment

when a user subscribes to an item, a token is generated via sha512 on a concatenated string of various things (username, datetime, itemid, etc.)

To ensure the crypto is solid here use a well-accepted Message Authentication Code such as HMAC-SHA to sign the details, in preference to a straightforward hash.

As well as the expiry timestamp, you could also consider a user-specific sequence number that gets updated every time a user changes their password, so that a password change invalidates previously-issued tokens.

You will need to ensure that the signed data do not change meaning. For example that any unique IDs are not re-used after a deletion, so that the item referred to in a token does not change. Also if any resource has mutable permissions, you will need to do authorisation at both generation and receipt time, so that a token issued for an action doesn't give a user permission to carry that action out after a change that would remove their permissions.

how secure is such a token system for other actions?

An HMAC-signed token of suitable length is generally tight enough. The worry would be how you distribute them—clearly e-mail is not a wonderfully secure channel.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.