909-546-4700

A Content Protection System

Introduction

The cartridge is a small device containing a flash memory chip, a small processor and some serial interface circuitry. This memory chip will eventually hold the content as clear text.

The holder is a small device containing a standard port (serial or USB) and a slot that accepts a cartridge. This device has no memory or processor. The purpose of the holder is to convert the cartridge edge connector to a standard cable interface.

The client software is a Win32 program that runs on any Win9x/NT system. It communicates with the cartridge via the serial cable using the protocol. It communicates with the web server by generating URLs and launching the default browser. The client software also registers a custom mime type so that it will be launched by the browser to process the file downloads from the web server.

The web server is a standard Apache server built with PHP3 and a slightly customized version of the mcrypt library (replacing the standard TEAN with our custom simpler cipher). It communicates with the client software by generating file downloads with the custom mime type. This causes the browser to launch the client software to process the downloaded file.


Encryption algorithms

We use 3DES and MD5 from the mcrypt library, and a simple weak cipher which is:

word8 rotate_left(word8 x);
word8 rotate_left(word8 x)
{
    return ((x << 1) & 0xff) | (x >> 7);    // rotate left by one bit
}


void _mcrypt_cl_enc_block(word8 *k, word8 *v)
{
    int i, j, r;
    for (r=0; r<4; r++) {
        for (i=0; i<7; i++) {
            for (j=0; j<4; j++) {
                // feistel network v[i], v[i+1] form the 16 bit block
                // L = v[i]
                // R = v[i+1]
                word8 t = v[i+1];
                v[i+1]  = v[i] ^ ((rotate_left(t) + k[r*4+j]) & 0xff);
                v[i]    = t;
            }
        }
    }
}

void _mcrypt_cl_dec_block(word8 *k, word8 *v)
{
    int i, j, r;
    for (r=3; r>-1; r--) {
        for (i=6; i>-1; i--) {
            for (j=3; j>-1; j--) {
                // feistel network v[i], v[i+1] form the 16 bit block
                // L = v[i]
                // R = v[i+1]
                word8 t = v[i];
                v[i]    = v[i+1] ^ ((rotate_left(t) + k[r*4+j]) & 0xff);
                v[i+1]  = t;
            }
        }
    }
}

Key formats

All the keys mentioned below are 16 byte binary strings.

Whenever these keys are transmitted between the client software and the cartridge, they are expanded to a 24 byte binary format which is composed of (KEY,HASH) where KEY is the actual 16 byte key, and HASH is the 8 byte result of encrypt-weak(substr(KEY,1,8),KEY). That allows the receiver to verify that it has received a valid key.

Whenever these keys are passed between the client software and the web server, they are expanded to a 50 byte ascii format (derived from the 24 byte format above) which is composed of three 16 byte hexadecimal fields separated by minus signs. A sample of such a key is 0904565493629c93-7e955e1f3fde82b3-b958059ef9db3a38

These keys are passed from the client software to the web server in the 50 byte format as part of a URL. They are passed from the web server to the client software in a 59 byte file download as FID-ECK. A sample of such a file is 01234567-0904565493629c93-7e955e1f3fde82b3-b958059ef9db3a38


Keys

Let GLOBAL be the shared global key. This key only resides on the secured workstations that are used to encrypt content for storage on the web server, on the secured workstation that is used to generate the keys to be placed in the ROM on the cartridges, and on the web server to decrypt the cartridge ESN values. If an attacker can determine this GLOBAL key, all content protection is lost.

All the content stored on the web server is encrypted-3des under that GLOBAL key.

Let CSN be the clear text serial number of the cartridge. This is a 16 byte binary serial number that will never be exposed in any transmission.

Let ESN be encrypt-3des(CSN,GLOBAL) which is the CSN encrypted under the global key. The ESN will be used by the web server to identify the cartridge.

Let CCK be the clear text content key. This will initially be zero, so the cartridge can determine whether it has ever received a content key from the client software. Note that the cartridge MUST refuse to accept any content data file until it has been initialized with a valid content key.

Let ECK be encrypt-weak(CCK,CSN) which is the CCK encrypted under the CSN key.


Key generation

There is only one GLOBAL key and it was generated by md5(random string).

The key generation program is used to generate serial number (CSN, ESN) pairs to be loaded into the cartridge flash memory at the factory. It uses MD5 and 3DES from the mcrypt library, and the weak cipher above. An initial input is repeatedly hashed with MD5 to generate the sequence of keys.

