on Articles Expert Tutorials

Obfuscate your e-mail address with PHP, JavaScript and CSS

5 comments
E-mail obfuscation
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedInShare on RedditShare on StumbleUpon

According to Wikipedia, more than 97% of all e-mails sent over the net are unwanted. That’s around 200 billion spam messages per day.

To keep this insane amount of spam out of your inbox, you should keep your e-mail safe when you display it on web. One of the ways to keep it safe is to obfuscate it. In this tutorial I’ll show you how to create a script that will do just that.

Basic requirements for such script (at least from my point of view) are:

  1. It should function pretty much out of the box. User should work only with real e-mail; script should obfuscate and deobfuscate it on the fly.
  2. It should show obfuscated e-mail to bots, but real one to humans; humans should not even notice that obfuscation (or deobfusaction) is taking place.
  3. Script should work on all desktop browsers (I’m talking IE6+here) and on mobile browsers as well (at least on the ones that support JavaScript).

To handle this, we’ll create a PHP function that will take string (e-mail address) as an argument. The function will reverse the address and obfuscate it using slightly modified ROT13 algorithm.

Deobfuscation will be done with JavaScript. When user opens the page we will detect which e-mail links have been obfuscated, apply reversed ROT13 algorithm on them and direction:rtl and unicode-bidi:override CSS rules.

This way, users will see real address, but spam bots will still (even if they can parse JavaScript) see a reversed e-mail.

This will, of course, leave us with a usability problem. If user copies the address, or if he clicks it, he’ll get the reversed one. Therefore, as a last deobfuscation step, we’ll reverse the reversed address and remove added CSS rules as soon as the user hovers over the link.

Demo

E-mail Obfuscation (PHP)

Code is thoroughly commented, have a look if you’d like to know more.

//Function takes string as an argument and returns a string.
function obfuscateEmail( $email ) {
	
	//We will work with UTF8 characters, just to be safe that we won't mess up any address.
	$emailLetters = preg_split( '//u', $email, null, 1 );
	$obfuscatedEmail = '';
	
	//Reversing the string (e-mail).
	$emailLetters = array_reverse( $emailLetters );
	
	//Characters that are to be used when obfuscating email address.
	//If you change this, make sure you change the characters string in JavaScript as well.
	//And please note that the string must have even number of characters for this to work.
	$characters = '123456789qwertzuiopasdfghjklyxcvbnmMNBVCXYLKJHGFDSAPOIUZTREWQ';

	//Get the number of characters dynamically.
	$charactersLength = strlen( $characters ) - 1;
	
	//Obfuscate string letter by letter.
	foreach( $emailLetters as $letter ) {
		
		//Get the current letter position in the string.
		$letterPos = strpos($characters, $letter);
		
		//If the character is present in our string of characters, 
		//we'll switch it; if not, we'll leave it as is.
		if( $letterPos !== false ) {
			
			$letterPos += $charactersLength / 2;
			
			//For letters that are in our characters string positioned 
			//after the total number of characters, we'll start from beginning.
			//For example, "v" becomes "1", "b" becomes "2"
			$letterPos = $letterPos > $charactersLength ? $letterPos - $charactersLength - 1 : $letterPos; 
			
			//Obfuscated letter.
			$newLetter = substr($characters, $letterPos, 1);
		
		} else {
			
			//Characters that aren't in our list will be left unchanged.
			$newLetter = $letter;
		
		}
		
		//We append obfuscated letter to the result variable.
		$obfuscatedEmail .= $newLetter;
		
	}
	
	//Sign @ is a control letter. Since more than one @ sign is illegal 
	//in email address, we're going to use two @ symbols to know when
	//the string has been obfuscated (and needs deobfuscation).
	//That way you can use obfuscated e-mail only in href attribute,
	//while the link text can be something entirely different. 
	//An example: <a href="mailto:myemail@gmail.com">This is my email</a>.
	return $obfuscatedEmail . '@';

}

After you include function into your code, obfuscation can be preformed like this:

<?php 
echo obfuscateEmail('mymail@mail.com');  //Obfuscated string will be: 4FQ.RGS4@RGS4E4@
?>

And in a real life usage, you’d create an obfuscated e-mail link like this:

