How to Build a Digital Decoder Ring Using PHP, FilePreviews.io and Twilio MMS

October 07, 2014
Written by

e7b3_secret_decoder_ring

Over the past few months I’ve been rewatching one of my favorite TV shows, Alias, on Netflix. For those of you who haven’t experienced this television classic, Alias follows CIA agent Sydney Bristow as she travels around the world solving mysteries and beating up bad guys. If you are like me you’ve probably always wanted to tap into your inner Sydney Bristow and become a kick-ass CIA spy. Today I’m going to show you how to use PHP, FilePreviews.io and Twilio MMS to come one step closer to that dream. We’re going to build an app that lets you snap a picture of a Caesar ciphered text, send it to a phone number and get a response with the decoded text. If you’re in a rush to save the world, you can find the finished code for our app on GitHub.

Our Tools

How It Works

Want to try it out now? Download this image of a Caesar ciphered text and send it to (646) 791-3807.

Analyzing Our Image

In order to detect text from our images we’re going to use OCR (optical character recognition). OCR is a very hard task. When I encounter hard problems I try to find APIs to make them easier. Enter FilePreviews.io. FilePreviews does a lot of really cool things with files but most important to us it lets you send in an image and returns the OCR results for that image. Exactly the tool we need in our spy toolkit.

To get started, sign up for a FilePreviews account and create a new app. Make note of the API key you get for this new app, we’ll be using it shortly.

Although FilePreviews doesn’t have PHP helper library, it has a REST API that we can use cURL to interact with. Let’s open up a new file called incoming.php and get started. We’ll kick off by setting a couple variables that contain the data we’ll use for our request:

<?php

$filepreview_url = "https://api.filepreviews.io/v1/";
$data = array(
  "url"=>"http://rickyrobinett.com/digitaldecoderring/sample-cipher.jpg",
  "metadata"=>Array("ocr"),
  "api_key"=>"FILEPREVIEWS_KEY"
);

We set the filepreview_url that we’ll use when we make our API request and the data we’ll be passing our request. Our data is an array with three elements:

  • url – the url the the image we want to pass to the API. Right now we’re sending a sample cipher image.
  • metadata – an array containing the metadata we want about this file, in the case we just want ocr
  • api_key – replace this with your FilePreviews API key for the app we just created.

Now that we have our variables in place let’s write the code that makes a POST request to the API to get the info we need:

$ch = curl_init($filepreview_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

As I mentioned earlier, we’re using PHP’s cURL library to make the request to the FilePreviews API. After we initialize the request with the filepreview_url, we use the curl_setopt function to set some options specific to our request. CURLOPT_RETURNTRANSFER tells cURL to return the results of request (as opposed to just TRUE on success which is default). The next three options tell cURL to make a post request with Content-type of application/json and send a JSON encoded copy of the data we set earlier.

Now that we have our setup in place, let’s write to code to make the request and check that the response was successful:

$response = json_decode(curl_exec($ch));
if($response->error){
  echo $response->error;
} else {
  echo 'yay!';
}

Pop open a browser and load up your incoming.php file. You should see the word “yay!”. We can now hop back into our FilePreviews dashboard and click on our app to see a history of our successful requests. We’ll work on pulling the OCR content a bit later but now that we’re successfully making requests to FilePreviews let’s move onto making things a bit more dynamic.

Receiving Images Via MMS

A spy doesn’t often have time to update a hard coded URL in a PHP file. Let’s update our code to receive our cipher image from an incoming Twilio MMS. First we need to configure our Twilio Messaging Request URL. Hop into your Twilio account, pick the number you want to use for this app and add the full URL for your incoming.php file. Twilio needs this URL to be publicly accessible so it can hit it. If you’re currently running locally you can deploy to your favorite server or use ngrok to expose your localhost to the outside world. If you haven’t used ngrok before you can kick off with our multi-platform or Windows focused tutorials.

When a user sends an MMS to your Twilio phone number Twilio will make a request to the URL you just set to give you information about the message. Now that we’ve got our configuration in place, let’s update our incoming.php file to use this information when we make our request to FilePreviews.

<?php
$filepreview_url = "https://api.filepreviews.io/v1/";
$media = $_POST['MediaUrl0'];
$from = $_POST['From'];
$data = array(
  "url"=>$media,
  "metadata"=>Array("ocr"),
  "api_key"=>"FILEPREVIEWS_KEY",
  "data"=>Array("From"=>$from)
);

We’re pulling MediaUrl0 from our POST data. This will be the first piece of media that a user included in their MMS message. Which in our case, will be a URL to our the the image of our ciphered code. We’re also pulling the From number. We want to track this so we know who to update when we’ve finally cracked the code.

Once we have these two pieces of information we update the data we’re sending to FilePreviews. We’ll switch out the hard coded URL we were using previously with the URL pointing to our media. We will additionally add a new piece of data to our request called data. This is user-specified key-value data that FilePreviews will associate with our request.

The OCR on this image could take a few minutes so right now let’s just add an SMS response letting the user know we’re hard at work cracking the code. To do this we’ll be using TwiML. TwiML is set of instructions (in the form of XML) that you can use to tell Twilio what to do when it receives a phone call or text message. Let’s return update our incoming.php file to return TwiML when it receives a request.

We’ll be using the Twilio PHP helper library to generate our TwiML so first let’s install it using composer.

composer require twilio/sdk

Let’s update our incoming.php file to autoload any libraries we require with composer.

<?php
require 'vendor/autoload.php';

Previously in our incoming.php file, we were just echoing some simple strings depending on the success or failure of our API request. Let’s replace that code to instead send back the appropriate TwiML to notify a user what’s going on.

header('Content-type: text/xml');
$TwiML = new Services_Twilio_Twiml();
if($response->error){
  $TwiML->message('Something went wrong. Get outta there!');
} else {
  $TwiML->message('Thanks! Our supercomputers are working hard to crack the code now.');
}
print $TwiML;

Give it a try! Send an MMS to your digital decoder ring phone number and wait as you get a response.  If it doesn’t, head over to the Twilio App Monitor to find helpful information that should help you debug. Let’s now get to work on getting the OCR response and deciphering it.

Cracking the Code

FilePreviews gives us the option to set a callback URL that will be triggered when image processing is complete. Let’s edit our Digital Decoder Ring app to set the callback URL to point at outgoing.php on our server.

When FilePreviews passes the information about our image to our callback URL they send it as a JSON Web Token. We’ll be using the firebase/php-jwt library to parse this token. Let’s install it using composer:

composer require firebase/php-jwt

Now let’s get to work creating our outgoing.php file:

<?php
require 'vendor/autoload.php';
$body = @file_get_contents('php://input');
$filepreviews_secret_key = "SECRET_KEY";
$decoded = JWT::decode($body, $filepreviews_secret_key);
$text = $decoded->results->metadata->ocr[0]->text;

First we’re making sure any libraries we’ve required with composer will autoload. Next, we’re calling file_get_contents on php://input to read the raw POST data that is sent to the this request. Then we’ll decode the data using our JWT library and our FilePreviews Secret Key. Make sure to replace your secret key with the key in your FilePreviews dashbooard. This will give as an object where we can pull out our OCR text.

Now that we have our text, we need to crack the Caesar Cipher. I couldn’t find a library to do this with PHP but fellow Twilion Rob Spectre put together a great Python library to do this. Of course, we’re already writing this in PHP and we don’t want to have to use a Python library. Don’t worry, I spent this weekend porting Rob’s awesome Caesar Cipher library to PHP. Let’s require it using composer:

composer require caesarcipher/caesarcipher

Now we can add this to the end our outgoing.php file to use this library to crack our code:

$cipher = new CaesarCipher\CaesarCipher();
$decoded_text = $cipher->crack($text);

We’re close! One last step. Now that we’ve got the decoded text let’s add some code at the end of outgoing.php to respond to the original user with our decoded text:

$sid = "ACXXXXXXXXXXXXXXXX"; // Your Account SID from www.twilio.com/user/account
$token = "YYYYYYYYYYYYYYYY"; // Your Auth Token from www.twilio.com/user/account
$client = new Services_Twilio($sid, $token);
$message = $client->account->messages->sendMessage(
  '555-555-5555', // From your Twilio Number
  $decoded->data->From, // Text this number
  $decoded_text
);

We’re using the Twilio PHP helper library again. This time we’re using the sendMessage function to send a text message with our decoded text. This function takes 3 parameters:

  • From Number – The number you want to the message to come from. In this case, your Twilio phone number.
  • To Number – remember earlier when we passed the number of the person who sent in the image to the FilePreviews API? We can access that number in the data they passed in the JWT.
  • Body – the body of our text message. In this case, the decoded text.

We’re now able to run OCR on a Caeser cipher using Twilio MMS + Filepreviews.io and then decode it using PHP. Snap a picture and give it a try:

Ready To Save the World?!?

Please make sure to use your newly learned spy talents for good and not evil. Bonus points – can you update the code to encode ciphers instead of decode them? Have any questions or want to show off what you’re doing with Twilio MMS? Drop me a note on twitter or e-mail.