A family may own more than one cartridge, and their downloaded content must be usable on all of their cartridges. As part of the registration process, a family content key (CCK) will be generated by the web server. This content key will be stored on the web server (with the list of ESN values that represent the family's cartridges) and used to encrypt all the content for that family. The CCK is generated by

<?php
  //get a chunk of random stuff
  $iv = mcrypt_create_iv(256, MCRYPT_DEV_RAND);
  $cck = hex2bin(md5($iv));
?>

The web server will also generate a unique family identifier (FID) which could be a simple serial number or the output of some hash. This FID will be used to allow the client software to ensure that any particular content file is actually usable by the current cartridge. The FID is 8 ascii hex digits.


Procedures overview

To register a cartridge, the cartridge supplies ESN to the client software, which passes it on in the form of a URL to the web server. The web server can then use that ESN to determine if it has been registered and if so to what family it belongs.

The web server, knowing the global key, decrypts ESN to derive CSN. As part of the registration process, the web server produces a random CCK (or retrieves an existing CCK from the user's data) and encrypts it to produce ECK. The web server also generates a random FID (or retrieves an existing FID from the user's data). The web server then sends FID-ECK back to the cartridge via the client software. The cartridge then decrypts ECK to derive CCK, and permanently remembers that CCK. All the content destined for that cartridge (or any of its siblings) will be encrypted with that CCK.

Note that this initializes the cartridge with the family unlock key. The client software does not need to keep this content key file, since it is only valid for one cartridge, and once the cartridge has accepted the content key it has no further use.

To download content from the web server for a cartridge, the web server decrypts the content using the global key, and immediately encrypts it under the CCK associated with the current user/family. It then prepends the FID and appends an md5 checksum.

The client software must be able to tell the difference between an initial FID-ECK file, and a subsequent content file that is encrypted under the CCK. Any 59 byte file where bytes 9, 26 and 43 contain '-' will be assumed to be a FID-ECK file. All other files will be assumed to be encrypted content files. This won't be a problem since the content files are all much larger than 59 bytes.


Procedures

Scenario 1: User/family wants to buy their first cartridge on the web. They select "Purchase a cartridge". A cartridge is added to their shopping cart. They fill out the registration page with user id, password, name, address, optional email address, etc. They go to checkout and confirm the purchase. If the credit card passes they see a receipt page and the server sends an email notification to them to confirm the order. The server also generates a FID and CCK if this is a new family and associates that FID,CCK pair with this family. Once they receive their cartridge, they proceed to Scenario 2.

Scenario 2: User/family purchased their first cartridge in a store or over the web, and they want some content. Instructions in the box get them to the web server where they click "Download Content". If they have already registered once before, they just login again. Otherwise, they need to fill out the registration page, and the web server will generate their FID and CCK. They shop from the list of content. The selected items go into their shopping cart. They go to checkout and download. The web server now associates those content items with their account and they can download the content.

At this point, they must have the client software installed on the computer they are using to run the browser. Actually, the family may have already downloaded the client software, but from some other computer. This computer may not have the client software installed, and the web server has no way to know that. If that is the case, they can download their content, but their browser won't know what to do with it, since the mime type won't be registered. All we can do is put a very prominent link on the download page indicating they must install the client software first.

The web site now generates a list of the titles they have purchased or have available for download. Each one of those links may be clicked on to download that title. Those URLs cause the web server to send a file with a specific mime-type to the client software (via the browser) as a file download. Note that the content is encrypted under the CCK before it is sent to the client. The user can now use the client software to send that content to the cartridge.

When the browser receives the downloaded encrypted content file, it will spawn the client software. The client software will recognize that the cartridge has not been initialized, and will launch the browser with a URL containing the cartridge ESN. This is the first and only time that the web server gets to see the cartridge ESN. If the brower supports cookies, the web server also knows the user/password and shopping state. If not, the user will be forced to login to the web site again.

If this is a new cartridge ESN (never seen by the web server), the Web server encrypts the family CCK to form the ECK, and sends it to the client software (via the browser) as a FID-ECK file download. The client software then initializes the cartridge with that ECK, so the cartridge is ready to accept content. The client software also stores the FID in the cartridge.

If this is an old ESN previously seen by the web server, but it belongs to a family other than the current user/family userid/password, then they are trying to move a cartridge from one family to another. If the web server allows this it changes the database to remove this cartridge from the old family and adds it to the new family. It also generates an ECK from the new family's CCK and sends it to the client software as above.

Scenario 3: User/family with a cartridge, wants to download more content. They go to the web site and log in. If they want new content they go through the purchase process. They can also download content they've already purchased. The download process works the same as in Scenario 2.

Scenario 4: User/family with one or more cartridges, wants to buy a additional cartridge. They can buy them via a store or via the web as in Scenario 1. After buying and receiving a new cartridge, they launch the client software, which detects that this cartridge has not been initialized, so it launches the web browser with a URL that contains the new cartridge ESN. They then login, and the web site adds this ESN to the list of cartridges for this family. The web site retrieves the family FID and CCK, makes a FID-ECK file, and sends it to the client software (via the web browser) as a file download. The client software sends the ECK to the cartridge which decrypts it to derive the CCK, and the cartridge is now ready to accept any of the family content, either old content they have already downloaded, or newly purchased content. The client software also stores the FID in the cartridge.

Scenario 5: User/family gets a new PC or re-installs their OS. We assume they lose everything including the mime type mappings, the client software, and all of their downloaded content files. However, they can simply start over and redownload the client software. As long as they remember their userid/password for the web site, they can put any of their cartridges in the holder, and press the "download content" button. That will take them to the web site, with the cartridge ESN as part of the URL. After going thru the web site login process (using their remembered userid and password) the web site will show them a list of all their content. At that point, they can redownload all their content, since the web site will have their family FID and CCK, and all of their cartridges are already initialized to accept content encrypted under that CCK.


Client Software Download process

The client software installation does not deal with keys or any registration process. It is simply a single Win32 application that needs to be placed in some directory under  "\program files". Once the client software is installed, it will be launched by the setup program, and it will then register the mime file types.


Threats

Any attacker that can physically open the cartridge and attach test probes directly to the flash memory chip can read the clear text content. We are attempting to ensure that they cannot load this clear text content into other cartridges unless they also open those other cartridges and directly program their flash memory chips. We are also attempting to ensure that such an attacker cannot derive the GLOBAL key.

It is easy to intercept the communications between the client software and the cartridge, since this is a standard serial or USB cable. It is also easy to use a debugger to trace the execution of the client software. We are attempting to ensure that none of (CSN, CCK, GLOBAL) are exposed to such attacks.

Any attacker that can break the encryption algorithm can derive the GLOBAL key, in which case all is lost. We are using 3DES from the mcrypt library to protect the GLOBAL key, and the custom weak cipher above to protect the content since the cartridge does not have much memory.


Communications protocol between the client software and the cartridge

 


Key Generation Program

extern "C"
{
#include "libdefs.h"
#include "md5.h"
#include "tean.h"
#include "des.h"
#include "xmemory.h"
}
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>

#define KEY_COUNT  15


void des3(word8 *key, word8 *data);
void des3(word8 *key, word8 *data)
{
    char akey[16][8];
    char bkey[16][8];
    _mcrypt_desinit(0);
    _mcrypt_des_setkey(akey, (char*)key);
    _mcrypt_des_setkey(bkey, (char*)key+8);
    _mcrypt_endes(akey, (char*)data);
    _mcrypt_dedes(bkey, (char*)data);
    _mcrypt_endes(akey, (char*)data);
    _mcrypt_desdone();
}

void weak(word8 *key, word8 *data);
void weak(word8 *key, word8 *data)
{
    /* custom cipher replacing tean in the library */
    _mcrypt_cl_enc_block(key, data);
}


void heading(unsigned char *globalkey);
void heading(unsigned char *globalkey)
{
    int i;

    printf("---------- global key -----------\n");
    for (i=0; i<8; i++) {
        printf("%02x", globalkey[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", globalkey[i+8]);
    }
    printf("\n\n");

    printf("---------------------- CSN ----------------------- ---------------------- ESN ----------------------- ---------------------- CCK ----------------------- ---------------------- ECK -----------------------\n");                                                                        //  00/04/15

}

void gen1(char *local_pass,
          char *entropy, int entropy_length,
          unsigned char *globalkey,
          unsigned char *csn,
          unsigned char *esn,
          unsigned char *cck,
          unsigned char *eck);
void gen1(char *local_pass,
          char *entropy, int entropy_length,
          unsigned char *globalkey,
          unsigned char *csn,
          unsigned char *esn,
          unsigned char *cck,
          unsigned char *eck)
{
    int i;
    unsigned char *result;
    unsigned char keybuf[24];
    #define key  (keybuf+0)
    #define key2 (keybuf+8)
    #define data (keybuf+16)

    // hash the previous entropy and local passphrase to make a CSN
    MD5_CTX context;
    MD5Init(&context);
    MD5Update(&context, (unsigned char *)entropy, entropy_length);
    MD5Update(&context, (unsigned char *)local_pass, strlen(local_pass));
    result = (unsigned char *)MD5Final(&context);
    memmove(key, result, 16);
    mxfree(result,16);

    // encrypt part of the key with the key to compute a verification hash
    memmove(data, key, 8);
    weak(key, data);

    // print the new CSN
    memmove(csn, key, 24);  // save the csn
    for (i=0; i<8; i++) {
        printf("%02x", key[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", key2[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", data[i]);
    }
    printf(" ");

    // encrypt the CSN under the global key to make ESN
    des3(globalkey, key);
    des3(globalkey, key2);

    // encrypt part of the key with the key to compute a verification hash
    memmove(data, key, 8);
    weak(key, data);

    // print the new ESN
    memmove(esn, key, 24);  // save the esn
    for (i=0; i<8; i++) {
        printf("%02x", key[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", key2[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", data[i]);
    }
    printf(" ");

    // hash the ESN and local passphrase to make a sample CCK
    MD5Init(&context);
    MD5Update(&context, (unsigned char *)key, 24);
    MD5Update(&context, (unsigned char *)local_pass, strlen(local_pass));
    result = (unsigned char *)MD5Final(&context);
    memmove(key, result, 16);
    mxfree(result,16);

    // encrypt part of the key with the key to compute a verification hash
    memmove(data, key, 8);
    weak(key, data);

    // print the new CCK
    memmove(cck, key, 24);  // save the cck
    for (i=0; i<8; i++) {
        printf("%02x", key[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", key2[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", data[i]);
    }
    printf(" ");

    // encrypt the CCK under the CSN to make ECK
    weak(csn, key);
    weak(csn, key2);

    // encrypt part of the key with the key to compute a verification hash
    memmove(data, key, 8);
    weak(key, data);

    // print the new ECK
    memmove(eck, key, 24);  // save the eck
    for (i=0; i<8; i++) {
        printf("%02x", key[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", key2[i]);
    }
    printf("-");
    for (i=0; i<8; i++) {
        printf("%02x", data[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    if (argc < 3) {
        printf("usage: makekey global-pass-phrase local-pass-phrase\n");
        printf("         this makes a list of 15K CSN,ESN,CCK,ECK values for the cartridges\n");
        printf("         and writes it to stdout.\n");
        printf("\n");
        printf("usage: makekey global-pass-phrase input-file output-file\n");
        printf("         this encrypts the entire input file with the hashed global\n");
        printf("         passphrase and writes it to the output file. This is used\n");
        printf("         to prepare content files for storage on the web server.\n");
        printf("\n");
        printf("usage: makekey global-pass-phrase local-pass-phrase input-file output-file output-fid-eck\n");
        printf("         this generates a single CSN,ESN,CCK,ECK tuple from the pass\n");
        printf("         phrases and writes it to stdout. It generates a random FID and\n");
        printf("         writes the fid-eck file. It then encrypts parts of the input\n");
        printf("         the input file under the CCK and produces a fid-content-md5 file\n");
        printf("         which may be used as a test file for the client software.\n");
        printf("\n");
        printf("usage: makekey global-pass-phrase local-pass-phrase input-file output-file output-fid-eck nocrypt\n");
        printf("         same as the above, but the existance of the 6th parameter suppresses\n");
        printf("         the partial encryption of the datafile.\n");
        printf("\n");
        exit(0);
    }
    char *keyptr = argv[1];
    int   keylen = strlen(keyptr);
    unsigned char globalkey[16];
    unsigned char csn[24];
    unsigned char esn[24];
    unsigned char cck[24];
    unsigned char eck[24];
    unsigned char fid[8];

    // hash the global pass phrase into a global key
    MD5_CTX context;
    MD5Init(&context);
    MD5Update(&context, (unsigned char *)keyptr, keylen);
    unsigned char *result = (unsigned char *)MD5Final(&context);
    memmove(globalkey, result, 16);
    mxfree(result,16);

    if (argc == 3) {
        heading(globalkey); // setup the heading
        keyptr = argv[2];
        keylen = strlen(keyptr);
        for (int n=0; n<KEY_COUNT; n++) {
            gen1(argv[2], keyptr, keylen, globalkey, csn, esn, cck, eck);
            keyptr = (char *)eck;
            keylen = 24;
        }
    }

    if (argc == 4) {
        // encrypt the entire file 3des under the global key
        // get the file open
        int fd = _open(argv[2], _O_RDONLY | _O_BINARY);
        if (fd != -1) {
            int fo = _open(argv[3], _O_CREAT | _O_TRUNC | _O_WRONLY | _O_BINARY, _S_IWRITE);
            if (fo != -1) {
                while (1) {
                    unsigned char databuf[8];
                    int c = _read(fd, databuf, 8);
                    if (c==0) break;
                    if (c < 8) memset(databuf+c, '\0', 8-c);
                    des3(globalkey, databuf);
                    _write(fo, databuf, 8);
                }
                _close(fo);
            }
            _close(fd);
        }
    }

    if ((argc == 6) || (argc == 7)) {
        // encrypt parts of the file under the cck (if argc==6)
        heading(globalkey); // setup the heading
        keyptr = argv[2];
        keylen = strlen(keyptr);
        gen1(argv[2], keyptr, keylen, globalkey, csn, esn, cck, eck);

        // make a random fid
        MD5Init(&context);
        MD5Update(&context, (unsigned char *)globalkey, 16);
        MD5Update(&context, (unsigned char *)csn,       24);
        MD5Update(&context, (unsigned char *)esn,       24);
        MD5Update(&context, (unsigned char *)cck,       24);
        MD5Update(&context, (unsigned char *)eck,       24);
        result = (unsigned char *)MD5Final(&context);
        memmove(fid, result, 8);

        // make the fid-eck file
        int fo = _open(argv[5], _O_CREAT | _O_TRUNC | _O_WRONLY | _O_BINARY, _S_IWRITE);
        if (fo != -1) {
            char hex[3];
            int i;
            for (i=0; i<4; i++) {
                sprintf(hex, "%02x", fid[i]);
                _write(fo, hex, 2);
            }
            _write(fo, "-", 1);
            for (i=0; i<8; i++) {
                sprintf(hex, "%02x", eck[i]);
                _write(fo, hex, 2);
            }
            _write(fo, "-", 1);
            for (i=0; i<8; i++) {
                sprintf(hex, "%02x", eck[i+8]);
                _write(fo, hex, 2);
            }
            _write(fo, "-", 1);
            for (i=0; i<8; i++) {
                sprintf(hex, "%02x", eck[i+16]);
                _write(fo, hex, 2);
            }
            _close(fo);
        }

        // get the file open
        int fd = _open(argv[3], _O_RDONLY | _O_BINARY);
        if (fd != -1) {
            int fo = _open(argv[4], _O_CREAT | _O_TRUNC | _O_WRONLY | _O_BINARY, _S_IWRITE);
            if (fo != -1) {
                MD5Init(&context);
                // prepend the initial fid- heading
                char hex[3];
                int i;
                for (i=0; i<4; i++) {
                    sprintf(hex, "%02x", fid[i]);
                    MD5Update(&context, (unsigned char *)hex, 2);
                    _write(fo, hex, 2);
                }
                MD5Update(&context, (unsigned char *)"-", 1);
                _write(fo, "-", 1);
                // write the partially encrypted content
                short page         = 0;
                short hipage       = 0;
                const int pagesize = 256;
                while (1) {
                    unsigned char databuf[pagesize];
                    int c = _read(fd, databuf, pagesize);
                    if (c==0) break;
                    if (c < pagesize)
                        memset(databuf+c, '\0', pagesize-c);
                    if (page == 0) memmove((char*)&hipage, databuf+8, 2);   // pickup the last directory page number
                    if ((page > 2) && (page <= hipage) && (argc==6)) {
                        for (int i=0; i<pagesize; i+=8) weak(cck, databuf+i);
                    }
                    if ((page > hipage) && (argc==6)) {
                        for (int i=0; i<pagesize; i+=128) weak(cck, databuf+i);
                    }
                    MD5Update(&context, (unsigned char *)databuf, pagesize);
                    _write(fo, databuf, pagesize);
                    page++;
                }
                result = (unsigned char *)MD5Final(&context);
                _write(fo, result, 16); // append the final md5 hash
                _close(fo);
            }
            _close(fd);
        }
        if (argc==6) printf("%s has been partially encrypted to %s using the cck above.\n", argv[3], argv[4]);
        if (argc==7) printf("%s has been copied to %s.\n", argv[3], argv[4]);
        printf("%s contains <fid>-<content><md5> where <fid> is 8 hex digits and <md5> is 16 binary bytes\n", argv[4]);
    }

    return 0;
}

Sample download script

<?php

    function hex2bin($data) {
        $len = strlen($data);
        return pack("H" . $len, $data);
    }

    $n         = strlen($key);                                  // ESN in 50byte format
    $ok        = (($n == 50) or die("Invalid key - wrong length"));
    $esn       = substr($key,0,16) . substr($key,17,16) . substr($key,34,16);
    $esn       = hex2bin($esn);                                 // ESN in 24byte format
    $globalkey = hex2bin(md5("passphrase-for-global-key"));     //GLOBAL in 16byte format
    $globalkey = $globalkey . substr($globalkey,0,8);           //GLOBAL in 24byte 3des format

    $test      = substr($esn,0,8);                              // source for HASH
    $data      = substr($esn,16,8);                             // HASH
    $esn       = substr($esn,0,16);                             // ESN in 16byte format
    $data      = mcrypt_ecb(MCRYPT_XTEA, $esn, $data, MCRYPT_DECRYPT);      // decrypt the hash
    $ok        = (($data == $test) or die("Invalid key - bad checksum"));   // verify the hash

    $csn       = mcrypt_ecb(MCRYPT_3DES, $globalkey, $esn, MCRYPT_DECRYPT); // CSN in 16byte format
    $cck       = hex2bin(md5($csn));    // CCK in 16byte format, should be derived from the ESN (or CSN) via a database lookup
    $fid       = substr(md5($csn . "salt"),0,8) . "-";    // FID should be derived from the ESN (or CSN) via a database lookup

    $fd   = @fopen($fn, "r") or die("File $fn does not exist.");
    $fs   = filesize($fn);
    $text = fread($fd, $fs);
    fclose($fd);
    $text = mcrypt_ecb(MCRYPT_3DES, $globalkey, $text, MCRYPT_DECRYPT);
    $text = $fid . mcrypt_ecb(MCRYPT_XTEA, $cck, $text, MCRYPT_ENCRYPT);
    $hash = hex2bin(md5($text));        // checksum to verify proper download

    header("Content-type: application/x-neurosmith-cybercartridge");
    header("Content-Disposition: attachment; filename=$fn.cyber");
    header("Content-Description: PHP3 Generated Data");
    echo $text . $hash;
?>

Sample registration script

<?php

    function hex2bin($data) {
        $len = strlen($data);
        return pack("H" . $len, $data);
    }

    $n         = strlen($key);                                  // ESN in 50byte format
    $ok        = (($n == 50) or die("Invalid key - wrong length"));
    $esn       = substr($key,0,16) . substr($key,17,16) . substr($key,34,16);
    $esn       = hex2bin($esn);                                 // ESN in 24byte format
    $globalkey = hex2bin(md5("passphrase-for-global-key"));     //GLOBAL in 16byte format
    $globalkey = $globalkey . substr($globalkey,0,8);           //GLOBAL in 24byte 3des format

    $test      = substr($esn,0,8);                              // source for HASH
    $data      = substr($esn,16,8);                             // HASH
    $esn       = substr($esn,0,16);                             // ESN in 16byte format
    $data      = mcrypt_ecb(MCRYPT_XTEA, $esn, $data, MCRYPT_DECRYPT);      // decrypt the hash
    $ok        = (($data == $test) or die("Invalid key - bad checksum"));   // verify the hash

    $csn       = mcrypt_ecb(MCRYPT_3DES, $globalkey, $esn, MCRYPT_DECRYPT); // CSN in 16byte format
    $cck       = hex2bin(md5($csn));    // CCK in 16byte format, should be derived from the ESN (or CSN) via a database lookup
    $fid       = substr(md5($csn . "salt"),0,8) . "-";    // FID should be derived from the ESN (or CSN) via a database lookup

    $eck       = mcrypt_ecb(MCRYPT_XTEA, $csn, $cck, MCRYPT_ENCRYPT);       // ECK in 16 byte format
    $test      = substr($eck,0,8);                                          // source for HASH
    $hash      = mcrypt_ecb(MCRYPT_XTEA, $eck, $test, MCRYPT_ENCRYPT);      // compute the hash
    $eck       = bin2hex($eck . $hash);                                     // 48 byte format
    $eck       = substr($eck,0,16) . "-" . substr($eck,16,16) . "-" . substr($eck,32,16);

    header("Content-type: application/x-neurosmith-cybercartridge");
    header("Content-Disposition: attachment; filename=fid-eck.cyber");
    header("Content-Description: PHP3 Generated Data");
    echo $fid . $eck;
?>