1

UPDATE (SOLUTION)

Since this post seems to get decent amount of attention, I'd like to let you know that the solution ended up being to provide a proper enctype (content type) parameter in the <FORM> declaration. You must set the value to multipart/form-data to prevent encoding that would otherwise take place using the default enctype of application/x-www-form-urlencoded. A small excerpt below from Forms in HTML Documents at w3.org:

The content type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.

And here is the proper FORM declaration:

<FORM method="POST" action="/path/to/file/" name="encryptedForm" enctype="multipart/form-data">

INITIAL QUESTION

I am working on a form spam protection class which essentially replaces form field names with an encrypted value using mcrypt. The problem with this is that mcrypt encryption is not limited to only alphanumeric characters which would invalidate form fields. Given the code below, can you think of any reason why I'd be having problems decrypting the values of the already encrypted array?

/**
 * Two way encryption function to encrypt/decrypt keys with
 * the DES encryption algorithm.
 */
public static function encryption($text, $encrypt = true)
{
    $encrypted_data = '';
    $td = mcrypt_module_open('des', '', 'ecb', '');
    $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
    if (mcrypt_generic_init($td, substr(self::$randomizer, 16, 8), $iv) != -1) {
        if ($encrypt) {
            // attempt to sanitize encryption for use as a form element name
            $encrypted_data = mcrypt_generic($td, $text);
            $encrypted_data = base64_encode($encrypted_data);
            $encrypted_data = 'i' . strtr($encrypted_data, '+/=', '-_.');
            self::$encrypted[] = $encrypted_data;
        } else {
            // reverse form element name sanitization and decrypt
            $text = substr($text, 1);
            $text = strtr($text, '-_.', '+/=');
            $text = base64_decode($text);
            $encrypted_data = mdecrypt_generic($td, $text);
        }
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
    }
    return $encrypted_data;
}

I later make a call setting a hidden form element's value using:

base64_encode(serialize(self::$encrypted))

Essentially the hidden field contains an array of all form fields which were encrypted with their encrypted value. This is so I know which fields need to be decrypted on the backend. Upon form submission this field gets parsed on the backend with the following code:

    // load the mapping entry
    $encrypted_fields = $input->post('encrypted', '');
    if (empty($encrypted_fields)) {
        throw new AppException('The encrypted form field was empty.');
    }

    // decompress array of encrypted fields
    $encrypted_fields = @unserialize(base64_decode($encrypted_fields));
    if ($encrypted_fields === false) {
        throw new AppException('The encrypted form field was not valid.');
    }

    // get the mapping of encrypted keys to key
    $data = array();
    foreach ($_POST as $key => $val) {
        // if the key is encrypted, add to data array decrypted
        if (in_array($key, $encrypted_fields)) {
            $decrypted = self::encryption($key, false);
            $data[$decrypted] = $val;
            unset($_POST[$key]);
        } else {
            $data[$key] = $val;
        }
    }

    // merge $_POST array with decrypted key array
    $_POST += $data;

My attempts to decrypt the encrypted form field keys are failing. It's simply creating a new garbled key in the $_POST array. My guess is that either base64_encoding or serialization is stripping chars from the $encrypted_data. Could somebody verify if this is the culprit and whether there are any alternative methods for encoding form keys?

4
  • @cballou I'm having the same problem - did you find out what it was about the post array (below) that was causing it? Commented Apr 30, 2010 at 19:37
  • @adam - the fix is to ensure your form has enctype="multipart/form-data" as this ensures characters don't get encoded and replaced. Commented May 13, 2010 at 15:46
  • Thanks cballou. I was posting using curl but wasn't using base64_encode - basically the same fix! Commented May 13, 2010 at 16:40
  • If you're interested, I open sourced my spam prevention class SPF30 using this encryption method at jqueryin.com/2010/04/26/… Commented Jul 8, 2011 at 11:26

1 Answer 1

1

So I took your code, and modified it a little so that I can remove the element of a post request and your function seems to work fine. If you take the code I posted and create a script with it, it should run in the cli and you'll see its encrypting/decrypting the fields correctly. This would have to mean that the post request is some how garbling the encrypted/serialized/encoded data. If using a framework, I would look more into how it handles the post array as it could altering your keys/values causing them to not match up. The code that you posted seems fine.

<?php
    /**
     * Two way encryption function to encrypt/decrypt keys with
     * the DES encryption algorithm.
     */
    function encryption($text, $encrypt = true, &$encryptedFields = array())
    {
        $encrypted_data = '';
        $td = mcrypt_module_open('des', '', 'ecb', '');
        $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
        if (mcrypt_generic_init($td, substr('sdf234d45)()*5gf512/?>:LPIJ*&U%&^%NBVFYUT^5hfhgvkjtIUUYRYT', 16, 8), $iv) != -1) {
            if ($encrypt) {
                // attempt to sanitize encryption for use as a form element name
                $encrypted_data = mcrypt_generic($td, $text);
                $encrypted_data = base64_encode($encrypted_data);
                $encrypted_data = 'i' . strtr($encrypted_data, '+/=', '-_.');
                //self::$encrypted[] = $encrypted_data;
                $encryptedFields[] = $encrypted_data;
            } else {
                // reverse form element name sanitization and decrypt
                $text = substr($text, 1);
                $text = strtr($text, '-_.', '+/=');
                $text = base64_decode($text);
                $encrypted_data = mdecrypt_generic($td, $text);
            }
            mcrypt_generic_deinit($td);
            mcrypt_module_close($td);
        }
        return $encrypted_data;
    }

    $encryptedFields = array();

    // encrypt some form fields
    encryption('firstname', true, $encryptedFields);
    encryption('lastname', true, $encryptedFields);
    encryption('email_fields', true, $encryptedFields);

    echo "Encrypted field names:\n";
    print_r($encryptedFields);

    // create a usable string of the encrypted form fields
    $hiddenFieldStr = base64_encode(serialize($encryptedFields));

    echo "\n\nFull string for hidden field: \n";
    echo $hiddenFieldStr . "\n\n";


    $encPostFields = unserialize(base64_decode($hiddenFieldStr));

    echo "\n\nDecrypted field names:\n";
    foreach($encPostFields as $field)
    {
        echo encryption($field, false)."\n";
    }
    ?>
Sign up to request clarification or add additional context in comments.

1 Comment

It's worth noting that my encryption mechanism did not work when trying to decrypt POSTed data from a form because POSTed data will, by default, get encoded. You must set <form enctype="multipart/form-data"> as this ensures characters don't get encoded and replaced.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.