My PHP Application uses URLs like these:
http://domain.com/userid/120
http://domain.com/userid/121
The keys and the end of the URL are basically the primary key of the MySQL database table.
I don't want this increasing number to be public and I also don't want that someone will be able to crawl the user profiles just by interating the Id.
So I want to encrypt this Id for display in a way I can easily decrypt it again. The string shouldn't get much longer.
What's the best encryption method for this?
Simple Obscuring: Base64 encode them using base64_encode.
Now, your http://domain.com/userid/121 becomes: http://domain.com/userid/MTIx
Want more, do it again, add some letters around it.
Tough Obscuring: Use any encryption method using MCrypt library.
A better approach (from a usability and SEO perspective) would be to use a unique phrase rather than an obscured ID. In this instance the user's user name would seem an ideal solution, and would also be un-guessable.
That said, if you don't want to use this approach you could just use a hash (perhaps md5) of the user's user name which you'd store in the database along with their other details. As such, you can just do a direct lookup on that field. (i.e.: Having encrypt and decrypt part of the URL is probably overkill.)
You have a variety of choices here:
Generate and store an identifier in the database. It's good because you can then have readable keys that are guaranteed to be unique. It's bad because it causes a database schema change, and you have to actually query that table every time you want to generate a link.
Run an actual key-based encryption, for instance based on PHP's MCrypt. You have access to powerful cryptographic algorithms, but most secure algorithms tend to output strings that are much longer than what you expect. XOR does what you want, but it does not prevent accessing sequential values (and the key is pretty simple to determine, given the a priori knowledge about the numbers).
Run a hash-based verification: instead of using 121 as your identifier, use 121-a34df6 where a34df6 are the first six characters of the md5 (or other HMAC) of 121 and a secret key. Instead of decoding, you extract the 121 and recompute the six characters, to see if they match what the user sent. This does not hide the 121 (it's still right there before the hyphen) but without knowing the secret key, the visitor will not be able to generate the six characters to actually view the document numbered 121.
Use XOR with shuffling: shuffle the bits in the 30-bit identifier, then apply the XOR. This makes the XOR harder to identify because the shuffle pattern is also hidden.
Use XOR with on-demand keys: use fb37cde4-37b3 as your key, where the first part is the XOR of 121 and md5('37b3'.SECRET) (or another way of generating an XOR key based on 37b3 and a secret).
Don't use base64, it's easy to reverse engineer: if MTIx is 121, then MTIy is 122 ...
Ultimately, you will have to accept that your solution will not be secure: not only is it possible for users to leak valid urls (through their browser history, HTTP referer, or posting them on Twitter), but your requirement that the identifier fits in a small number of characters means a brute-force attack is possible (and becomes easier as you start having more documents).
Simplest but powerful encryption method: XOR with a secret Key. http://en.wikipedia.org/wiki/XOR_cipher
No practical performance degradation.
Base64 representation is not an encryption! It's another way to say the same.
Hope this helps.
Obscuring the URL will never secure it. It makes it harder to read, but not much harder to manipulate. You could use a hexadecimal number representation or something like that to obscure it. Those who can read hex can change your URL in a few seconds, anyway:
$hexId = dechex($id); // to hex
$id = hexdec($hexId); // from hex
I'd probably say it's better indeed to just create a random string for each user and store that in your database than to get one using hash. If you use a common hash, it's still very easy to iterate over all pages ;-)
I would write this in comments, but don't have the rep for it (yet?).
When user click on a link you should not use primary key, You can use the pkey in a session and get it from that session. Please do not use query string....
generate an unique string for each user and use it in your urls
http://domain.com/user/ofisdoifsdlfkjsdlfkj instead of http://domain.com/userid/121
you can use base64_encode and base64_decode function for encrypt and decrypt your URLS
Related
I am building an online-and-mailout ballot system in PHP/Laravel that requires an access token that is to be typed in by a person at some point, either the actual user or an administrator. As such, I would assume that the token would have to be generated and not traceable back to the original user via database dumps.
What would be the best way to generate such a code? I have looked at Hashids, but unsure if that would be a suitable solution, unless it is fine to use the ballot creation timestamp in seconds as a second value to use along with the ballot ID.
Create a random byte array with a CSPRNG such as openssl_random_pseudo_bytes and then Base58 encode the result. This will give the shortest human usable character string for level of unique-ness.
Base58 a very human usable character set without easily confused characters. One example of this usage is Bitcoin.
Here is the character set:
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
One of the Best and one of the fastest is:
$token = bin2hex(openssl_random_pseudo_bytes(int));
binary safe and cryptographically secure, but if You want get with length wanted, You must cut this and then You will get easer to hook token made only from 16 characters. If You want get human reliable string You must implement it by self and this is long way. I made something like this using cryptographically secure extensions SafeToken.
What would be useful solutions for hiding true database object ID in URL for security purposes? I found that one of the solutions would be:
1) Using hashids open source project
2) Using something like same old md5 on creation of the object to generate hash and store it in database, then use it in url's and querying by them, but the drawback is that querying by auto-incremented primary keys (IDs) is faster than hashes. So I believe the possibility to hash/unhash would be better?
Also as I'm on Symfony, are there maybe bundles that I could not find or built in functionalities that would help?
Please tell me what you found useful based on your experiences.
This question has been asked a lot, with different word choice (which makes it difficult to say, "Just search for it!"). This fact prompted a blog post titled, The Comprehensive Guide to URL Parameter Encryption in PHP .
What People Want To Do Here
What People Should Do Instead
Explanation
Typically, people want short random-looking URLs. This doesn't allow you much room to encrypt then authenticate the database record ID you wish to obfuscate. Doing so would require a minimum URL length of 32 bytes (for HMAC-SHA256), which is 44 characters when encoded in base64.
A simpler strategy is to generate a random string (see random_compat for a PHP5 implementation of random_bytes() and random_int() for generating these strings) and reference that column instead.
Also, hashids are broken by simple cryptanalysis. Their conclusion states:
The attack I have described is significantly better than a brute force attack, so from a cryptographic stand point the algorithm is considered to be broken, it is quite easy to recover the salt; making it possible for an attacker to run the encoding in either direction and invalidates property 2 for an ideal hash function.
Don't rely on it.
Quote from the site:
Do you have a question or comment that involves "security" and "hashids" in the same sentence? Don't use Hashids.
I'd use true encryption algorithm, like function openssl_encrypt (for example), or something like this. And encrypt ids when passing outside, decrypt when using in your code (like for db queries).
And I won't recommend storing ids in a base like any kind of encrypted "garbage", in my opinion its very inconvenient to hash your real ids. Keep it clean and pretty inside and encrypt for external display only.
Following your idea, you just need to cipher your IDs before writing the URL to HTML page and decipher them when processing those URLs.
If you want just security by obscurity, which is sufficient for, maybe 99% of curious people out there who likes to iterate over IDs in URLs, you use something simple like base64 or rot13. Of course, you can also precalculate those "public IDs" and store in the database, not encrypting each time the URL is being shown to end user.
If you want true security you have to encrypt them with some serious asymmetric cypher, storing both keys at your side, as you essentially talking with yourself and don't want a man-in-the-middle attack. This you will not be able to precalculate as at each encrypting there'll be different cyphertext, which is good for this cause.
In any case, you need something two-way, so if I were you I'd forget about word "hash", hashes are for purposes different from yours.
EDIT:
But the solution which every blog out there uses for this task for several years already is just to utilize URL rewriting, converting, in your case, URLs like http://example.com/book/5 to URLs like http://example.com/rework-by-37signals. This will completely eradicate any sign of database ID from your URL.
Ideologically, you will need something which will uniquely map the request URL to your database content anyway. If you hide MySQL database IDs behind any layer of URL rewriting, you'll just make this rewritten URL a new ID for the same content. All you gain is protection from enumeration attacks and maybe SEF URLs.
What would be useful solutions for hiding true database object ID in URL for security purposes? I found that one of the solutions would be:
1) Using hashids open source project
2) Using something like same old md5 on creation of the object to generate hash and store it in database, then use it in url's and querying by them, but the drawback is that querying by auto-incremented primary keys (IDs) is faster than hashes. So I believe the possibility to hash/unhash would be better?
Also as I'm on Symfony, are there maybe bundles that I could not find or built in functionalities that would help?
Please tell me what you found useful based on your experiences.
This question has been asked a lot, with different word choice (which makes it difficult to say, "Just search for it!"). This fact prompted a blog post titled, The Comprehensive Guide to URL Parameter Encryption in PHP .
What People Want To Do Here
What People Should Do Instead
Explanation
Typically, people want short random-looking URLs. This doesn't allow you much room to encrypt then authenticate the database record ID you wish to obfuscate. Doing so would require a minimum URL length of 32 bytes (for HMAC-SHA256), which is 44 characters when encoded in base64.
A simpler strategy is to generate a random string (see random_compat for a PHP5 implementation of random_bytes() and random_int() for generating these strings) and reference that column instead.
Also, hashids are broken by simple cryptanalysis. Their conclusion states:
The attack I have described is significantly better than a brute force attack, so from a cryptographic stand point the algorithm is considered to be broken, it is quite easy to recover the salt; making it possible for an attacker to run the encoding in either direction and invalidates property 2 for an ideal hash function.
Don't rely on it.
Quote from the site:
Do you have a question or comment that involves "security" and "hashids" in the same sentence? Don't use Hashids.
I'd use true encryption algorithm, like function openssl_encrypt (for example), or something like this. And encrypt ids when passing outside, decrypt when using in your code (like for db queries).
And I won't recommend storing ids in a base like any kind of encrypted "garbage", in my opinion its very inconvenient to hash your real ids. Keep it clean and pretty inside and encrypt for external display only.
Following your idea, you just need to cipher your IDs before writing the URL to HTML page and decipher them when processing those URLs.
If you want just security by obscurity, which is sufficient for, maybe 99% of curious people out there who likes to iterate over IDs in URLs, you use something simple like base64 or rot13. Of course, you can also precalculate those "public IDs" and store in the database, not encrypting each time the URL is being shown to end user.
If you want true security you have to encrypt them with some serious asymmetric cypher, storing both keys at your side, as you essentially talking with yourself and don't want a man-in-the-middle attack. This you will not be able to precalculate as at each encrypting there'll be different cyphertext, which is good for this cause.
In any case, you need something two-way, so if I were you I'd forget about word "hash", hashes are for purposes different from yours.
EDIT:
But the solution which every blog out there uses for this task for several years already is just to utilize URL rewriting, converting, in your case, URLs like http://example.com/book/5 to URLs like http://example.com/rework-by-37signals. This will completely eradicate any sign of database ID from your URL.
Ideologically, you will need something which will uniquely map the request URL to your database content anyway. If you hide MySQL database IDs behind any layer of URL rewriting, you'll just make this rewritten URL a new ID for the same content. All you gain is protection from enumeration attacks and maybe SEF URLs.
I'm making a website which people can publish posts. In my database each post has an ID like 1, 2, 3 etc. but I would like to change them, like using a hash like Youtube does.
For example instead of http://localhost/post/1
They would go to http://localhost/post/hu9NA827z
Is there a method like hashing the numbers and decoding it?
Well, while you could encrypt/decrypt it doesn't make much sense (you're gonna make it slower without any real benefit).
Waht you can do is to have the primary key in your DB to be a string and generate a hash for the id or add a new column with a unique index, save the hash there and search the posts by the hash column (and maybe keep the id for internal purposes). You can use complex algorithms or just md5(uniqid()), since this is not for security i wouldn't worry too much. Make sure that when creating a new post, the uniqueness is not being violated. Now you have another reason for an insertion to fail (the hash not being unique) so prepare for that.
Check:
http://php.net/manual/en/function.md5.php
http://php.net/manual/en/function.uniqid.php
Since there is no need for this hash to be secure, you can just use the PHP built-in hash function, md5(). I suggest using the timestamp as input:
$id = md5(time());
Just truncate it to make it shorter. I suggest you keep the original primary key an autoincrement integer and add this hash as a new column.
The sequence hu9NA827z is BASE64. Decoding it, you get a binary sequence of 6 bytes.
For instance:
base64_encode('123456') // = 'MTIzNDU2'
base64_decode('MTIzNDU2') // = '123456'
However, on YouTube, BASE64 is not being used to protect the information, its purpuse is just to serialize it into a human-readable ASCII format. The real message behind it is a 48-bit binary sequence.
This binary sequence is probably the encrypted version of what would be the video ID on a database, but what it really is only YouTube developers knows for sure and they certainly expect it to remain that way.
In your case, you could simply implement a similar system using one of the many two-way encryption methods offered in PHP like MCrypt that supports a lot of encryption algorithms of your choice including the very safe AES.
I'm looking to store (in mySQL) an obfuscated version of a phone number, where the number is used for authentication (I need to be able to get back the original number).
I've thought about an arbitrary scheme like storing the number * 15 or some constant only my app knows.
What are some better ways of doing this?
EDIT: Some things I'd like to clarify:
The phone numbers that are saved can be used to log into an iPhone app - so I want users to be able to see which number they have connected to the service incase they want to log into the app with a different number later. This means I cannot hash the value.
Essentially I am looking for a way to protect the data if someone lifts my database that they don't have a bunch of phone numbers in raw form. So I'd like to obfuscate them so I can use them for authentication, but be able to get one back in its original form without storing it raw.
EDIT: To clarify, I am not authenticating on JUST the phone number. If implemented, it would be phone number + a password! Enter a single string of digits that may exist and you're in? lol - my apologies if I have misled some folks.
Store where? In a database? Use an encryption function rather than rolling your own system.
In MySQL it'd be as simple as:
INSERT INTO users (phone) VALUES (AES_ENCRYPT('yourkey', '867-5309'));
of course, now you're changed the problem from hiding the phone numbers to "where the #$##$## can I hide this key?". Obvious solution: hide the key under a rock outside your server's front-door. Which changes the problem into "where the ####$###% can I hide this rock?". Obvious solution: cover your front yard with a steel cage with a padlock on the door. New problem: how to hide the padlock key... and so on.
How about actual encryption? In this scenario, a good symmetric encryption algorithm is trivial, since the length of the payload is limited to, what, 10 digits, so you can get by with a key that's also 10 decimal digits long; using such a key, all you need to do is something like XOR or increment / mod 10 on each digit. Of course, the weak link in this scheme then is the way you store the key.
I am curious, however, why you need to get them back out - if it's for authentication:
you shouldn't be using phone numbers, as these are easy to look up, even automatically
you should be storing secure one-way hashes with individual salts, so you couldn't even get them back out youself if you wanted to (except by brute-forcing)
Using the Cipher Class you can do this:
$phone = '...';
$key = 'secret.for.each.number';
$phone = Cipher::encrypt($phone, $key);
Before you store it in the database. Then later you can pull it out and do this:
$phone = Cipher::decrypt($phone, $key);
A better way would be not doing that. There is a reason one-way encryption is used to store passwords.
If you need to get back the original value, you should not be using it for authentication, since it will invariably be easy for an attacker to find it.
If you feel you need to hide the value by obfuscating it, you probably need to change something fundamental about how you're storing the data.
This isn't a very good approach to security. Several things jump out at me:
Phone numbers are very easy to guess: just program something to start guessing random combinations. Encrypted or not, your program is validating using these numbers, so it will eventually work on some. You need an extra layer of security like a password known only to the user in question. I would recommend anti-brute-force attack measures as well.
Any two-way encryption can be cracked, it is as simple as that. If you need to be able to decrypt data in the database easily, the only benefit from encrypting it is if someone hacks into your database and grabs the information. As others have pointed out, if that happens, you have bigger issues. The other scenario is for staffers who could have valid access to the DB. If you are hiding the data from them, it is important to encode the information in some way. But multiplying the phone number by a "unknown" constant is not ideal. Use a better method.
Surely I know my friend's numbers, so I could hack into anyone's account, correct? You need to add a password component if you haven't already. The password should be 1-way encryption using a strong and unique SALT. Once added, you only need to encrypt phone numbers in the DB if you don't want your staffers to see them. Otherwise you are wasting time encrypting them.
There is no point in this question.
Just leave these phone numbers as is. You will gain no security improvement from such obfuscation