Sometimes I do overthink things. I wrote a plugin to protect Gravatar image URLs.

About a month ago there was a post on Wordfence’s blog titled “Gravatar Advisory: How to Protect Your Email Address and Identity” which sounds more serious than it is.

The short version is this: it is possible that your email address can be determined by your Gravatar image source URL. The email address example@example.com can be md5’ed to 23463b99b62a72f26ed677cc556c44e8. The URL for that email takes the hash and appends it to a URL like so.

https://secure.gravatar.com/avatar/23463b99b62a72f26ed677cc556c44e8

Which gets you the default Gravatar image.

Default Gravatar

Read the article, it’s a good description of this. With enough time and the right hardware then the one way md5 hash can be used to get the email address.

The article points out that email is considered to be personally identifiable information (PII) by the National Institute of Standards and Technology (NIST). See section 2.2 on page 2-2 of this “Guide to Protecting the Confidentiality of Personally Identifiable Information (PII)” for even more examples.

Is this a big deal?

I don’t think so. To be more accurate, I don’t worry about data that is given out so freely and readily.

Data protection is serious business and your email address is definitely PII. So is your name, your IP address on the Internet, social security number, etc. Some of that is more risky than others. I don’t want my email address to get out there but it already is along with my name.

There are personal details that you do want to protect but your email address isn’t one of them. You give your email address to too many people and places for it to be considered a risk. You use it to log into services such as Facebook, Google, Twitter and your own WordPress site.

Your email address has become the same level of PII that your name is.

My name is Jan Dembowski. I’m not worried about that detail getting out there. That doesn’t mean you should spread my email around but like my name, it is already out there.

Email Address Holders Of The World Unite!

What if you, as a responsible WordPress user, wanted to enjoy the nice Gravatar service that so many people signed up for but you don’t want to expose the md5 hash of their email addresses? Then I have a plugin for you.

My Tin Foil Hat Gravatars plugin will still use Gravatars but instead of presenting an image loaded from a (possibly data leakage friendly) gravatar.com URL it will inline the image in your HTML source.

This plugin will use the WP HTTP API to fetch the Gravatar image. Then it will base64 encode the image and replace the Gravatar URL with a data:image/jpeg;base64 inline image instead. If there’s no Gravatar for a specific email then the md5 will be replaces with the hash for example+number@example.com.

To validate if the email has a Gravatar I re-used Justin Heideman’s code found on this gist. Why re-invent the wheel? And I like the wp_cache part too in his function.

If you look at a comment on this site then right click the Gravatar and select “Open image in new tab” then you’ll see the data:image as the URL instead.

This plugin does not modify anything in your WordPress installation. It uses the get_avatar filter to modify the HTML. Because it uses a filter it plays nicely with other Gravatar plugins such as the Wapuuvatar plugin.

There is a thing to consider: if you have a post with 10 comments then every time that post is visited your WordPress will retrieve 10 Gravatar images. This can be minimized by using a caching plugin. Or I may do it in the plugin using wp_cache for the retrieved images.

The plugin will retrieve the Gravatar and adjust the HTML with the inline image. This will be saved in a transient for 30 minutes which will reduce the amount of times your site will connect to gravatar.com.

If I use this plugin then my commenter’s emails are safe?

Of course not.

User’s sign up for Gravatar. It’s not an automatic process, you have to create a WordPress.COM account to get one. The base64 images this plugin produces can be uploaded to Google and you can search for that image elsewhere. Once you find the image, you’ll get the md5 hash that the Wordfence blog advisory was about.

I wrote this plugin because I read the post and thought “OK. What if I wanted to use Gravatars but don’t want to share a possibly iffy md5?” I also wanted to fool around more with the WP HTTP API.

Installing the plugin

Visit the plugins gist page via this link. There you will find a “RAW” button on the right.

Click that and you’ll get the ASCII source of the plugin in your browser.

Then save the raw text to a file in your /wp-content/plugins/ directory as not-gravatar.php and then activate the “Tin Foil Hat Gravatar” plugin in your dashboard.

If anything goes askew then just use the file manager of your choice and delete the not-gravatar.php file in wp-content/plugins and you’ll be all set.

Feel free to peruse the plugin source. It’s not a complicated plugin and there’s no options. Just activate it and go. I tested it with Wapuuvatar but I’d love feedback on this code.

In the meanwhile enjoy using Gravatar like a card carrying Tin Foil Hat user would.