<a class="obfuscatedEmail" href="<?php echo obfuscateEmail('mailto:mymail@mail.com'); ?>" rel="nofollow"><?php echo obfuscateEmail('mailto:mymail@mail.com'); ?></a>

We’re adding a class obfuscatedEmail to each obfuscated link. That way it will be easy for us to find all elements that need deobfuscation.

E-mail deobfuscation (JavaScript & CSS)

Step one is to detect all obfuscated links on a page. That’s fairly easy to do; we just find all elements that have class obfuscatedEmail.

//This is written to be as general as possible; you can leave this part out and just call
//deObfuscateEmail() manually on elements that should be deobfuscated.

var obfuscatedEmails = document.getElementsByTagName('a'),
    obfuscatedEmailsLength = obfuscatedEmails.length,
    i = 0;

for( ; i<obfuscatedEmailsLength; i++ ) {

    if( obfuscatedEmails[i].className.indexOf('obfuscatedEmail') > -1 ) {
    
        deObfuscateEmail( obfuscatedEmails[i] );
        
    }
    
}

I encourage you to adjust this part to your own needs. If you have only one e-mail on the page, you don’t have to loop through all page elements. Something like this would suffice:

//PHP
<a id="myObfuscatedEmail" href="<?php echo obfuscateEmail('mailto:mymail@mail.com'); ?>" rel="nofollow"><?php echo obfuscateEmail('mailto:mymail@mail.com'); ?></a>

//JavaScript
deObfuscateEmail( document.getElementById(‘myObfuscatedEmail’) );

Function deObfuscateEmail still hasn’t been created. We’ll get to it after we create a function that will change back e-mail letters based on ROT13 algorithm.

//This is a first layer of deobfuscation.
//Basically a reversed ROT13 algorithm.
function changeLetters(string) {
    
    //Helper variables.
    var currentLetter, 
        currentPos,
        currentString = '',
        
        //Behold! The one and only counter.
        i = 0,
        
        //We're going to loop through the obfuscated strings characters, so this will come in handy.
        stringLength = string.length - 1,
        
        //Characters that will be used when deobfuscating email address.
        //Same as string in PHP obfuscate function (obfuscateEmail).
        characters = '123456789qwertzuiopasdfghjklyxcvbnmMNBVCXYLKJHGFDSAPOIUZTREWQ',
        charactersLength = characters.length;

    
    //Counter variable has been declared before.
    for( ; i<stringLength; i++ ) {
        
        //This letter will be deobfuscated.
        currentLetter = string.charAt(i);
        
        //Position of the letter in our characters string.
        currentPos = characters.indexOf(currentLetter);
        
        //If character is present in our string, replace it with a character
        //30 places before (opposite from obfuscating).
        //If not, leave it as it is (because character wasn't obfuscated).
        if( currentPos > -1 ) {
        
            currentPos -= (charactersLength-1) / 2;
            currentPos = currentPos < 0 ? charactersLength + currentPos : currentPos;
            
        } else {
        
            currentString += currentLetter;
        
        }
        
        //Finally, append a character to our temp string that will be returned.
        currentString += characters.charAt(currentPos);
        
    }

    return currentString;

}

We’ll call this function on each letter of obfuscated e-mail.

Here’s the final part of the code, the one that takes an HTML element as a parameter, checks its value and href attribute and calls changeLetters function on each of its characters.
Then it adds CSS rules to correctly show reversed address and binds mouseover event on the link, which triggers final function that re-reverses the address.

