Stone PHP SafeCrypt: Convenient, Secure and Typesafe Encryption (Tutorial, Library and Test Code)

7:47 pm PHP, Programming, Tools and Libraries, Tutorials

When wandering around Das Intarweb one sees a lot of sad, sad code. In fact, people who should know better get busted on weak crypto all the time. Indeed, even the PHP manual examples have unacceptable security flaws. When one is writing encryption code for one’s own site, that turns into a problem. Here’s a library to wrap and a little primer on using the standard encryption facilities in PHP safely and correctly.

See, there are a lot of subtle attacks one can bring to bear on even sound cryptological systems, if one doesn’t know what one is doing. PHP does a pretty neat job of wrapping up these issues behind a semi-generic library interface, but it doesn’t do such a great job in the manual of detailing safe usage. In particular, some of the user notes give conflicting, and frequently outright bad, advice; furthermore, the displayed methods for using the interface aren’t themselves particularly generic, and skip some particularly useful things they could do. It also doesn’t help that there’s a distinct trend of more-than-one-way-itis in libmcrypt, including a lot of support for deprecated functionality.

So, what to do about it, but write a new tute? Actually, this one’s pretty low-impact; might as well just release it almost as straight source. Still, it’s helpful to explain.

The Quick Version

It’s surprisingly simple.

If you just want to throw the library in and start working, follow this download link, then throw the file in with the rest of your PHP project. Change the define DEFAULT_MD5_SALT on line 36 of StonePhpSafeCrypt_config.php (the library will force you to do this) and fill it with some random string. Really doesn’t matter, and you’ll never need to remember it; it’s just a “salt,” which is used to prevent dictionary attacks. Don’t use a phrase someone can guess, and consider just mashing your hands all over the keyboard for a second instead of trying to make up something smart.

Next, require_once() the library, then use PackCrypt($data, $key [, $options] ) and UnpackCrypt($data, $key [, $options] ) on any serializable PHP datatype. $key does not need to be secure, except against social engineering attacks like guessing according to the user’s behavior; it is hardened by the library, and salted by you.

As options, you may pass in an array containing up to four things as key/value pairs:

  • cipher‘, which may take any value from the mcrypt cipher module list, and which defaults to twofish
  • mode‘, which may take any cipher block type, and which defaults to CBC
  • compressor‘, which may take any compressor listed in StonePhpSafeCrypt_compressors.php, and which defaults to off (the package comes with gzip, gzip deflate and bzip2 defined, but you may add others)
  • salt‘, which allows you to override the default salt with a salt of your own choice.

All four of these options are reflexive: if you use them during encryption, you must use them identically during decryption, or the process will not allow you to unpack the data. All functions other than PackCrypt() and UnpackCrypt() in the library are for internal support, and should not be used directly.

Here’s a minimalist example, to show you what you’re doing:

require_once('StonePhpSafeCrypt.php');

$sourcedata = array('Hello', 1, 2, 3=>4, 'Goodbye');
$packed = PackCrypt($sourcedata, 'Password');

if ($packed['state'] === false) { die('Error: ' . $packed['reason']); }

echo 'Packed: ', $packed['output'], '<br />';

$unpacked = UnpackCrypt($packed, 'Password');
echo 'Unpacked: ', print_r($unpacked);

And that’s all there is to it. :D

The Fundamentals

The first thing we need is a way to wrap the encryption and decryption process, without actually forcing a choice of which method is used. This is important because some machines may not have some algorithms available, because algorithms get broken and replaced, and so on. The user really needs to be able to select the algorithm. Even so, the actual process of encryption is fairly ridiculously cumbersome, and can be wrapped.

That said, there’s a lot to PHP. One thing that’s really nice about PHP is its automatic data munging. Of particular utility is the concept of serialization; objects even have a magic stub they can provide to implement serialization magically. It can be argued that a high quality wrapper for encryption should magically handle the packing and unpacking of a PHP data type, be it a scalar, an array or even a correctly made object. Serialize is the appropriate means by which to do so, and so we’ll build that into our encryptor.

Another important thing for an encryption wrapper is that the user shouldn’t actually need to know a whole lot to use it safely. The actual data to be packed and the password by which to do so are sort of common-sense minimums; ideally, a function should be flexible enough to allow more customization, but should require nothing more than these and still be safe. Importantly, this function must not require the user to know how to make safe passwords. We will do that for them. In order to keep that mindset, we will begin referring to two passwords: the user’s password, which we’ll call the Weak Key, and the password we derive therefrom, called the Strong Key. Only the strong key is reliable, or used in any way in the transmitted cipher. (Weak keys are still subject to social engineering attacks, so end users should still be advised of rules they refuse to follow, but this at least protects the data stream itself.)

