How-to: Improve your online privacy – level 2 – encrypted email

1. Introduction

In my last “online privacy” article, I looked at how we can improve our privacy while browsing the web. So far, so good. But what about email? As it happens, email is problematic.

Growing from one of the oldest-established internet standards, email has changed very little from its inception. Email content is sent in plain text, just as it was on day one. Attachments are encoded to facilitate transmission, but any old email program can decode them.

Given the widespread use of email, we might wonder that there is no universally-agreed standard for transmitting messages securely. The big problem here is complexity. Email is used by people from all walks of life and all levels of computing ability. For universal acceptance, the barrier to entry must be kept very low (this is one reason why Dropbox is so successful – it’s easy). But security almost always increases complexity and decreases usability. We have options, but they all make email harder to use (even if that might be just slightly).

2. Simple but limited encryption: SecureGmail

SecureGmailI’ve recently come across a pretty simple option for encrypting email. Unfortunately simplicity comes with limitations. SecureGmail is an extension for the Chrome browser that enables encryption of email between Gmail users. So immediately you can see two limitations: firstly, the sender and recipient must both be using Gmail and secondly, they must both be using Chrome. You can’t use this to send a single email securely to all your contacts (unless they all happen to fit those criteria).

Also, SecureGmail does not encrypt attachments – just the text in the email. Still, you could zip the attachment, encrypting it with a password, and include that password in the secure part of the email.

A further limitation is that SecureGmail uses a single key to encrypt and decrypt the message. This differs from PGP encryption, where the sender uses a recipient’s “public key” to encrypt an email and the recipient uses a “private key” (known to no one else) to decrypt the message. PGP gives you a reasonably high degree of certainty that only the recipent can read the message, assuming the private key is kept safe (everything depends on this).

So there are some sacrifices to be made, in order to use SecureGmail. If you can live with that, it’s a great option – because it’s easy. Head over to SecureGmail and follow the instructions there.

3. Robust encryption: Enigmail

If you want to do this right, you have to use something like PGP encryption. I say “something like”, because although PGP is the standard more people have heard of, it is actually less common than the alternative GPG. Oh, and GPG is an implementation of the OpenPGP standard. Confusing, huh? PGP (“Pretty Good Privacy”) is proprietary and not free for commercial use. GPG (“Gnu Privacy Guard”) and OpenPGP were originally intended to provide a free, open source alternative to PGP. In fact GPG is more secure than PGP, since it uses a better encryption algorithm. Because it’s free and more secure than than PGP, I will focus here on GPG. Also, there are many different ways of skinning this cat, so I’ll just point you in a direction that’s free and one of the easiest ways of doing this. Note that the following instructions are for Windows.

3.1 Setting up your Enigmail environment

You’ll need:

Install Thunderbird. When installing Gpg4Win, you don’t need any of the optional extras, but you may install them if you wish. When you get to the “Define trustable root certificates” dialogue, you can select “Root certificate defined or skip configuration” and click “Next”.

If you’re using Firefox as your browser, make sure you right-click and save Enigmail, otherwise Firefox will try to install the extension. All other browsers will normally just download the file.

Run Thunderbird and click the menu (triple horizontal lines icon, top right), then Add-ons. Then click the cog icon (near the search box, top right) and “Install add-on from file”. Locate and install the Enigmail add-on you downloaded previously. You will need to restart Thunderbird to complete the installation. Then, if you’ve not already set up your email account in Thunderbird, do so now.

Add-ons Manager - Mozilla Thunderbird

Go to Thunderbird’s menu –> OpenPGP

Enigmail

–> Key Management

Enigmail_02

In the OpenPGP Key Management window, click Generate –> New Key Pair.

Enigmail_03

Choose and enter a secure passphrase. This should be hard for anyone else to guess. I tend to pick a line from a song. Yes, it takes a while to type, but it’s highly unlikely that anyone will ever crack it through brute force. Bear in mind though that if you forget the phrase, you’re stuck.

Back in the Key Management window, if you check the box “Display All Keys by Default”, you’ll see your new key along with its 8 character identifier.

Enigmail_04