<?php
/*
Plugin Name: Tin Foil Hat Gravatars
Description: Use Gravatars but don't use Gravatars. What? Shut up.
Author: Jan Dembowski
Author URI: https://blog.dembowski.net/
Version: 0.7
License: GPL2
*/
add_filter( 'get_avatar', 'tinfoilhat_get_avatar', 10, 6 );
function tinfoilhat_get_avatar( $avatar, $id_or_email, $size, $default, $alt, $args ) {
// Are we in the WordPress dashboard? If so stop, don't
// bother with any Gravatar substitutions.
if ( is_admin() ) return $avatar;
// Save the orginal hash of the unmodified $avatar
$mh_avatar_hash = md5( $avatar );
// Is the result already in a transient? If so get and return that.
$mh_transient_avatar = get_transient( 'mh_notgravatar_' . $mh_avatar_hash );
if( $mh_transient_avatar ) {
$avatar = '<!– Transient Gravatar –>' . $mh_transient_avatar;
return $avatar;
}
// If there's no Gravatar to hide then just return $avatar after
// the email hash is munged.
if( !(mh_validate_gravatar( $id_or_email ))) {
// Get md5 for example+number@example.com
$mh_email_hash = md5( strtolower( trim( 'example+' . rand(1,2000) . '@example.com' )));
// Replace the current md5 with the new one
$avatar = preg_replace( '/gravatar.com\/avatar\/.{32}/', 'gravatar.com/avatar/' . $mh_email_hash, $avatar );
// Save the new $avatar in a transient for the next time
set_transient( 'mh_notgravatar_' . $mh_avatar_hash, $avatar, 30 * MINUTE_IN_SECONDS );
return $avatar;
}
// If there is a valid Gravatar then remove the URL and inline the image.
// Regex and preg_match_all to put all urls from $avatar into an array.
$mh_src_regex = "/'(http|https)\:(.*?)[' ]/";
preg_match_all( $mh_src_regex , $avatar, $mh_urls );
// Go through that array and replace src='url' with src='data:…'
foreach( $mh_urls[0] as $match ) {
// Remove the first and last characters in the string. The regex
// is close enough for government work but has 1 extra character
// at the start and end.
$mh_old_url = substr( $match , 1, -1 );
// Download the image into an array.
$mh_image = wp_remote_get( $mh_old_url );
// base64 encode the image from the body part of that download.
$mh_inline_gravatar = base64_encode( $mh_image['body'] );
// I need the content type from the headers for JPEG/PNG or
// whatever was retrieved.
$mh_content_type = $mh_image['headers']['content-type'];
// Put it together and make the inline substitution for the avatar
$mh_inline_src = 'data:' . $mh_content_type . ';base64,' . $mh_inline_gravatar;
$avatar = str_replace( $mh_old_url, $mh_inline_src, $avatar );
}
// Save the new $avatar in a transient for the next time
set_transient( 'mh_notgravatar_' . $mh_avatar_hash, $avatar, 30 * MINUTE_IN_SECONDS );
return $avatar;
}
// I copied this whole from https://gist.github.com/justinph/5197810
// Justin Heideman (https://github.com/justinph) wrote validate_gravatar
// and it's very complete. The wp_cache part is neat.
//
// I prefixed the function name in case a validate_gravatar() exists
// in future WordPress versions.
function mh_validate_gravatar($id_or_email) {
//id or email code borrowed from wp-includes/pluggable.php
$email = '';
if ( is_numeric($id_or_email) ) {
$id = (int) $id_or_email;
$user = get_userdata($id);
if ( $user )
$email = $user->user_email;
} elseif ( is_object($id_or_email) ) {
// No avatar for pingbacks or trackbacks
$allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
if ( ! empty( $id_or_email->comment_type ) && ! in_array( $id_or_email->comment_type, (array) $allowed_comment_types ) )
return false;
if ( !empty($id_or_email->user_id) ) {
$id = (int) $id_or_email->user_id;
$user = get_userdata($id);
if ( $user)
$email = $user->user_email;
} elseif ( !empty($id_or_email->comment_author_email) ) {
$email = $id_or_email->comment_author_email;
}
} else {
$email = $id_or_email;
}
$hashkey = md5(strtolower(trim($email)));
$uri = 'http://www.gravatar.com/avatar/' . $hashkey . '?d=404';
$data = wp_cache_get($hashkey);
if (false === $data) {
$response = wp_remote_head($uri);
if( is_wp_error($response) ) {
$data = 'not200';
} else {
$data = $response['response']['code'];
}
wp_cache_set($hashkey, $data, $group = '', $expire = 60*5);
}
if ($data == '200'){
return true;
} else {
return false;
}
}