To that end, we will create two basic functions: PackCrypt() and UnpackCrypt(). These functions will take two mandatory and two optional arguments each: the data, the weak password, and if the user wants to, they may also override the default choice of encryption algorithms (which is something best reserved for those who are familiar with the advantages and disadvantages of the various algorithms.)

The workhorses: PackCrypt() and UnpackCrypt()

PackCrypt and UnpackCrypt are the two functions you actually use in this library.

PackCrypt(&$Data, &$WeakKey, $options)

PackCrypt takes any serializable data structure for $Data, any string for $WeakKey, and if you like, some extra commands in $options. $Data can be a nested array full of objects, all sorts of creepy stuff, whatever. If it’s a correct and complete PHP class which follows the rules for serialize(), then it should be safe for PackCrypt().

It’s really just that simple. Make some horribly complex data structure, and do

$packed = PackCrypt($someData, 'my key sucks');

and then look at $packed['output']. Bang: safe, clean serialized secure encryption.

Why $packed['output']? Why not just $packed?

This library (like most of John’s libraries) returns a state tuple for its operations. This is a behavior that’s common in some languages, particularly Erlang/OTP, and which PHP can support quite easily. It’s also a very important and powerful technique: it makes concurrent message tracking very easy, it makes keeping track of parallel error states very easy, and it makes complex returns with ancillary information not only possible but quite natural.

PackCrypt returns an array with ['status'], ['reason'] and ['output'] set. Status is a boolean which represents whether things proceeded correctly. Reason is a textual description which explains what error set status to false when something broke; if status is true, which is to say if things went well, it just says “Successfully encrypted.” or whatever is appropriate. Output contains the actual results, or false if something failed.

  • If everything goes correctly,
    • ['status'] will be true,
    • ['output'] will contain your encrypted result, and
    • ['reason'] will say ‘Successful Pack’.
  • If there is a problem,
    • ['status'] will be false,
    • ['output'] will be false, and
    • ['reason'] will contain a message explaining the problem, which will most likely be that the encryption algorithm chosen or the compressor chosen is unavailable on this system

Okay, so how does it work

It’s fairly straightforward. First, the functions unpack and apply the options, check for the existance of the requested compressor and so on. Once that’s done, the compressor opens the cipher module (such as Blowfish, RSA, Rijndael, TripleDES and so on,) engages the appropriate block cipher mode (cbc, cfb, ecb or ofb,) creates a salted MD5 hash of the weak key, generates a randomized initialization vector, ciphers the IV with a resalted strong key, serializes the incoming data, compresses the serialized data if requested, encrypts the serialized data, writes out the result array and cleans up. Unsurprisingly, the unpacking scheme is basically the same thing in reverse.
The thing is, you have to know all the steps in order, and you have to do them correctly, accomodating for the different sizes in each algorithm and platform implementation, trim extra space and so on. So, wrap ‘em up, no problem, done. Welcome to PackCrypt() territory.

Safe IV Transfer: BlockScramble() and BlockDescramble()

The PHP manual incorrectly states that the initialization vector for an encrypted datastream may be transmitted plaintext. This would be highly convenient and provides a desirable mechanism for transfer, but can be the source of man-in-the-middle attacks in CBC block mode algorithms, as well as several other situations. See Ciphers by Ritter for a detailed explanation of the problem. This library solves the problem by salted hashing the weak key and then reversibly merging that with the IV, so that the leading block can be transmitted safely as a stream prefix.

The functions which perform this salting, hashing and merging are called BlockScramble() and BlockDescramble().

Convenient compression: $options['compressor']

Of course, one thing that’s really helpful to have in this kind of routine is inline compression. Compression both makes the original stream harder to detect and (obviously) reduces bandwidth and storage costs. However, the way to apply compression to the encryption stream is inobvious; most people would just try compressing the results of the encryption, and that’s not going to work. The reason is that compression and encryption are at ends: compression relies on patterns in data, whereas the purpose of encryption is to hide patterns in data. A well encrypted string when compressed is generally larger than uncompressed.