Next click the key, then Keyserver –> Upload Public Keys. This permanently publishes the “public” part of your key (which people use to encrypt messages to you). Accept the default keyserver when prompted.

Enigmail_06

3.2 Key exchange with Enigmail

In order to send and receive emails securely, both you and your correspondent must have a public/private key pair. Whoever you’re writing to, they’ll need to have gone through the steps above (or something similar). Once you’re ready, you need to pass to each other your public keys.

Sometimes this public/private thing confuses people. But it’s pretty easy to remember what to do with each key. Your public key – well that’s public. Give it away as much as you like. There’s no shame in it. 😉 Your private key? Guard it with your life. Hopefully you will have chosen a secure passphrase, which will make it difficult for anyone else to use your private key, but you don’t want to weaken your two-factor authentication at any time (something you have – the private key, and something you know – the passphrase) by letting go of the “something you have” part.

Anyway, you don’t really need to know or understand how this works. Just make sure you and your correspondent have both published your keys to a key server. Next, tell each other your key ids (remember the 8 character code generated with the key?) and/or email addresses. Import a public key like this:

Go to Thunderbird’s menu –> OpenPGP

Enigmail

–> Key Management

Enigmail_02

In the OpenPGP Key Management window, click Keyserver –> Search for Keys.

Enigmail_08

You can search by email address or by key id. If you’re searching by id, it must always start with “0x” (that just indicates that the key is in hexadecimal).

Enigmail_09

You should see your correspondent’s key in the next dialogue. Click “OK” to import it. This places your correspondent’s public key in a data store that is colloquially referred to as your “keyring”.

3.3 Sending encrypted email with Enigmail

You can only send encrypted email to someone whose public key is on your keyring. See the previous step for details. We use the public key to encrypt the contents of the email, meaning that only someone with access to the corresponding private key can decrypt and read the email. This gives you a high degree of certainty that no one other than your correspondent can see your message.

Compose your message in plain text. You can send in HTML, but it’s much harder to encrypt correctly.

Remember that while the contents of the email will be encrypted, the subject will not be. Before sending it, you need to tell Thunderbird to encrypt the email. There are three easy ways of doing this.

  1. Click OpenPGP –> Encrypt Message.
  2. Press Ctrl-Shift-E.
  3. Click the key icon, bottom right.

Enigmail_11

Enigmail will search for the public key that corresponds to your recipient’s address. If you don’t have the correct public key on your keyring (or you’ve typed the address incorrectly or whatever), you will be warned that there was no match.

Enigmail_12

If you’ve forgotten to compose in plain text, you will be warned about the problems of using HTML.

Enigmail_13

I would recommend configuring Thunderbird to use plain text by default, at least for your fellow users of encrypted email. In Account Settings under Composition & Addressing, just uncheck “Compose messages in HTML format”.

When your correspondent receives the encrypted message, it can only be read by using the correspondent’s private key. Until the message has been decrypted, it will look something like this:

-----BEGIN PGP MESSAGE-----
Charset: ISO-8859-1
Version: GnuPG v2.0.20 (MingW32)
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