//Function that will handle email deobfuscation.
//@param element is a reference to html element that will be deobfuscated.
//Deobfuscation is done on text and on href attribute of the element.
//Nevertheless, function will work well with any element you pass in, 
//even if href attribute won't be present.
function deObfuscateEmail( element ) {
    
    //Get the text of the element.
    var text = element.innerHTML,
        
        //Get href attribute. If there is no href attribute, set href value to be an empty string.
        //Regular expression is an IE Fix.
        //Namely, IE appends obfuscated email to the url (www.domain.com/com.liameym@em).
        //Therefore, the first part of the link needs to be removed (we grab just everything after the last forward slash '/').
        href = element.getAttribute('href').replace(/http:\/\/(.+)\//gi, '') || '',
        
        //Control variable. if the two @ symbols are present, we will perform deobfuscation,
        //if not, the string is not obfuscated and doesn't have to be deobfuscated.
        textReplace = text.search(/@.+@/),
        hrefReplace = href.search(/@.+@/),
        
        //This function handles the second layer of deobfuscation.
        //It is called later in the code.
        //Letters of the email are reversed (again) and css direction returned back to ltr.
        //This is called on mouseover event.
        reverseEmails = function(){
            
            //Only if htef is obfuscated.
            if( hrefReplace > -1 ) {
                
                //That's the reversing part right here.
                element.setAttribute('href', href.split('').reverse().join('') );
                
            }
            
            //Only if text is obfuscated.
            if( textReplace > -1 ) {
            
                //Reverse the text of the element and
                //return the direction to normal (left to right).
                element.innerHTML = text.split('').reverse().join('');
                element.style.direction = 'ltr';
                element.style.unicodeBidi = 'normal';
                
            }
            
            
            //Letters are replaced and the event isn't needed anymore.
            if( element.removeEventListener ) {
                
                element.removeEventListener('mouseover', reverseEmails, false);
                
            } else {
            
                // IE8-
                element.detachEvent('onmouseover', reverseEmails);
                
            }

        
        };
        //End variables and functions definitions.
    

    //href has to be processed first, because of the strange 
    //IE bug that will mix the href and innerHTML values.
    if( hrefReplace > -1 ) {
        
        href = changeLetters(href);
        element.setAttribute('href', href);

    }
    
    //Change the direction of the text to show real address
    //to users, instead of an reversed one.
    if( textReplace > -1 ) {
        
        text = changeLetters( text );
        element.innerHTML = text;
        element.style.direction = 'rtl';
        element.style.unicodeBidi = 'bidi-override';
        
    }
    
    
    //Since we have a rtl text, user can't copy or click on a link.
    //Therefore we'll replace the value as soon as user hovers over the link.
    if( element.addEventListener ) {
        
        element.addEventListener('mouseover', reverseEmails, false);
        
    } else {
    
        element.attachEvent('onmouseover', reverseEmails);
    
    }
        
}

If we put all these pieces of code together, we will get the fully functional deobfuscation script:

(function(){
    
    //This is a first layer of deobfuscation.
    //Basically a reversed ROT13 algorithm.
    function changeLetters(string) {
        
        //Helper variables.
        var currentLetter, 
            currentPos,
            currentString = '',
            
            //Behold! The one and only counter.
            i = 0,
            
            //We're going to loop through the obfuscated strings characters, so this will come in handy.
            stringLength = string.length - 1,
            
            //Characters that will be used when deobfuscating email address.
            //Same as string in PHP obfuscate function (obfuscateEmail).
            characters = '123456789qwertzuiopasdfghjklyxcvbnmMNBVCXYLKJHGFDSAPOIUZTREWQ',
            charactersLength = characters.length;

        
        //Counter variable has been declared before.
        for( ; i<stringLength; i++ ) {
            
            //This letter will be deobfuscated.
            currentLetter = string.charAt(i);
            
            //Position of the letter in our characters string.
            currentPos = characters.indexOf(currentLetter);
            
            //If character is present in our string, replace it with a character
            //30 places before (opposite from obfuscating).
            //If not, leave it as it is (because character wasn't obfuscated).
            if( currentPos > -1 ) {
            
                currentPos -= (charactersLength-1) / 2;
                currentPos = currentPos < 0 ? charactersLength + currentPos : currentPos;
                
            } else {
            
                currentString += currentLetter;
            
            }
            
            //Finally, append a character to our temp string that will be returned.
            currentString += characters.charAt(currentPos);
            
        }

        return currentString;
    
    }
    
    //Function that will handle email deobfuscation.
    //@param element is a reference to html element that will be deobfuscated.
    //Deobfuscation is done on text and on href attribute of the element.
    //Nevertheless, function will work well with any element you pass in, 
    //even if href attribute won't be present.
    function deObfuscateEmail( element ) {
        
        //Get the text of the element.
        var text = element.innerHTML,
            
            //Get href attribute. If there is no href attribute, set href value to be an empty string.
            //Regular expression is an IE Fix.
            //Namely, IE appends obfuscated email to the url (www.domain.com/com.liameym@em).
            //Therefore, the first part of the link needs to be removed (we grab just everything after the last forward slash '/').
            href = element.getAttribute('href').replace(/http:\/\/(.+)\//gi, '') || '',
            
            //Control variable. if the two @ symbols are present, we will perform deobfuscation,
            //if not, the string is not obfuscated and doesn't have to be deobfuscated.
            textReplace = text.search(/@.+@/),
            hrefReplace = href.search(/@.+@/),
            
            //This function handles the second layer of deobfuscation.
            //It is called later in the code.
            //Letters of the email are reversed (again) and css direction returned back to ltr.
            //This is called on mouseover event.
            reverseEmails = function(){
                
                //Only if htef is obfuscated.
                if( hrefReplace > -1 ) {
                    
                    //That's the reversing part right here.
                    element.setAttribute('href', href.split('').reverse().join('') );
                    
                }
                
                //Only if text is obfuscated.
                if( textReplace > -1 ) {
                
                    //Reverse the text of the element and
                    //return the direction to normal (left to right).
                    element.innerHTML = text.split('').reverse().join('');
                    element.style.direction = 'ltr';
                    element.style.unicodeBidi = 'normal';
                    
                }
                
                
                //Letters are replaced and the event isn't needed anymore.
                if( element.removeEventListener ) {
                    
                    element.removeEventListener('mouseover', reverseEmails, false);
                    
                } else {
                
                    // IE8-
                    element.detachEvent('onmouseover', reverseEmails);
                    
                }

            
            };
            //End variables and functions definitions.
        
    
        //href has to be processed first, because of the strange 
        //IE bug that will mix the href and innerHTML values.
        if( hrefReplace > -1 ) {
            
            href = changeLetters(href);
            element.setAttribute('href', href);

        }
        
        //Change the direction of the text to show real address
        //to users, instead of a reversed one.
        if( textReplace > -1 ) {
            
            text = changeLetters( text );
            element.innerHTML = text;
            element.style.direction = 'rtl';
            element.style.unicodeBidi = 'bidi-override';
            
        }
        
        
        //Since we have a rtl text, user can't copy or click on a link.
        //Therefore we'll replace the value as soon as user hovers over the link.
        if( element.addEventListener ) {
            
            element.addEventListener('mouseover', reverseEmails, false);
            
        } else {
        
            element.attachEvent('onmouseover', reverseEmails);
        
        }
            
    }
    
    

    //We could use native getElementsByClassName in browsers that support this method,
    //but I did a few quick tests and it seems to be slower. 
    //If you have more info on performance getElementsByClassName vs getElementsByTagName + className.indexOf please let me know.
    //var obfuscatedEmails = document.getElementsByClassName('obfuscatedEmail'),
    
    //This is written to be as general as possible; you can leave this part out and just call
    //deObfuscateEmail() manually on elements that should be deobfuscated.
    
    var obfuscatedEmails = document.getElementsByTagName('a'),
        obfuscatedEmailsLength = obfuscatedEmails.length,
        i = 0;
    
    for( ; i<obfuscatedEmailsLength; i++ ) {
    
        if( obfuscatedEmails[i].className.indexOf('obfuscatedEmail') > -1 ) {
        
            deObfuscateEmail( obfuscatedEmails[i] );
            
        }
        
    }
    
}());

DemoSource files

Conclusion

With the amount of spam that is being sent over the internet, you surely don’t want to leave your e-mail address unattended; do everything you can to prevent it from falling in wrong (spammy) hands.

Technique that I’ve shown you here should be (if nothing else) at least a good start.

Additional readings:

Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedInShare on RedditShare on StumbleUpon



  • http://www.influxwebdesign.com Matt Zimmermann

    Great tutorial. I think this one is going in my developer tool box.

    • http://codeforest.net Zvonko

      Thanks, Matt. Zoran really made a great effort to produce this high quality article.

      Hope that you will use it on some of your projects.

    • http://twitter.com/ZoranJambor Zoran Jambor

      Thanks Matt, I’m glad you find it useful.

  • Noel

    Wow, this is really awesome content.

    I was looking for something similar the other day, found some good resources, but this is really extraordinary.

    Can I use this in my development project?

    Great work.

    • http://twitter.com/ZoranJambor Zoran Jambor

      Thanks a lot Noel.

      Sure you can use it in your projects! Please drop me a link if you do, I’d love to see it in action. :)