用于通过URL传递变量的php双向加密

OK... to the point...

I have a e-mailshot program that sends out thousands of emails - each with str_replace() merge fields (fields that get replaced by a row value in a recordset).

One of the important parts is my ability to track those mails on open so I include a single server-side generated pixel...

<img src="http://...trace.php?email=<<<EMAIL>>>" alt="" height="1" width="1">

The str_replace() replaces <<<EMAIL>>> with a unique real email address.

The trace.php file reads the $_GET['email'] and either logs it or sends mail confirmation.

My issue is security :)

I want to use two-way encryption so that the $_GET variable sent in the URL is an encrypted email. The trace.php file then needs to decrypt it.

As it's being sent in a URL, It has to be in ASCII format otherwise it will corrupt before decrypting.

I can't use openssl_encrypt() & openssl_decrypt() and I'm having to work with php 5.2.0 (don't hurl abuse at me!).

Any help would be greatly appreciated!!

While many of the comments you have received offer other valid ways of solving the problem e.g. a table of email addresses with primary keys, I am of the position that the best way to solve the problem is the way you originally intended: including the email address encrypted in the query URL.

I feel that this way is better because:

  • Computing the email address does not require database access. Database bottle-necking is generally the biggest offender for high-latency requests.
  • Encryption means that the same email address will produce a different IV/ciphertext pair each time you encrypt it. Thus, if you send multiple emails at different times (say, for two different marketing campaigns), the URL will be different each time. This may not have an effect, but it does provide a security advantage in that an attacker can't "pretend" that an email has been opened simply by visiting a URL.

The issue is that for this way to be better, you have to do it well. I've included an excerpt in PHP from this repository below. If you can't use openssl_* then upgrade your PHP version. Do not, ever, use the mcrypt_ functions. They are deprecated for a reason. You may need to hex encode instead of base64 encode the email addresses as is done in the example below.

<?php

define("ALGORITHM_NAME", "aes-128-gcm");
define("ALGORITHM_NONCE_SIZE", 12);
define("ALGORITHM_TAG_SIZE", 16);
define("ALGORITHM_KEY_SIZE", 16);
define("PBKDF2_NAME", "sha256");
define("PBKDF2_SALT_SIZE", 16);
define("PBKDF2_ITERATIONS", 32767);

function encryptString($plaintext, $password) {
    // Generate a 128-bit salt using a CSPRNG.
    $salt = random_bytes(PBKDF2_SALT_SIZE);
    // Derive a key.
    $key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
    // Encrypt and prepend salt and return as base64 string.
    return base64_encode($salt . encrypt($plaintext, $key));
}
function decryptString($base64CiphertextAndNonceAndSalt, $password) {
    // Decode the base64.
    $ciphertextAndNonceAndSalt = base64_decode($base64CiphertextAndNonceAndSalt);
    // Retrieve the salt and ciphertextAndNonce.
    $salt = substr($ciphertextAndNonceAndSalt, 0, PBKDF2_SALT_SIZE);
    $ciphertextAndNonce = substr($ciphertextAndNonceAndSalt, PBKDF2_SALT_SIZE);
    // Derive the key.
    $key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
    // Decrypt and return result.
    return decrypt($ciphertextAndNonce, $key);
}
function encrypt($plaintext, $key) {
    // Generate a 96-bit nonce using a CSPRNG.
    $nonce = random_bytes(ALGORITHM_NONCE_SIZE);
    // Encrypt and prepend nonce.
    $ciphertext = openssl_encrypt($plaintext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
    return $nonce . $ciphertext . $tag;
}
function decrypt($ciphertextAndNonce, $key) {
    // Retrieve the nonce and ciphertext.
    $nonce = substr($ciphertextAndNonce, 0, ALGORITHM_NONCE_SIZE);
    $ciphertext = substr($ciphertextAndNonce, ALGORITHM_NONCE_SIZE, strlen($ciphertextAndNonce) - ALGORITHM_NONCE_SIZE - ALGORITHM_TAG_SIZE);
    $tag = substr($ciphertextAndNonce, strlen($ciphertextAndNonce) - ALGORITHM_TAG_SIZE);
    // Decrypt and return result.
    return openssl_decrypt($ciphertext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
}
?>