hQEMA/dOFDapHX5yAQf/YbYJz+vm2AnzWDn08sOP66gVVoCBh/qnbcAcdSYkCTA2
WjuWfV3ZSFVwV+lYyr/VqgcHl607a7KIJEQh251RSQEJmNg56gC/JYNtj9frhaIT
Ay46xhyz2Ebj8EjcvSX+wcUh8Qd/YPMqZDFB/wBNnA48JxkwuxXBU0AFLYw2Osc2
gvUttSZfN/Dn3Mq0fMxqr2s+YZA9qZebfzzjfVIfDWvbtqTFo1HhWKkuCqgPbYQ2
SuinDVtzlQRSTbbygvWVltd7miKsb19hZS9KQRda5XpaaWC3FHrLBeeUf9FvhIIq
kzF53oXv/Tp/fcbnujp5A4cyy8Bkw8RFi6xbQ9Baw4UBCwOnkycIVlt3QQEH+JKi
HB4VqC6i/N8sL7stR063yE0RYcIn18th+zDbZtTt2QlaSyzoCHwDqYo4R8o8VXAo
oe68jO2N9c/FyX7iSzYpKVKW26UL2SaWk0yd40Gae1XoEfHozZ8lmz7c4cO/ionU
81MYqcMdqdIg948OE6if2yb3Cl65p70jOQ/Ep6h9tw33Iu+ukj8r7MCPYRNPxIGh
EvrwktK0Ej1qbDP296i+C6QgJxsV9qDeGbPIs5q54Bni60qnMeSV1ttxit7bOgMF
xCQR3XbwXU1qeLvfWa1sdhqBq0zbEXxWeBT/9Gq3EJ8ca8JXeaTAV4Ry26oHL5Cf
ZrH+kIMrYo6g0Agg2dJ2AYjfvS2bDRoOsvlNoUQj8X+VIlyR9XqiLTnyEeanzL0f
79dH5Rfk0yPURl1pqOT6Dv5ioHyQjtJuF1qt+pO6NSbVwuE9xeoU4KhdmnMAkeIX
y7adNBCKr6zhpElhdkt1kfWDeW0mqyshYU/aetFDbW0l3XzIcQ==
=w5DD
-----END PGP MESSAGE-----

Following decryption, the content of the message will be visible as usual. A padlock icon indicates that this message was encrypted before transmission.

Enigmail_14

3.4 Enigmail – conclusion

So this is all you need, to send and receive email securely. Not even the mighty PRISM can unlock the treasures in your encrypted email. And this solution isn’t merely limited to users of Thunderbird. The Gpg4Win project referred to above has a plugin for Outlook, which covers the vast majority of corporate users.

All is not sweetness and light however. Due to security limitations of browsers, there isn’t really a solution for webmail users. And there aren’t any bulletproof solutions for mobile users. To start with, Apple’s terms of use are incompatible with open source (GPL) software, so GnuPG is automatically excluded. There will probably never be a solution for a non-jailbroken iPhone or iPad.

With Android, you do have some options, using Android Privacy Guard and K-9 Mail. The end user experience is not perfect though and you’re still left with a fundamental problem: you have to put your private key on your mobile device. The private key is the one thing you really don’t want to risk losing, so is this a good idea anyway?

Personally, I would say if the email is so sensitive that you need to encrypt it, you probably should wait to read it, until you have access to your desktop/laptop and your secure email environment. But then that decreases usability of encrypted email, which is the main reason this has not yet gained significant traction.

As you can see, there do remain some technical and social obstacles to overcome before we see encrypted email in widespread use. But as long as you understand its limitations, and if you care about keeping your email private, the GPG/Enigmail proposition is really very compelling.

BlackBerry Z10, Gmail and 2-Step Authentication

Update: If you’ve arrived here looking for solutions to Gmail problems generally on the new BlackBerry OS 10, you might want to read my more recent article, Gmail + Google Apps on the BlackBerry Z10.

BlackBerry Bold 9900 and Z10
The old (Bold 9900) and the new (Z10) – any family resemblance is purely coincidental
One of the perks of my job is getting to try out some new mobile phones, once they’ve been released by my company’s preferred carrier. This week I took delivery of a BlackBerry Z10. It’s a really nice device, but given that my normal phone is a Samsung Galaxy Note 2, I’m afraid I became a little bored of the Z10 within 10 minutes of powering it up. (Yeah yeah, I know, first world problems.)

Inevitably with BlackBerry OS 10 (which is a stonkingly good OS, by the way), one of the problems was going to be that the apps are lagging behind mainstream Apple and Android offerings. Google’s Play store and Apple’s App Store have apps outnumbering BlackBerry apps by a factor of 10. Whether BlackBerry will catch up is a question not even the most far-seeing analysts can predict with confidence.

In the meantime, there will be some speed bumps encountered by dedicated BlackBerry aficionados, such as the one I ran into when attempting to set up my Gmail account today. Like all security conscious users, I have 2-step authentication enabled on my Gmail account. That means that whenever I log into Gmail using a new device or application, a text message is sent to my phone containing a one-time authorisation code, which then needs to be entered into whichever application is trying to use my Gmail credentials.