As such, to keep things simple, I put compression directly into the mechanism, in a way that can be extended to new compression algorithms later (though I have a hard time imagining needing to replace BZip2.) However, if you want to add support for the latest greatest algorithm, even if it’s newer than my library, well, no problem. Open StonePhpSafeCrypt_compressors.php and add your compressor to the list. Instructions are in the file, but it’s really quite trivial.

Subdirectories and require_once()

Because of the way PHP inclusion works, the require directives in a given script are referential to the including script’s location, rather than their own. That means that the includes for the various parts of this library need to be referenced according to their own directory. If you just dump all the files in the same directory as your main project, well great, works fine. If you want to put them in a subdirectory, do the following before you include the library (or it will fail in a fairly unreadable way):

define('STONE_PHP_SAFE_CRYPT_HOST_DIRECTORY', 'dirgoeshere/');

The directory may be absolute or referential, should be in Unix directory format, and needs the trailing slash.

That’s it?

That’s it. Unpack the files, set the salt, $foo = PackCrypt(data, key, options); and look in $foo['output']. Safe, convenient and reliable. This is what it should have been all along.

In case you missed it earlier, get the source here. There is a working example in test.php, but here’s that same fairly minimalist example again, now that you know what it’s doing:

require_once('StonePhpSafeCrypt.php');

$sourcedata = array('Hello', 1, 2, 3=>4, 'Goodbye');
$packed = PackCrypt($sourcedata, 'Password');

if ($packed['state'] === false) { die('Error: ' . $packed['reason']); }

echo 'Packed: ', $packed['output'], '<br />';

$unpacked = UnpackCrypt($packed, 'Password');
echo 'Unpacked: ', print_r($unpacked);

And that’s all there is to it. :D

