Would this cookie system be secure for storeing passwords? - security

I'd like input on this cookie password storing security system,
When the user ticks the remember me box, it stores these cookies:
User name in plain text.
Password encrypted with a completely random key that the server stores in the database, that is never passed to the client and is user-specific, changes with every login.
And the server then decodes the password with the encryption key when needed.

An attacker could steal somebody else's cookie and then be logged in without having to know the actual password. They would just send the same cookie and would get in. Being able to sniff traffic off the wire and then resend it later is a replay attack.
The best defense is to use SSL so the security is end to end. If you're running a serious commercial site then you should use SSL, no ifs ands or buts. Using SSL cookies will always be encrypted over the wire so it doesn't matter quite so much what their contents are as the attack vector changes from packet-sniffing to having to read the cookie off end users' hard drives.
If your site is not quite so serious, then read on.
On my site, I take the user's password and concatenate their IP address plus a secret token and hash all of those. That hash is stored in a cookie. Then to authenticate them on the server I recalculate the hash and verify that the one they sent matches.
This ties the cookie to a particular IP address so it cannot be so easily reused by a third party. It also eliminates any danger of decrypting the cookie and discovering the password since the hash (SHA256, say) is one-way and cannot be reversed.
Also, I hope you are not storing raw plaintext passwords on your database. You are storing password hashes, yes? And are also salting them to prevent rainbow table attacks?

No offence intended, but why do you want to reinvent the wheel?
Many have already implemented their own versions of this particular wheel so why not search SourceForge? Reusable software - can you code this more quickly than you can find an acceptable (and tested) solution?
Use off the shelf building blocks for the grunt work and move on to the interesting parts ;-)

Per #caf, adding this as an answer:
If you're going to go down this path, where the cookie basically stores the server's secret, there's absolutely no reason for that secret to have anything to do with the user's password. The client can't interpret the meaning/value of that data anyway.
To the client, it's just random data since it doesn't know the key
To the server, it's no different from any other data that is
Unique to the client, and
Secret to the server
To an attacker, it's neither more, nor less random (or similarly, hard to brute-force) than a random number of equal bit length.
In essence, that encrypted data is just an authentication token with special rules for how the token was generated. But making the token the user's encrypted password only adds more risk, because now if an attacker somehow gets the encryption/decryption key from the server, they have your user's password. You've made the token itself an attractive target since it has intrinsic value.
So instead of storing the encrypted password in the cookie and the encryption key in the database, you could just have the server generate a sufficiently long random number and store it in both the DB and the cookie. Since it has no meaning to the client anyway, it doesn't really matter what you use for the token, as long it's random, hard to guess and the server can verify it. Don't bother making it have a correlation with the password.

As Leonix said, this is probably better left to someone else who has developed it before and fixed all the bugs. Especially because it's security related.
Other than that, a glaring flaw I can spot is lack of authentication, or susceptibility to a 'replay' attack, where someone just blindly sends the cookie data that they could of copied from whom they want to impersonate.

Related

Is it acceptable to publicly expose salts?