The email app on BlackBerry 10 is not (yet) capable of handling this process. That means you need to use one of the alternative methods of authentication. To start, you must (preferably from a computer) log into your Google 2-step verification page. Once there, you can be forgiven for thinking that Google has got you covered. There is, after all, a link saying “BlackBerry”:

2-step verification

If you follow the link, you’re instructed to browse to m.google.com/authenticator and download the Authenticator app to your phone, following which everything will be ticketyboo. Except it won’t. You are taken to a largely blank web page. Buried deep within Google’s online help is the reason: “To use Google Authenticator on your BlackBerry device, you must have OS 4.5-7.0.” No OS 10 support then. Bother.

Never fear. Go back to the 2-step verification page and instead choose the Manage application-specific passwords link. Go to the bottom of that page (“Step 1 of 2: Generate a new application-specific password”). Enter a name (like “BlackBerry Z10”) and click “Generate”. You’ll be given a code to enter in the IMAP password and SMTP password boxes within the email setup on your phone. Once you’ve done that, you’re good to go.

Rackspace API for CodeIgniter

Logo_lockup_version-2 SPOTRackspace is a great email hosting company, providing, amongst other things, a handy API for creating bespoke email solutions. The exercise of integrating that API into your application is of course left to the end user. I’ve spent some time working on a Rackspace API library for the PHP programming framework, CodeIgniter. This is not functionally complete – I have only implemented the interfaces that I needed – but it should provide a useful springboard for your own projects.

Configuration

In /system/application/config/RackspaceAPI.php:

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

$config['user_key']   = 'your user key';
$config['secret_key']     = 'your secret key';
$config['user_agent']     = 'name of your app';
$config['api_version']    = 'v0'; // amend if necessary
$config['rackspace_host'] = 'api.emailsrvr.com'; // amend if necessary

/* End of file RackspaceAPI.php */
/* Location: ./system/application/config/RackspaceAPI.php */

Library

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * Uses curl and pecl_http
 */
class Rackspace_API {
    
  /**
   * Store recent http_message
   * @var object
   */
  protected $_http_message;
  
  /**
    * CI object
    * @var object
    */
  protected $_ci;

  /**
   * Rackspace config items
   */
  protected $_user_key;
  protected $_secret_key;
  protected $_user_agent;
  protected $_api_version;
  protected $_rackspace_host;
  
  function __construct() {
    $this->_ci =& get_instance();
    $this->_ci->config->load('RackspaceAPI', TRUE);
    $this->_user_key = $this->_ci->config->item('user_key', 'RackspaceAPI');
    $this->_secret_key = $this->_ci->config->item('secret_key', 'RackspaceAPI');
    $this->_user_agent = $this->_ci->config->item('user_agent', 'RackspaceAPI');
    $this->_api_version = $this->_ci->config->item('api_version', 'RackspaceAPI');
    $this->_rackspace_host = $this->_ci->config->item('rackspace_host', 'RackspaceAPI');
  }


  /**
   * Get info about a domain
   * @param string $domain
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function getDomainInfo($domain) {
    return $this->genericGet('/customers/me/domains/'.$domain);
  }

  
  /**
   * Get all domain names
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | array (domains)
   */
  public function getDomains() {
    $obj = $this->genericGet('/customers/me/domains');
    if(!$obj->error){
      // Reformat into an array of domains
      foreach($obj->result->domains as $domain) {
        $domains[]=$domain->name;
      }
      $obj->result = $domains;
    }
    return $obj;
  }