44 Responses

  1. Patater Says:

    Nice, clean, simple. Well done.

  2. Yanick Rochon Says:

    Well, that sound marvelous… but how do you handle the encrypted string on the client side ? Is it possible to decrypt the return value of the library with Javascript ?

  3. stonecypher Says:

    Yanick: this is for php->php. Javascript does not have a uniform decryption library, nor does it have a library for unpacking the PHP datatype. This is more intended for storage and transmission of data between servers, keeping things securely in databases and so on.

    Thank you for asking. I’ll kick together a safe server->client library too, soon, now that someone’s pointed out that it’s useful.

  4. Vinu Thomas Says:

    Great writeup.
    I’m looking forward to your safe server->client library.

  5. Jan Matthiesen Says:

    Nice library.
    Is it possible to put out an mixed up string by entering the wrong password?

  6. Marcus Hofmann Says:

    Cool. This is about what I’ve been looking for in a while now. Thanks a lot.

  7. Jeff Says:

    Pardon me, I am sure this is a stupid question but never hurts to ask. I have five varibles I am passing to a page to encrypt them. I need to encrypt each and store in a mysql database. I need the result to be a string and not an array. Any help appreciated. I agree the php.net site has not helped me with this. Thanks

  8. stonecypher Says:

    Just PackCrypt() them as explained above.

  9. jeff Says:

    When I use for example:

    $submitted_data = ($_POST['submitted_data']);
    $submitted_key = ($_POST['submitted_key']);

    $crypt_submitted_data = PackCrypt($submitted_data, $key);

    echo “Submitted data: “. $enc_sumitted_data;

    I get:

    Submitted data: Array

    And nothing goes into the database.

    Thanks for responding. I have been working with this for WEEKS!

  10. jeff Says:

    Sorry that should have been

    $submitted_data = ($_POST[’submitted_data’]);
    $key = ($_POST[’submitted_key’]);

    $crypt_submitted_data = PackCrypt($submitted_data, $key);

    echo “Submitted data: “. $enc_sumitted_data;

    I get:

    Submitted data: Array

    And nothing goes into the database.

    Thanks for responding. I have been working with this for WEEKS!

  11. Jeff Says:

    OK, I got this to work using the ['output'] as you described above, but on decryption I get this error:

    Warning: mcrypt_generic_init() [function.mcrypt-generic-init]: Iv size incorrect; supplied length: 0, needed: 16 in /mysite/StonePhpSafeCrypt_packcrypt.php on line 161

    It did however decrypt the data.

  12. stonecypher Says:

    Jeff: please direct further support requests to my email address, not the blog. I will help you, but this really isn’t the appropriate place.

    Also, I can’t figure out what you’ve done incorrectly if I can’t see your decryption code.

  13. Jeff Says:

    I do not see an email address and would really appreciate your help.

  14. Doug Says:

    I have followed your example (test.php) and the output element on unpacking is null/blank.

    However, how do you get the decrypted data? I would have thought it would be in the output element of the array $unpacked.

    i.e.

    $sourcedata = array(“This is the txt to pack”);

    $packed = PackCrypt($sourcedata, “Password”);
    if ($packed["state"] === false) { die(“Error: ” . $packed["reason"]); }

    echo “Packed: “, $packed["output"], “”;

    $unpacked = UnpackCrypt($packed, “Password”);
    echo “Unpacked: “, print_r($unpacked);

    ———————————
    would have thought that: $unpacked["output"] = “This is the txt to pack”, but is blank.

    Hope that makes sense.

  15. Jeff Says:

    Found it. It was a coding error on my part. A field from the database was not being populated so when the decryption took place no data was available to decode. Sorry for wasting your time, please delete my posts.

    Thanks for a GREAT script.

  16. Jeff Says:

    Do you know of any place I can go to get help with php and secure connections, ie ssl or https? I have looked at CURL and OpenSSL but this a hard read for a novice.

  17. stonecypher Says:

    Jeff: use stonecypher at gmail dot com. Please don’t leave messages like this on the blog anymore. This is personal correspondance. I’m happy to help, but this really isn’t the place. Thanks.

  18. stonecypher Says:

    Doug: you need to unpack $packed['output'] , not $packed. Look carefully at like 195 of test.php.

    I’ll make that clearer in the instructions. Thank you for helping me understand where I have been insufficiently detailed.

  19. Ryan D LaBarre Says:

    Great script, many thanks. I’d also like to throw in another voice of desire for a server->client library, specifically an ActionScript (or JavavScript) decryption function so that encrypted data can be passed to, and decrypted securely within, an instance of a flash player.

  20. Jorgen Says:

    Jeff this is really a wonderfull library.

    I had however had to make minor change for it too work.

    First I had to initialize $ki in phpStoneSafeCrypt_blockscramble.php > Both functions to get rid of that nasty notice php kicks out (my dev server has error_reporting E_ALL by default).

    Second I had to use MCRYPT_RANDOM in phpStoneSaveCrypt_packcrypt.php since my dev server is windows based. You might want to change it or atleast write a line of OS detection code.

  21. Tumaini Says:

    Great work, and many thanks!
    If only the world had more of the sharing spirit of people like yourself!

  22. Doug Brown Says:

    I’m getting push back from our security team that we should just use “native Microsoft SQL Server” encryption rather than a library like this. The basis for this recommendation is that SQL Server has apparently been evaluated by various security groups/institutions/whatever. Has any such validation or evaluation of libmcrypt been done? Can it stand up to SQL Server’s encryption in front of the security community?

  23. stonecypher Says:

    Doug: if your security team is recommending MSSQL encryption, you need a new security team.

    That said, the library isn’t the security issue, and essentially never is, with those rare exceptions of faulty implementations. libmcrypt is used in the vast bulk of your internet transactions, is the basis of most of your SSL and SSH sessions, and quite probably protects a bunch of your passwords. The issues are twofold:

    1) You need a correct implementation of a strong algorithm. Both MSSQL and libmcrypt can claim this, provided you’re choosing an appropriate algorithm, and

    2) You need to use said implementation carefully.

    Why do I say that you need a new security team, if there’s nothing wrong with MSSQL’s implementations of algorithms? Well, it’s a bit like if you went to the mechanic and asked for AC Delco parts, and were told to get GM parts instead, because they’re well tested.

    What your security team has just done is shown a disasterous level of naïveté. libmcrypt is much, much more thoroughly tested than MSSQL is; whereas MSSQL is quite well tested by Microsoft, libmcrypt is tested by IBM, Apple, the Apache foundation, the PHP/Zend foundation, every BSD vendor, every Linux vendor, Sun, Netscape/Mozilla foundation, Opera, banks, financial institutions, insurance institutions, various branches of the US Military, the NSA, the CIA, and yes, even Microsoft.

    Why the AC Delco comparison? Because any mechanic who thinks GM parts are better tested than AC Delco parts doesn’t have a clue what they’re talking about. AC Delco makes parts for tanks, construction cranes, oil rigs and space ships. They’re not impressed by General Motors, and libmcrypt is not impressed by MSSQL.

    Either way, this library has nothing to do with the quality of an encryption implementation whatsoever. This library is just a quick wrapper to keep someone from making common mistakes with the PHP interface to the library.

    Not to be rude, but it sounds like neither you nor your security team are in fact familiar enough with this kind of thing to deploy a secure system. I recommend that you consider reading the following two books, in this order, or perhaps to consider outsourcing for insurance purposes (honestly, it’s likely to be cheaper if you’re insured) :

    1) Schneier
    2) SPD

    Best of luck.

  24. Michel Says:

    Great script and nice tuto,

    I agree with what Jorgen said on November 10, 2006 about implementing OS detection or a least adding a constant that specify on wich OS the php server runs. I always try to be the least platform dependent as possible since I must work with what I get.

    Reading, reading and reading more and more about mcrypt on PHP.NET and all the comments was turning me off. I’m not to familiarized with encryption but i do have to work with it and I do not have budget or time for books this time. This save me a lot of work right now but I’m still going to investigate more and learn more about the subject.

    Thanks again.

  25. Michel Says:

    Hummm, I have a winXP box here and calls to

    mcrypt_create_iv($iv_ivsz, MCRYPT_RAND);

    returned me the same IV every time. Bad thing since they should be random I belive. Tried srand() as they say in PHP doc and keep getting the same IV over and over. I decided to use the alt_mcrypt_create_iv function found in user contributed notes in PHP doc and now I get a different encrypted pack output each time. Decryption work fine.

    If the random seed generator works fine when creating an IV, getting different IV each time, is it better to use MySQL AES or other DB side specific encrypting technique for all data that you have to test against in querys?

    Development of my biz is done here on my XP box, not much cash yet to loan the VPS, but will be hosted on a VPS running Linux Redhat or Fedora. If then using MCRYPT_DEV_RANDOM will I get a different IV each time?

    I ask because, at first, I intended to use this library to encrypt login infos but if the encrypted output change every time it’s a nogood for finding key in a DB cause no way to validate what was stored against what the user submit when he comes back. Since my cie will be on a VPS my commercial partners formely asked me to cipher everything, logins included. Tuff bunch but, in some way, they are the boss ;o)

    As it is right now, this library will proove to be usefull for securing session and cookie data, data transfers between biz VPS, main office and partners servers and some of the DB data storage.

    Thanks

  26. Jeff Brown Says:

    StoneCypher,

    Excelent job.

    In your minimalist example on this page, there’s a small error, that was probably causing Doug and the other Jeff to thrash.

    In both the top and bottom minimalist example says:

    $unpacked = UnpackCrypt($packed, ‘Password’);

    should say

    $unpacked = UnpackCrypt($packed['output'], ‘Password’);

    Thanks for the code,

    Cheers

    Jeff

  27. Ryan Haney Says:

    Does flash support SSL? Maybe that’s the best way to transmit text between client and server. As for storing info in a database, I’ll try out this lib.

    Thanks!

  28. stonecypher Says:

    Yes, Flash does. There are many points at which one wants encryption at different points than transit, though; this set of functions is intended to help make that easier, is all.

  29. Mattias Says:

    Thanks a lot for this library, really what a noob (me) needs! There was previously a mention of a server->client library. I was thinking that also a client->server library would be useful. As an example, if a user is providing personal information in a form on the client side (password etc), you would want to encrypt for transmission using javascript and decrypt using php. Or am I missing something? Maybe such libraries already exist? Or would that not be the way to go?

    Thanks beforehand, any help is greatly appreciated!

  30. Howard Says:

    Hello,

    We’ve been using this awesome encryption library for years now, but now we’re exploring moving to UTF-8 and the data created by PackCrypt is corrupted when saved to the database.

    Basically we have Apache, PHP, Mysql, and the Mysql PHP API all talking in UTF-8 but the passwords cannot be saved to the db. I also suspect that when we convert the tables to UTF-8 the existing encrypted data is all corrupt.

    Any tips or advice?

    Thanks!

  31. John Haugeland Says:

    Well, encoded data isn’t Unicode data. There are byte patterns that aren’t legal in Unicode, and byte patterns which will be converted to other byte patterns by the utf8 normalization process.

    Encoded data wants to look as random as possible; all patterns that are suppressable should be suppressed. As such, there’s going to be a near-perfect random distribution of data, meaning you’re almost certainly hitting the convertible and suppressable ranges in.

    Even if the passwords are unicode – which in some ways would be deeply elite – the encoded versions of them are just raw binary data. Regardless of your other content, the password column should have a simple case sensitive binary 8-bit collation.

    Hope that helps.

  32. Howard Says:

    Hi John,

    Your timely response is indeed immensely helpful in explaining what I discovered and came back to report. I wanted to add for any other readers that I fixed my particular problem by switching the password column to a BLOB in MySQL 4.

    For users of MySQL 5 it looks like you can use VARBINARY or BLOB as you wish.

    John, may I ask: I tried using a TEXT colum with utf8_bin (multilingual case sensitive binary?) collation and it seems that’s insufficient. For me only a BLOB (which has no collation) did the trick, and from your description it looks like this works since BLOGS are storing case sensitive binary data (not sure if its 8-bit).

  33. John Haugeland Says:

    Yeah, well, it’s still UTF is why. UTF isn’t binary safe. The problem isn’t the text column, it’s the collation. (Admittedly, that’s not what text columns are for, and they’re ill placed in context. Still they aren’t the actual problem.)

  34. Mori Says:

    I’ve got such a result:

    array(3) {
    ["success"]=>
    bool(true)
    ["reason"]=>
    string(18) “Successful unpack.”
    ["output"]=>
    bool(false)
    }

    What have I done wrong? I’m storing the data from $packed['output'] in MySQL5′s BLOB, so it should be fine, right?

  35. John Haugeland Says:

    Mori: yes. Usually when you get a result like that it’s because you didn’t escape the data on its way into the database. Remember that the binary output is well distributed across the ascii range; that means it contains things that aren’t liked in raw SQL, like quote marks and semicolons and equals signs and so on.

    You need to escape all data going into a database, always. This kind of error is also the basis of most SQL injections.

  36. James Van Lommel Says:

    John -

    Thanks so much for providing this!

    So that it could meet some additional requirements of mine (encrypting very large text files), I created my own version that would work as a PHP stream filter.

    Full details on the web page at (link removed by blog owner)

    Kept the BSD license – let me know if there’s anything else I can do to give your proper credit for this.

  37. John Haugeland Says:

    Mister van Lommel has graciously allowed me to fold his work into the library as a collaboration. To that end, I will make it available at the new site in a matter of days.

    I appreciate his sensitivity to my desire to keep a single point of release for this library; he has been eminently kind about the matter.

  38. Jupiter Says:

    The links to download the StonePHPSafeCrypt.zip file no longer work now that content is hosted at fullof.bs

    Please update links or provide new so this beautiful work of art can continue to live.

  39. John Haugeland Says:

    Whoops! Thanks.

    There’s actually something very new coming up very soon. :D

    The fixed link for now is:

    http://sc.tri-bit.com/outgoing/StonePhpSafeCrypt.zip

  40. Jupiter Says:

    Hi John. Your loyal fans are anxiously awaiting the new SafeCrypt project based on your collaboration with James Lommel. Any idea of the time frame for that release?

    Thanks for all the hard work you do.

  41. John Haugeland Says:

    Yeah. I’ll do that this weekend. I kinda forgot – I’ve been on an Erlang kick lately, and I was documenting scutil, my erlang utility library (you can see the half-baked docs here: scutil.html , and you can download that library here: http://scutil.com/ )

    Thanks for reminding me. I have too many libraries, and I tend to forget about one in favor of another fairly frequently.

  42. John Haugeland Says:

    Incidentally, the new version of PhpSafeCrypt will be documented, and will have test suites.

  43. Carlton Says:

    Where can I get the source? The link doesn’t work.

    I have been looking for something like this for a long time.

    Thanks for your contribution.

  44. John Haugeland Says:

    The host that was on appears to have been accepting money for backups and mirroring they never actually did. This is particularly galling because I own a host, and never moved the site because I was too lazy.

    It’s been seven years since I wrote that. I can do a much better job these days, and since the Gawker incident, I’ve been inspired to believe that someone has to. I am currently, over this holiday break, working on the replacement.

    I put this on like GitHub or Ohloh or something once. If you need something immediately, you might be able to dig it up there. There’s also a pretty decent chance that I put one or another variant of this in my public utility directory once, http://scutil.com/ , which wasn’t on that godforsaken host, so is still just fine.

    The short version is, unless you’re in immediate need, wait for the new one. It should be a week or so, and it’s going to be quite a bit better than the one I wrote way back when.

    Sorry it took me so long to respond. Holiday season, and all that jazz. I’m going to email this to you, to make sure you saw it, because it’s been two weeks and maybe you think I don’t pay attention to this blog anymore.

    Happy holidays, mang.

    - John

Leave a Comment

Your comment

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.