The Mysterious Case of iTunes U Encoding

Let’s start with a simple scenario. Suppose we have at our disposal a scripting language whose libraries contain a routine called encrypt(). encrypt() takes as its arguments two strings: the first is a string we want to encrypt and the second is a secret key which we’ll use to do the encryption. encrypt() returns an encrypted string as its result; it works like this:

  encryptedString = encrypt("richwolf", "my secret key");

Now that we have our encrypt() routine, what happens when we run the following code?

  encryptedString1 = encrypt("richwolf", "my secret key");
  encryptedString2 = encrypt("RICHWOLF", "my secret key");
  if (encryptedString1 == encryptedString2)
    print("Encryptions are the same");
  else
    print("Encryptions are different"):

Clearly, "Encryptions are different" should be printed. And the reason why the encryptions are different is obvious—richwolf and RICHWOLF are different strings. In computing, there are often contexts in which case is insignificant (filenames in Mac OS X, for example), and in those contexts richwolf and RICHWOLF are interchangeable. But that’s definitely not the case when we encrypt strings. When encrypting, case matters.

So let’s call that our first working principle: when encrypting strings, case matters!

Now let’s consider an instance in which case doesn’t matter. Say we have a string consisting of an e-mail address surrounded by brackets, such as this: [richwolf@uic.edu]. And let’s further say we want to URL-encode that string. To do that, we’ll have to replace the at-sign (@) with its URL-encoded equivalent…%40…and the brackets with their URL-encoded equivalents…%5b and %5d respectively. The result will be: %5brichwolf%40uic.edu%5d. When we complete our percent substitutions, we’ll end up with a legally encoded string. We could use that encoded string within an HTTP request. And since case doesn’t matter whenever we deal with hex digits, we could equally well have URL-encoded our string this way: %5Brichwolf%40uic.edu%5D. That encoding would also work in an HTTP request, producing the very same results.

So our second working principle is this: when URL encoding strings, the case used in percent escapes doesn’t matter.

But now we have a small problem…what if we want to encrypt a string we’ve URL-encoded? In that case, we do need to worry how we do our URL encoding. After all, if we execute this code:

  encryptedString1 = encrypt("%5brichwolf%40uic.edu%5d", "my secret key");
  encryptedString2 = encrypt("%5Brichwolf%40uic.edu%5D", "my secret key");
  if (encryptedString1 == encryptedString2)
    print("Encryptions are the same");
  else
    print("Encryptions are different");

we’ll see "Encryptions are different" just as we did when we encrypted and compared richwolf to RICHWOLF. So even though %5b and %5B are equivalent in an encoded-URL, they won’t be in any string we wish to encrypt.

So our third, and final, working principle is this: when URL encoding strings, the case in percent escapes does not matter except when the URL-encoded string needs to be encrypted.

Now that we have our working principles, think about the way in which they might impact you as you compose your iTunes U CGI. In particular, consider that Apple’s iTunes U Administration Guide says that you must URL-encode a string in the same way that Java does. What it does not say, at least not explicitly, is whether Java uses lowercase or uppercase hex digits whenever it URL-encodes a string. As it turns out, Java uses uppercase in URL-encodings. So whenever you URL-encode a string for iTunes U, you must do the same thing—use uppercase in percent escapes.

These days, most language environments give you a simple way to encrypt and digest strings. In addition, these environments provide you a simple way to URL-encode strings. However, these environments don’t consider that you’re going to do both to the very same string. So unless your language’s URL-encoder behaves exactly as Java’s does (that is, it uses uppercase for URL-encoding), you’ll have to “roll your own”. Unfortunately, the language environment with the most notable lowercase encoding problem is probably the most widely used environment—.NET. And .NET covers a lot of possible configurations…C#, VB.Script, and ASP.NET. Let’s look at how we might tackle the problem of URL-encoding in .NET using C# as an example language environment.

We begin by composing an iTunes U token string. The first part of the token string is our credential:

  // Credential string
  string credentialString = "Administrator@urn:mace:itunesu.com:sites:uic.edu";
  credentialString = "credentials=" + credentialString;