  /**
   * Get info about a mailbox ($domain@$id)
   * @param string $domain
   * @param string $id
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function getMailboxInfo($domain, $id) {
    return $this->genericGet('/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id);
  }
   
 
  /**
   * Used by Get functions above - generalised use case
   * @param string $url - see the API; constructed by the calling function
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  private function genericGet($url) {
    $this->get(
        $url,
        'application/json');
    if($this->_http_message->getResponseCode() == 200) {
      // Call worked.  JSON is missing enclosing brackets, apparently needed by json_decode
      $json = '['.$this->_http_message->getBody().']';
      if(is_string($json)) {
        $obj = json_decode($json);
        $result->error = false;
        $result->result = $obj[0];
      } else {
        // JSON failure
        $result->error = true;
        $result->result = 'Failed to parse JSON';
      }
    } else {
      // API call failed
      $result->error = true;
      $result->result = $this->_http_message->getHeader("x-error-message");
    }
    return $result;
  }
  

  /**
   * Create a mailbox ($domain@$id)
   * @param string $domain
   * @param string $id
   * @param string $first: First name
   * @param string $last: Last name
   * @param string $name: Display as
   * @param string $office: Name of office/profit centre
   * @param string $locno: Office/profit centre number
   * @param string $password
   * @param string $fwd: comma-separated forwarding address(es) - max 4 off domain
   * @param string $save: save forwarded email - 'true' or 'false'
   * saveForwardedEmail
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function addMailbox($domain, $id, $first, $last, $name, $office,
          $locno, $password, $fwd, $save='true') {
    $fields = array(
        'password' => $password, 
        'emailForwardingAddresses' => $fwd,
        'firstName' => $first,
        'lastName' => $last,
        'displayName' => $name,
        'saveForwardedEmail' => $save,
        'organization' => $office,
        'organizationUnit' => $locno);
    return $this->genericPost( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
  }


  /**
   * Used by Post functions above - generalised use case
   * Note: Rackspace API suggests use POST to add, PUT to edit
   * @param string $url - see the API; constructed by the calling function
   * @param array $fields - data to be POSTed
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  private function genericPost($url, $fields) {
    $this->post(
        $url,
        $fields,
        'application/json');
    if($this->_http_message->getResponseCode() == 200) {
      $result->error = false;
      $result->result = $this->_http_message->getBody();
    } else {
      // API call failed
      $result->error = true;
      $result->result = $this->_http_message->getHeader("x-error-message");
    }
    return $result;
  }


  /**
   * Edit user's forwarding
   * @param string $domain
   * @param string $id
   * @param string $fwd: comma-separated forwarding address(es) - max 4 off domain
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function changeForwarding($domain, $id, $fwd) {
    $fields = array(
        'emailForwardingAddresses' => $fwd
        );
    return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
  }
  
    
  /**
   * Edit user's location
   * @param string $domain
   * @param string $id
   * @param string $office: Name of office/profit centre
   * @param string $locno: Office/profit centre number
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function changeLocation($domain, $id, $office, $locno) {
    $fields = array(
        'organization' => $office,
        'organizationUnit' => $locno);
    return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
  }
  
    
  /**
   * Edit user's name
   * @param string $domain
   * @param string $id
   * @param string $first: First name
   * @param string $last: Last name
   * @param string $name: Display as
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function changeName($domain, $id, $first, $last, $name) {
    $fields = array(
        'firstName' => $first,
        'lastName' => $last,
        'displayName' => $name);
    return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
  }
  
    
  /**
   * Edit user's password
   * @param string $domain
   * @param string $id
   * @param string $password
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function changePassword($domain, $id, $password) {
    $fields = array(
        'password' => $password);
    return $this->genericPut( '/customers/me/domains/'.$domain.'/rs/mailboxes/'.$id, $fields);
  }
  
    
  /**
   * Used by Put functions above - generalised use case
   * Note: Rackspace API suggests use PUT to edit, POST to add
   * @param string $url - see the API; constructed by the calling function
   * @param array $fields - data to be PUT
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  private function genericPut($url, $fields) {
    $this->put(
        $url,
        $fields);
    if($this->_http_message->getResponseCode() == 200) {
      $result->error = false;
      $result->result = $this->_http_message->getBody();
    } else {
      // API call failed
      $result->error = true;
      $result->result = $this->_http_message->getHeader("x-error-message");
    }
    return $result;
  }


  /**
   * Delete a mailbox
   * @param string $domain
   * @param string $id
   * @return stdClass Object ( 'error'  => bool,
   *                           'result' => string (error message) | stdClass Object
   */
  public function deleteMailbox($domain, $id) {
    return $this->genericDelete("/customers/me/domains/$domain/rs/mailboxes/$id");
  }
  
  
  /**
   * Used by Get functions above - generalised use case
   * @param string $url - see the API; constructed by the calling function
   * @return stdClass Object ( 'error'  => bool,
   *                           ['result' => string (error message)]
   */
  private function genericDelete($url) {
    $this->delete($url);
    if($this->_http_message->getResponseCode() == 200) {
      // Call worked.
      $result->error = false;
    } else {
      if($this->_http_message->getResponseCode() == 500) {
        // Internal server error
        $result->error = true;
        $result->result = 'An internal server error occurred deleting  object.  Url: '.$url;
      } else {
        // API call failed
        $result->error = true;
        $result->result = $this->_http_message->getHeader("x-error-message");
        
      }
    }
    return $result;
  }
  

  
  // The remainder of this file is mostly lifted from Rackspace's examples: http://api-wiki.apps.rackspace.com/api-wiki/index.php/PHP_Examples_(Rest_API)
  private function get($url_string, $format) {
      $headers = array("Accept: $format");
      $curl_session = self::construct_session($url_string, $headers);
      $this->_http_message = self::send_request($curl_session);
  }