I'm building a REST API for a mobile app. I need to handle authentication. As far as I could find, these are the best practices regarding password storing.
On the client side-register
Let user choose a password
Create a sufficiently long random salt from a strong random number generator
Append the the salt to the password and hash it.
Send the hash and salt to the server and store it in database
Server side-log in
Expose a method that returns user's salt for a given userid(email)
Client side-log in
User types in his userid and password
Retrieve the salt for the given userid
Append the the salt to the password and hash it.
Send the hash to the server, check that it's the same and authenticate user
Obviously the week point in this approach is that an attacker can see the salt for any user effortlessly. Is this ok? Obviously I can store the salt on the client side as well but if e.g. the user is logging in from a different phone he has to be able to retrieve the salt.
The alternative to this is to send the password in plaintext and handle all the salting and hashing on the server side. I would use HTTPS.
Which of these approaches is preferable? Is there a third one I'm missing?
Obviously the week point in this approach is that an attacker can see the salt for any user effortlessly.
No, the weak point is that your server is trusting the client. Here is MSDN's brief and pointed take on the situation:
You should never trust user input directly, especially if the user input is anonymous. Remember the two golden rules: never trust user input, and always check data as it moves from an untrusted to a trusted domain.
Source: Do Not Trust User Input Directly
Here you have user data moving from their untrusted client to your server. You need to verify their input on your server. There is no attack that you can prevent by performing the generation of the salt and the hashing of the password on the client. The ONLY way to secure the server-client communication is with secure handshake key exchange for asymmetric encryption. This occurs over HTTPS, and once the link is secure, then sending the password in plaintext is no less dangerous than sending the code to the client to perform the hashing client side.
If you do not secure the connection to the client with HTTPS, you are always vulnerable to a Man-in-the-Middle attack. Even though the password is nominally obscured when returning to your server, an attacker could instead impersonate your server to the client, sending an HTML form that skips the hash or sends the password in plaintext to their server as well.
Exposing the salt in your scenario is an additional security hole, but it is minor next to the glaring hole that would allow an attacker to get the password directly. I can explain this attack further if you desire, but it is a totally pointless attack unless the password itself is better secured; and once that happens, you have no need to expose the salt in the first place.
Responding to comments, it is true that salts can be made publicly known without reducing security if you are certain that they were generated correctly. For a salt, that means they are of sufficient length, sufficiently random, and in some schemes that they are cryptographically secure. The problem is that you allow the client to generate the salt, which means, I think we all agree now, that they can't be trusted. Maybe an attacker corrupted an external library you're relying on for PRNG. Maybe some user devices have an insufficient entropy pool. Maybe their device isn't secure against side-channel attacks. If you're making an Android App, there are certainly some very insecure devices out there. In Apple's walled garden, these risks might be tolerably low, but you can never eliminate them.
Like I said originally, however, these risks are minuscule next to the real dangers of your scheme. Namely, you're transmitting the hashed passed in the clear to your server. Even if you hash it further before storing it in your database, so a SQL injection can't just dump all the user passwords, you're still vulnerable to Man-in-the-Middle attacks. Unencrypted HTTP traffic can be observed and recorded by any router it passes through. Once an attacker observes a hashed password, they can re-send the same request to your server, and be authenticated incorrectly.
You should strictly go for the alternative, "to send the password in plaintext and handle all the salting and hashing on the server side. I would use HTTPS."
Of course the sentence above is highly contradictory: if you would be using HTTPS then the password would not be in plaintext. You should use HTTPS.
At the server you can store the randomly generated salt as plaintext in the database. You should not opt for a normal hash but a password hash - such as one generated by PBKDF2 - instead.
The salt is there if your DB gets stolen: the salt will make sure that identical passwords are not revealed to be identical. Furthermore the salt will make it impossible to use rainbow tables with precomputed password hashes.
Two important things to notice: for web clients you will have to rely on the trusted TLS certificate store for creating a secure TLS connection. Furthermore, your client may not have access to a good random number generator.

Can I securely implement cookieless "keep me signed in" using localstorage in a mobile app?

I don't want users to have to enter their passwords each time, and I don't want to store the passwords locally. When the user first sends me their username and password, I'm planning to send back some hashed version of their password (hash of pasword+salt), which will be put into localstorage to be used to authorize subsequent calls to our web services.
I'm aware that if my localstorage is compromised (e.g. stolen phone), the thief will be able to steal the token and make web service calls as the user, but at least they won't have the user's password.
Are there other vulnerabilities that I'm overlooking? Is there a reason to put it in a cookie instead of localstorage?
There are a few things to consider. You should use SSL to protect against eavesdropping. Otherwise any peer (someone else at the same insecure WiFi spot as your user) may be able to record your hashed password and replay requests with it. Also, the strength of the hashing algorithm is important. With a known username and a weak password it may be possible to generate a table of potential hashes and test that against the known hash (if the attacker manages to compromise the hash).
Regarding the difference between this and cookies, it's pretty simple. Cookies are usually randomly generated by the Server and aren't derived from the password at all. Other than that, cookies are pretty much a shortcut to what you're talking about. Whether you save it in your own storage within your app or to the HTTP library's built-in cookie handler, doesn't really matter. As long as you make sure to use SSL to protect against the eavesdropping/replay attack you should be pretty safe.
Also, I would recommend that you use a server generated hash so that you take the user password out of the equation completely.