Next, let’s tackle the identity string:

  // Identity string
  string nameString = "Richard Wolf";
  string emailString = "richwolf@uic.edu";
  string usernameString = "richwolf";
  string useridString = "69";
  if (nameString.Length > 0)
    nameString = """ + nameString + """;
  if (emailString.Length > 0)
    emailString = "<" + emailString + ">";
  if (usernameString.Length > 0)
    usernameString = "(" + usernameString + ")";
  if (useridString.Length > 0)
    useridString = "[" + useridString + "]";
  string identityString = nameString + emailString + usernameString + useridString;
  identityString = "identity=" + identityString;

…and finally, the time token:

  // Time string
  DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  DateTime now = DateTime.UtcNow;
  TimeSpan interval = now - epoch;
  int seconds = (int)interval.TotalSeconds;
  string timeString = "time=" + seconds;

So, here’s our token string as it stands right now:

  // Token string (so far)
  string tokenString = credentialString + " & " + identityString + " & " + timeString;

At this stage, we need to URL-encode our token string. Normally, we’d use Microsoft’s URL-encoder (contained in the HttpServerUtility assembly):

  /tokenString = HttpServerUtility.UrlEncode(tokenString);

but that will not work because Microsoft’s UrlEncode() method does not uppercase its percent escapes, as we noted earlier, so it’ll be necessary to roll our own. To find out what we need to do, we can check out the Wikipedia.

Here is our URL-encoder:

  tokenString = tokenString.Replace(" ", "+");
  tokenString = tokenString.Replace(""", "%23");
  tokenString = tokenString.Replace("(", "%28");
  tokenString = tokenString.Replace(")", "%29");
  tokenString = tokenString.Replace(":", "%3A");
  tokenString = tokenString.Replace("<", "%3C");
  tokenString = tokenString.Replace(">", "%3E");
  tokenString = tokenString.Replace("@", "%40");
  tokenString = tokenString.Replace("[", "%5B");
  tokenString = tokenString.Replace("]", "%5D");

Some things to note about our version…

The first is that we convert all spaces in our token string to plusses (+). Although no longer strictly necessary, it’s what the Java URL-encoder does, so we must be sure to match Java’s behavior. Next, notice that we need to escape the double-quote character, preceding it with a backslash (") so that it is properly interpreted by C#. Finally, there are a number of characters that have reserved uses, or that may not appear, in a URI query or MACE URN, so we do not need to consider them when URL-encoding our token string.

Once URL-encoded, we are now free to encrypt and digest our token string to get our signature.

  // Compute signature string
  string sharedSecret = "SHAREDSECRETWITH32CHARACTERSSINIT"
  byte[] encodedKey = System.Text.Encoding.UTF8.GetBytes(sharedSecret);
  System.Security.Cryptography.HMACSHA256 hmac = new
  System.Security.Cryptography.HMACSHA256(encodedKey);
  byte[] digest = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tokenString));
  string digestString = ToHexString(digest);
  digestString = digestString.ToLower(System.Globalization.CultureInfo.InvariantCulture);
  string signatureString = “signature=" + digestString;

We start by re-encoding our shared secret as a UTF-8 string (recall that in .NET strings are encoded using UTF-16…but we’ll need change that to UTF-8 when dealing with iTunes U). Next, we instantiate a System.Security.Cryptography.HMACSHA256 object using our shared secret as key. Then we encrypt and digest our token string—the result is a byte array (notice that we were careful to re-encode our token string to UTF-8 before encrypting it). We then convert our encrypted byte array back into a string and, although not strictly necessary, we convert all the hex digits in it to lowercase characters. The result is our signature string.

For the curious, ToHexString() is our own utility method used to convert a byte array into a string representation of the array:

  public static string ToHexString(byte[] bytes)
  {
    string hexString = "";
    for (int i = 0; i < bytes.Length; i++)
    {
      int num = bytes[i];
      hexString += String.Format("{0:X2}", num);
    }
    return hexString;
  }

Now that we have all our tokens, including our signature, we can compose our final token string:

  // Token string (final)
  tokenString = tokenString + " & " + signatureString;

And with our token string properly URL-encoded and containing a correctly encrypted signature, we can combine it safely with an iTunes U request:

  string iTunesURLString = "https://deimos.apple.com/WebObjects/Core.woa/Browse/uic.edu" + "?" + tokenString;

and send it to Apple.

Share |
Views: 500 views    Report Inappropriate Content
Articles by Category
All Articles
AcademiX  (27 articles)
Announcements  (1 article)
Press Releases  (1 article)
Reviews  (3 articles)
Solutions  (2 articles)
This Article's Tags
Related Articles