  private function post($url_string, $fields, $format) {
      $headers = array("Accept: $format");
      $curl_session = self::construct_session($url_string, $headers);
      curl_setopt($curl_session, CURLOPT_POST, true);
      curl_setopt($curl_session, CURLOPT_POSTFIELDS, $fields);
      $this->_http_message = self::send_request($curl_session);
  }

  private function put($url_string, $fields) {
      $curl_session = self::construct_session($url_string, array());
      curl_setopt($curl_session, CURLOPT_CUSTOMREQUEST, 'PUT');
      curl_setopt($curl_session, CURLOPT_POSTFIELDS, $fields);
      $this->_http_message = self::send_request($curl_session);
  }
  
  private function delete($url_string) {
      $curl_session = self::construct_session($url_string, array());
      curl_setopt($curl_session, CURLOPT_CUSTOMREQUEST, 'DELETE');
      $this->_http_message = self::send_request($curl_session);
  }

  private function send_request($curl_session) {
      $response = curl_exec($curl_session);
      curl_close($curl_session);
      /* Reponse string may contain two HTTP sessions, if there was an initial
         "HTTP/1.1 100 Continue" response.  So strip that first response out.  Eg:
                  HTTP/1.1 100 Continue
                  Via: 1.1 [proxy]

                  HTTP/1.1 400 Bad Request
                  Via: 1.1 [proxy]
                  Connection: Keep-Alive
                  Proxy-Connection: Keep-Alive      
                  ...     
       * 
       */
      $response = preg_replace('|HTTP/1.1 100.*HTTP/1.1|isU', 'HTTP/1.1', $response);
      return new HttpMessage($response);
  }

  private function construct_session($url_string, $existing_headers) {
      $headers = array_merge(
              self::authorization_headers(), $existing_headers);
      $url = self::construct_uri($url_string);
      $curl_session = curl_init($url);
      curl_setopt($curl_session, CURLOPT_HEADER, true);
      curl_setopt($curl_session, CURLOPT_HTTPHEADER, $headers);
      curl_setopt($curl_session, CURLOPT_RETURNTRANSFER, true);
      return $curl_session;
  }

  private function authorization_headers() {
      $time_stamp = date('YmdHis');
      $data_to_sign = $this->_user_key . $this->_user_agent .
          $time_stamp. $this->_secret_key;
      $signature = base64_encode(sha1($data_to_sign, true));
      $headers = array();
      $headers[] = "User-Agent: " . $this->_user_agent;
      $headers[] = 'X-Api-Signature: ' .
          $this->_user_key . ":$time_stamp:$signature";
      return $headers;
  }

  private function construct_uri($url_string) {
      $url = 'http://' .  $this->_rackspace_host . '/' . $this->_api_version . $url_string;
      return $url;
  }
}

?>

Example

Example usage:

function testRackspace() {
    $this->load->library('Rackspace_API');
    $client = new Rackspace_API();
    $obj = $client->getMailboxInfo('somedomain.com', 'test.user');
    if($obj->error) {
      echo 'Error: '.$obj->result;
    } else {
      var_dump($obj);
    }
  }

Image copyright © Rackspace Ltd. All rights acknowledged.