Login system, security

I need to make a log-in system and having basically no previous knowledge of how it's done (with security in mind) I studied it on the internet. The way I would do it now is something like this:
Server has login information in a database table - username and a password hash per user (encrypted with SHA224 for example).
When client wants to authenticate, password is encrypted with SHA224 (client-side) and sent with username to the server to verify a match in the database.
If the user ticked "Remember me" option, an authentication key is generated on the server, inserted into a database along with the IP of the client.
The authentication key is sent to the client and stored in cookies.
Now, when the client returns, authentication key from cookies is sent to the server, the server finds it in the database and checks if the IPs match as well. If it does, the user is authenticated and a new authentication key is generated and sent to the user (and stored in cookies) for next visit.
My questions are:
How does encrypting password make this any safer? The hash still can be captured on the way from client to server and misused just as well as if it was plaintext. I know that this is an elementary question but I somehow couldn't find an answer to this one.
Is this security system secure enough? (or better yet - Did I get it right?)
Why does hashing a password make the system more secure
Hashing is not equal to encryption. Encrypted data can be decrypted back into plain text. Hashed data cannot be decrypted.
By hashing your user's passwords, nobody can see what passwords are used. So if your data gets stolen, the hashes cannot be decrypted by the hacker. The same goes for the system administrator, he/she cannot 'lookup' a password. This can be an all to common scenario in shared hosting environments.
Storing passwords
The easiest way to get your password storage scheme secure is by using a standard library.
Because security tends to be a lot more complicated and with more invisible screw up possibilities than most programmers could tackle alone, using a standard library is almost always easiest and most secure (if not the only) available option.
The good thing is that you do not need to worry about the details, those details have been programmed by people with experience and reviewed by many folks on the internet.
For more information on password storage schemes, read Jeff`s blog post: You're Probably Storing Passwords Incorrectly
Whatever you do if you go for the 'I'll do it myself, thank you' approach, do not use MD5 anymore. It is a nice hashing algorithm, but broken for security purposes.
Currently, using crypt, with CRYPT_BLOWFISH is the best practice.
From my answer to: Help me make my password storage safe
As for the infamous remember me option.
Create a random token and give it to the user in the form of a cookie.
If the user presents a cookie with this token, you give them access. Key is to only accept each token once. So after it is used, replace it with a new random token.
This token is, in essence, just another password. So in order to keep it safe, you do not store the token, but a hash of it. (just as you did with the password)
Your suggestion of binding the cookie to an IP-address will unfortunately not work. Many people have dynamic IP-addresses, some even change from request to request during a single session. (this is for example caused by load-balancing proxies).
Sending passwords to the server
The only method currently usable for sending a password from a web browser to server is by using a SSL-secured connection. Anything else will not be safe, as you cannot guarantee the integrity of the solution on the client side.
Some points I want to add:
the hashing of the password is not done on the client. You cannot do it reliably. The necessary technique for computing the hash (JavaScript in your case) might not be available and you cannot trust the result. If somebody can retrieve the hashes of the passwords in your database he could just login without knowing the actual passwords.
make sure to use SSL or another secure transport for transmitting the given passwords from the client to the server. SSL is a good idea for everything after all.
you should not use a single hash algorithm for storing the passwords in the database. Have a look at HMAC. That is far better. Additionally read about salts in cryptography.
Never ever invent your own crypto
mechanisms. Use someone else's.
Crypto is beyond tricky, and unless
you're Bruce Schneier, you have an
extremely slim chance of improving
it, while having a huge chance of
screwing it royaly.
Do not encrypt passwords, hash them.
If you're using hashes, salt them.
If you don't have
to use straight hashes, use HMAC,
they're much more resistant to
precalculated attacks.
If you're
sending stuff across an unsecure
link, add a NONCE to the transmission
to prevent replay attacks. This goes
for both client->server and
server->client.
If you're using salts and nonces, make sure they have high entropy. 'secret' is not a good one. Make it random, make it long, make it use large character sets. The extra computation cost is minimal, but the security you gain from it is enormous. If you're not sure how, use a random password generator, and then use ent to measure entropy.
Do NOT use a
timestamp as a nonce, unless you have
a very specific need and really know
what you're doing.
Use session
protection. SSL isn't perfect but
it's helluva better than nothing.
If you're using SSL, make sure to disable weak protocols. SSL session starts with 'offerings' of lists of ciphers both sides can do. If you let clients use a weak one, an attacker will definitely use that.

Should I hash the password before sending it to the server side?

I noticed that most sites send the passwords as plain text over HTTPS to the server. Is there any advantage if instead of that I sent the hash of the password to the server? Would it be more secure?
This is an old question, but I felt the need to provide my opinion on this important matter. There is so much misinformation here
The OP never mentioned sending the password in clear over HTTP - only HTTPS, yet many seem to be responding to the question of sending a password over HTTP for some reason. That said:
I believe passwords should never be retained (let alone transmitted) in plain text. That means not kept on disk, or even in memory.
People responding here seem to think HTTPS is a silver bullet, which it is not. It certainly helps greatly however, and should be used in any authenticated session.
There is really no need to know what an original password is. All that is required is a reliable way to generate (and reliably re-generate) an authentication "key" based on the original text chosen by the user. In an ideal world this text should immediately generate a "key" by salting then irreversibly hashing it using an intentionally slow hash-algorithm (like bcrypt, to prevent Brute-force). Said salt should be unique to the user credential being generated.
This "key" will be what your systems use as a password. This way if your systems ever get compromised in the future, these credentials will only ever be useful against your own organisation, and nowhere else where the user has been lazy and used the same password.
So we have a key. Now we need to clean up any trace of the password on the clients device.
Next we need to get that key to your systems. You should never transmit a key or password "in the clear". Not even over HTTPS. HTTPS is not impenetrable. In fact, many organisations can become a trusted MITM - not from an attack perspective, but to perform inspections on the traffic to implement their own security policies. This weakens HTTPS, and it is not the only way it happens (such as redirects to HTTP MITM attacks for example). Never assume it is secure.
To get around this, we encrypt the key with a once off nonce.
This nonce is unique for every submission of a key to your systems - even for the same credential during the same session if you need to send it multiple times. You can reverse said nonce (decrypt), once it arrives in your own systems to recover the authentication key, and authenticate the request.
At this point I would irreversibly hash it one last time before it is permanently stored in your own systems. That way you can share the credential's salt with partner organisations for the purposes of SSO and the like, whilst being able to prove your own organisation cannot impersonate the user. The best part of this approach is you are never sharing anything generated by the user without their authorisation.
Do more research, as there is more to it than even I have divulged, but if you want to provide true security to your users, I think this method is currently the most complete response here.
TL;DR:
Use HTTPS.
Securely hash passwords, irreversibly, with a unique salt per password. Do this on the client - do not transmit their actual password. Transmitting the users original password to your servers is never "OK" or "Fine". Clean up any trace of the original password.
Use a nonce regardless of HTTP/HTTPS. It is much more secure on many levels. (Answer to OP).
Since it's over HTTPS, it's definitely just fine to send the password without hashing (over HTTPS it's not plaintext). Furthermore, if your application is depending on HTTPS to keep it's content secure, then it's useless to hash the password before sending it over HTTPS (i.e. if an attacker can unencrypt the data on the wire, you're screwed anyways)
No, in fact this would be a vulnerability. If the attacker is able to obtain the hash from the database, then he could use it to authenticate without needing to crack it. Under no circumstance should a user or an attacker be able to obtain a hashes password.
The whole point of hashing passwords is to add an extra layer of security. If an attacker is able to obtain the hash and salt from the database using SQL Injection or an insecure backup then he has to find the plain text by brute forcing it. John The Ripper is commonly used to break salted password hashes.
Not using https is a violation of the OWASP Top 10: A9-Insufficient Transport Layer Protection
EDIT:
If in your implementation you calculate a sha256(client_salt+plain_text_password) and then calculate another hash on the server side sha256(server_salt+client_hash) then this is not a serious vulnerability. However, it is still susceptible to eavesdropping and replaying the request. Thus this is still a clear violation of WASP A9. However, this is still utilizing a message digest as a security layer.
The closest thing i have seen to a client-side replacement for https is a diffie-hellman in key exchange in javascript. However, this does prevent active MITM attacks and thus is till technicality a violation of OWASP A9. The Authors of the code agree that this is not a complete replacement for HTTPS, however it is better than nothing and better than a client-side hashing system.
Sending a hash over the wire completely defeats the purpose of the hash, because an attacker can simply send the hash and forget about the password. In a nutshell, a system that athenticates using a hash in clear text is wide open and can be compromise with nothing more than network sniffing.
The password in plaintext show never (not even when using HTTPS) leave the client. It should be irreversibly hashed before leaving the client as there is no need for the server to know the actual password.
Hashing then transmitting solves security issues for lazy users that use the same password in multiple locations (I know I do). However this does not protect your application as a hacker that gained access to the database (or in any other way was able to get his hands on the hash) as the hacker could just transmit the hash and have the server accept it.
To solve this issue you could of course just hash the hash the server receives and call it a day.
My approach to the issue in a socket-based web application I'm creating is that on connection to the client the server generates a salt (random string to be added before hashing) and stores it on the sockets variable, then it transmits this hash to the client. The client takes the users password, hashes it, adds the salt from the server and hashes the whole thing, before transmitting it to the server. Then it's sent to the server which compares this hash to the hash(hash in the DB + salt). As far as I know this is a good approach, but to be fair I haven't read a lot on the topic and if I'm wrong about anything I'd love to be corrected :)
Disclaimer: I'm by no stretch a security expert-- and I'm posting with the hope that others will critique my position as overly cautious or improvable and I will learn from it. With that said, I just want to emphasize that hashing when it leaves your client doesn't mean you get to don't have to hash on the backend before putting it in the database.
Do both
Do both because:
Hashing on the ride over helps cover vulnerabilities of transport, if SSL connection is compromised, they still can't see the raw password. It won't matter in terms of being able to impersonate authorized users, but it will protect your users from having their passwords read in association w/ their email. Most people don't follow best practice and use the same password for many their accounts, so this can be a serious vulnerability to your visitors.
If someone, somehow was able to read passwords from the database (this does happen, think SQL injection), they still won't be able to execute privileged actions impersonating users through my API. This is because of hash asymmetry; even if they know the hash stored in your DB, they won't know the original key used to create it and that's what your auth middleware uses to authenticate. This is also why you should always salt your hash storage.
Granted, they could do a lot of other damage if they had free rein to read what they want from your database.
I just want to emphasize here that if you do decide to hash the key before departure from your clients, that isn't enough-- the backend hashing is, imo, much more important and this is why: If someone is intercepting traffic from your client, then they will see the contents of the password field. Whether this is a hash, or plain text, it doesn't matter-- they can copy it verbatim to impersonate an authorized client. (Unless you follow the steps which #user3299591 outlines, and I recommend you do). Hashing the DB column, on the other hand, is a necessity and not at all difficult to implement.
Use HTTP Digest - it secures the password even over http (but best useage would be http digest over https)
Wikipedia:
HTTP digest access authentication is one of the agreed methods a web server can use to negotiate credentials with a web user (using the HTTP protocol). Digest authentication is intended to supersede unencrypted use of the Basic access authentication, allowing user identity to be established securely without having to send a password in plaintext over the network. Digest authentication is basically an application of MD5 cryptographic hashing with usage of nonce values to prevent cryptanalysis.
Link: http://en.wikipedia.org/wiki/Digest_access_authentication
If you want to see a "real life" use, you could look at phpMyID - a php openid provider that uses http digest authentication http://siege.org/phpmyid.php
.. or you could start from the php auth samples at http://php.net/manual/en/features.http-auth.php
Http digest rfc: http://www.faqs.org/rfcs/rfc2617
From my tests all modern browsers support it...
If you're looking to replace a clear-text password over HTTPS with a hashed password over HTTP then you're asking for trouble. HTTPS generates a random, shared transaction key when opening up a communication channel. That's hard to crack, as you're pretty much limited to brute forcing the shared key used for a (relatively) short-term transaction. Whereas your hash can be just sniffed, taken off-line and looked up in a rainbow table or just brute forced over a long amount of time.
However, a basic client-side password obfuscation (not hash) sent over HTTPS does have some value. If I'm not mistaken this technique is actually used by some banks. The purpose of this technique is not to protect the password from sniffing over the wire. Rather, it's to stop the password from being usable to dumb spying tools and browser plug-ins that just grab every HTTPS GET/POST request that they see. I've seen a log file captured from a malicious website that was 400MB of random GET/POST transactions captured from user sessions. You can imagine that websites that used just HTTPS would show up with clear-text passwords in the log, but websites with very basic obfuscation (ROT13) as well would show up with passwords that are not immediately of use.
Whether there's an advantage, and whether it's more (or less) secure really depends on implementation. There's arguably some advantage, but if you implement it poorly, you could definitely create a solution that is less secure than passing even a plaintext password.
This can be looked at from the perspective of two types of attacks-- one with access to the network traffic, and another with access to the database.
If your attacker can intercept the plaintext version of the network traffic, then seeing a hash of the password is more secure than seeing the password in plaintext. Although the attacker could still log in to your server using that hash, it would require a brute-force crack (sometimes pre-computed) of that hash to determine the password that might be useful on other systems. People should use different passwords on different systems, but often don't.
If an attacker gained access to the database, perhaps through a copy of a backup, then you'd want to ensure that one couldn't log in with only that knowledge. If, for example, you stored a hash salted with the login name as hash(login_name+password), and you passed that same hash from the client for direct comparison, then the attacker could pick a user at random, send the hash read from the database and log in as that user without knowing the password, increasing the scope of the breach. In that case, sending the password in plaintext would have been more secure because the attacker would need to know the plaintext in order to log in, even having a copy of the database. This is where implementation is key. Whether you send a plaintext password or a client-side hash of that password, you should hash that value at the server-side and compare that hash with the hash stored in the user record.
Concepts to keep in mind:
You "salt" a hash by mixing in some scope-unique value to your hash, typically row-unique. Its purpose is to guarantee uniqueness of hashes from each other even if the plaintext values they represent are the same, so two users with the same password would still have different hashes. It's unnecessary to treat a salt as a secret.
When authenticating, always hash on the server-side whatever value you pass from the client as a password (even if it's already hashed) and compare it with a pre-hashed value stored on the database. This may necessitate storing a double-hashed version of the original password.
When you make a hash, consider adding a server/cluster-unique salt to the hash as well as a row-unique salt to safeguard against matching any pre-computed hashes in lookup tables.
If you're connected to an https server the data stream between the server and browser should be encrypted. The data is only plain text before being sent and after being recieved. Wikipedia article
If you want to achieve the same reliability as when transferring over https, then yes - one option - the implementation of an asymmetrically encrypted channel at the ajax level.
If we are not talking about registration (for example, the first transmission of the password is always protected), then there are options.
For example,
The server generates a random string and generates a salt sends it to the user.
The user calculates a hash from his password and using this hash as
a key encrypts this random string with a blowfish, for example (there
is an implementation in JS for sure) and sends it back to you.
You, on your own, using the hash stored on the server, also encrypt this
random string with a blowfish.
Compare.
An attacker would have to attack the bluefish key using a random source and ciphertext. The task is not easy.
Isn't SSL/TLS replacing the nonce? I don't see added value of this since SSL/TLS also protects against Replay Attacks.
Ref.
https://en.wikipedia.org/wiki/Cryptographic_nonce
It would actually be less secure to hash the password and send it over a non-encrypted channel. You will expose your hashing algorithm on the client. Hackers could just sniff the hash of the password and then use it to hack in later.
By using HTTPS, you prevent a hacker from obtaining the password from a single source, since HTTPS uses two channels, both encrypted.

Is it advisable to store a hashed password in a cookie?

I want user's to be able to select a "remember me" box on my website so they need not log in each time they come. So, I need to store a unique ID in a cookie to identify them. Is it safe to hash their password with sha512 and a long salt in PHP and store that value in the cookie?
If the cookie was stolen, would their password be at risk?
Obviously it must be connected to their password somehow, otherwise if the cookie value was guessed or stolen, the user would not be able to stop someone else logging in.
Also, is it advisable to use a GUID at all as the unique identifier?
Thanks,
Ben
Remember, the hash of the password is effectively the same as their password. Somebody who stole the hash would have the same access to the user's account as if they had stolen their password. Therefore it is not advisable to store a hash of the user's password in a cookie unless there was some other information not stored with the cookie that is used to authenticate (i.e. 2-factor authentication).
Here is an excellent article on this very topic. Many of the answers to your question are hitting on techniques outlined in it.
There's a low risk with a good algorithm and large salt, but why take any unnecessary risk?
If you just need to identify the user, then store something that can uniquely identify the user, like a guid along with some other stored verification code (not their password, some random long string). I wouldn't use a guid alone as it would not be a safe method of authentication.
It wouldn't hurt to have some kind of "password" in the cookie along with a user id (to prevent users from changing the uid to that of another user), just don't make the "password" the same as the actual user's password.
And just because it's a hash doesn't necessarily mean it's one-way (well, by definition it does, but there are utilities to generate MD5 plaintexts and I would guess it's only a matter of time before it happens to others). I would hash some kind of secondary password.
An alternative way of doing this might be use the cookie as an encrypted storage for only indirection data. You'd need some sort of unencrypted identifier that would serve as a pointer to the key (or the required information to derive the key) in the application's database, followed by a blob encrypted by the key obtained from the identifier, which itself would contain some sort of one-time-usable identifier that authenticates the session.
Given the following assumptions:
Your database is secure (e.g., your application can access it, but your user cannot directly do so, and also assuming that the application has been proofed against SQL injection)
Your salts are strong; that is, reasonably high-entropy enough that attempting to crack the salted password is infeasible even if the password is known
Then what this would provide is a method by which one can be reasonably certain that the session is not able to be hijacked or stolen in any way. That is to say that a copied cookie is only of limited usefulness, since the user must not have used the cookie between its theft and usage by an attacker.
While this protects against replay, it also means that if someone does manage to steal the cookie at exactly the right time, and manages also to use it before the original, legitimate user does, the attacker now is in control of the session. One can limit a session to an IP address to mitigate that risk (somewhat; if both the user and the attacker are behind the same NAT, which is the most likely scenario in any home or small-to-medium business network) then this point is pretty moot, since the IP address would appear to be the same anyway. Also useful might be limiting to the current user agent (though that can break unexpectedly if the user updates their browser and the session does not expire at browser close time), or finding some method by which one can identify the computer that the user is on just well enough that there is reasonable certainty that the user hasn't moved the cookie from one system to the next. Short of using some binary plugin (Flash, or Silver/Moonlight), I'm not sure that the latter is possible.
To protect against a permanent session hijacking, require that the user to reauthenticate him- or herself periodically (e.g., limit the allowed session lifetime or require something like a token/fob/dongle) and require that the user reauthenticates him- or herself upon entering sensitive areas of the application, such as password change and potentially dangerous actions, patterns or behaviors such as the deletion of data, unusual usage patterns, bulk actions, and so forth.
It is difficult to secure applications and yet retain their ease-of-use. If done carefully, security can be implemented in a manner which is minimally intrusive and yet still effective—well, for most Internet-facing applications, anyway.
It's not advisable, but if your gonna do it, at least salt your passwords before you do so. This will stop people from using hash crackers if they do manage to get hold of your visitor's cookies.

Resources