My application (obviously) uses a unique ID to distinguish records. This UID is passed in URLs (e.g.
./examplepage.php?UID=$example_int), among other things.
While I obviously have server-side validation in place to make sure clients don’t access other clients’ data, is there a two-way encryption method I can use in PHP to only pass encrypted UIDs (e.g.
./examplepage.php?EUID=$encrypted_int), to further reduce the chance of anyone thinking “hey, what happens if I increment this integer?”
Placing a hash next to the ID to ensure it’s security, or padding the ID with extra data, or even converting the ID to hex would all work fairly well I think.
PHP 5.3 has introduced a new encryption method that is really easy to use:
openssl_decrypt. It’s not well-documented here, so here’s a simple example:
$textToEncrypt = "My super secret information."; $encryptionMethod = "AES-256-CBC"; // AES is used by the U.S. gov't to encrypt top secret documents. $secretHash = "25c6c7ff35b9979b151f2136cd13b0ff"; //To encrypt $encryptedMessage = openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash); //To Decrypt $decryptedMessage = openssl_decrypt($encryptedMessage, $encryptionMethod, $secretHash); //Result echo "Encrypted: $encryptedMessage <br>Decrypted: $decryptedMessage";
I chose 256-AES because it’s solid and fast. It’s been adopted by the U.S. gov’t to encrypt top secret documents. It’s fast considering machine and software. Here’s a list of available encryption methods:
AES-128-CBC, AES-128-CFB, AES-128-CFB1, AES-128-CFB8, AES-128-ECB, AES-128-OFB, AES-192-CBC, AES-192-CFB, AES-192-CFB1, AES-192-CFB8, AES-192-ECB, AES-192-OFB, AES-256-CBC, AES-256-CFB, AES-256-CFB1, AES-256-CFB8, AES-256-ECB, AES-256-OFB, BF-CBC, BF-CFB, BF-ECB, BF-OFB, CAMELLIA-128-CBC, CAMELLIA-128-CFB, CAMELLIA-128-CFB1, CAMELLIA-128-CFB8, CAMELLIA-128-ECB, CAMELLIA-128-OFB, CAMELLIA-192-CBC, CAMELLIA-192-CFB, CAMELLIA-192-CFB1, CAMELLIA-192-CFB8, CAMELLIA-192-ECB, CAMELLIA-192-OFB, CAMELLIA-256-CBC, CAMELLIA-256-CFB, CAMELLIA-256-CFB1, CAMELLIA-256-CFB8, CAMELLIA-256-ECB, CAMELLIA-256-OFB, CAST5-CBC, CAST5-CFB, CAST5-ECB, CAST5-OFB, DES-CBC, DES-CFB, DES-CFB1, DES-CFB8, DES-ECB, DES-EDE, DES-EDE-CBC, DES-EDE-CFB, DES-EDE-OFB, DES-EDE3, DES-EDE3-CBC, DES-EDE3-CFB, DES-EDE3-CFB1, DES-EDE3-CFB8, DES-EDE3-OFB, DES-OFB, DESX-CBC, RC2-40-CBC, RC2-64-CBC, RC2-CBC, RC2-CFB, RC2-ECB, RC2-OFB, RC4, RC4-40, SEED-CBC, SEED-CFB, SEED-ECB, SEED-OFB, aes-128-cbc, aes-128-cfb, aes-128-cfb1, aes-128-cfb8, aes-128-ecb, aes-128-ofb, aes-192-cbc, aes-192-cfb, aes-192-cfb1, aes-192-cfb8, aes-192-ecb, aes-192-ofb, aes-256-cbc, aes-256-cfb, aes-256-cfb1, aes-256-cfb8, aes-256-ecb, aes-256-ofb, bf-cbc, bf-cfb, bf-ecb, bf-ofb, camellia-128-cbc, camellia-128-cfb, camellia-128-cfb1, camellia-128-cfb8, camellia-128-ecb, camellia-128-ofb, camellia-192-cbc, camellia-192-cfb, camellia-192-cfb1, camellia-192-cfb8, camellia-192-ecb, camellia-192-ofb, camellia-256-cbc, camellia-256-cfb, camellia-256-cfb1, camellia-256-cfb8, camellia-256-ecb, camellia-256-ofb, cast5-cbc, cast5-cfb, cast5-ecb, cast5-ofb, des-cbc, des-cfb, des-cfb1, des-cfb8, des-ecb, des-ede, des-ede-cbc, des-ede-cfb, des-ede-ofb, des-ede3, des-ede3-cbc, des-ede3-cfb, des-ede3-cfb1, des-ede3-cfb8, des-ede3-ofb, des-ofb, desx-cbc, rc2-40-cbc, rc2-64-cbc, rc2-cbc, rc2-cfb, rc2-ecb, rc2-ofb, rc4, rc4-40, seed-cbc, seed-cfb, seed-ecb, seed-ofb
Thanks Hobo and Jorwin for pointing out that in PHP 5.3.3 > there is a new parameter that makes this function a little more secure.
Jorwin referenced this link in his comment, and here is an excerpt that is applicable:
In 5.3.3 they added a new parameter,
vector) Real parameters are:
string openssl_encrypt ( string $data ,
string $method , string $password, bool $raw_output = false, string
$ivis missing, a warning is issued: “Using an empty Initialization Vector (iv) is potentially insecure and not recommended”.
$ivis too short, another warning: “IV passed is only 3 bytes long, cipher expects an IV of precisely 8 bytes, padding with \0”
same IV should be used in
You don’t need two-way encryption – encryption is for maintaining secrecy, but what you’re really looking for here is authenticity.
HMACs (essentially, keyed hashes) are one way of getting cryptographic authenticity. Accompany the UID with a HMAC of the UID (PHP has a HMAC implementation), using a key that only the server knows. At the start of each request, check the HMAC.
Basically, use the right tool for the right job.
While PHP supports many two way hashing algorithms I do not see it being useful in this example. What you need to do is:
- Load the row from storage by the provided id
- Check that the owner of the row is the authenticated user and if not throw an exception and inform the user not to do that again
But if your heart is set on hashing just pick one of the algorithms provided.
First, encrypting URL parameters is usually a bad idea, and a separate lookup (based on an index
CHAR column generated by a CSPRNG) is better for 99.9% of use cases.
Encryption without message authentication is dangerous, especially if you’re trusting an end-user with the ciphertext.
If you cannot for whatever reason install a PECL extension, there are two PHP libraries to choose from: defuse/php-encryption and zend-crypt. They both offer standards compliant authenticated encryption and they’re both safe to use (for what it’s worth, I frequently perform code audits for cryptography implementations in PHP, I’m not merely some random person on the internet).