search
How to write more secure software
By Jim Thomason
From Jim's Journal
Copyright and © 03/19/2005, published on 03/21/2005

This is a tutorial to help programmers write better serial number routines for their applications

Before anybody chews me out about releasing hacking information, it's already out there. I'm not detailing anything that the hackers don't already know and that isn't publicly available already on very detailed hacking sites. Knowledge is power. If you know how the crook is going to try and break into your home, you can take steps to deter it.

Okay, buddying hobbyist programmer. You've decided that you've got a great idea for an application. You want to write the next killer program (Kool Widget (tm) ) and are positive that it's gonna make you megabucks. There's just one problem - how do you get it to your users?

You can try to find a software publishing house, license it to them, and have them manufacture, market, and distribute it. It's a great way to go, but it's a tough market to get into (lord knows I don't know how), and you also end up with only a percentage of your sales. Of course, the argument can be made that getting 10% of $1,000,000 is still better than getting 100% of $0, so it's your call.

But, there is this amazing new thing called the internet that allows all of these computers to hook together and share information. And with the internet came shareware - downloadable programs. Give away your Kool Widget, people will download it, realize how great it is, and pay you your money.

In a perfect world, that is.

Unfortunately, there are a few hiccups in ours. One of which is that people tend not to want to pay for things that they already have gotten for free. If they've downloaded Kool Widget, it works perfectly, and it's really great, why should they give you your $10 fee? Sure, you wrote a license agreement, but nobody ever pays any attention to those. Sure, it's the Right Thing To Do, but it also takes effort. There's no reward, just the consequence of losing $10, seemingly for no reason. So how to encourage your users to pay up?

Registration is the most common way to do it. This mainly takes three avenues.

1) Crippleware - you slightly disable Kool Widget in some manner. The user gets a taste of what it can do and how awesome it is, but if they want to use everything, they have to pay you. You then send them a code, they type it in, and whammo - their program is fully functional. If you want to do it this way, then the best of luck to you, you don't need to read any more.

2) nagware - similar, but in place of (or addition to) disabling some features, you pop up alerts from time to time. "Hey, you're using my app! Register or you're a thief! Thief thief thief!" That sort of thing. People are encourage to register because you guilt them into it. And also annoy them with the alerts. Of course, there's the risk that you may piss them off enough that they just delete your program.

3) separate programs - this is far less common, which is a shame since it's much more secure. You release a demo program. It doesn't just have features disabled - they're not even in the program! When your user registers, you grant them access to download the full version of your program. They then delete the demo and use the full Kool Widget. If you want to do this, then good for you. It's tougher to pirate, since it requires someone to make a full version of Kool Widget available for download. Not impossible - but fewer people would do it and it's a little tougher to track down. It's also a smidgen bit more difficult to manage. Personally, I'm just too lazy. Regardless, it's outside the scope of this article.

But, no matter what you do to encourage people to register, there will still always be people that just want your program for free and will try to steal it. They'll hack into it, or break your registration system, or bypass it, or download the full version from somewhere. That eats into your revenue, and this article will try to help you prevent it.

Before we begin, I want to emphasize something - you cannot stop hackers. Was that clear enough? You CANNOT stop hackers. There will always be someone somewhere willing and able (and with enough time) to break through anything you have. No software safeguards are enough of an immovable object against hacking's irresistable force. Your goal is to make it enough of a pain in the ass that you'll stop the junior to average hacker, and hope that the dedicated one gets bored before breaking through.

Someone will always have drive enough (some people love a challenge) to break through no matter what. So you'll need some backdoor traps to try to shut him down after the fact. We'll discuss those as well.

Of course, the overarching thing that you will always need to remember is that you want to keep your customers happy. Involved registration schemes that lock out hackers but piss off legitimate users will cost you far more sales in the long run than a few hot passwords on the internet. Most people don't know where to find them or how to use them. It's really a small market you're worrying about here, all things considered. So keep your users happy first, and keep your software secure second.

Hey, though, the hackers always like to say that they're rising to the challenge of breaking into software, so I'm all about raising the bar right back for them. It's a challenge on our end to improve security.

The counter-argument is that time spent improving the security of your application is time better spent on improving your application itself. I can't argue with this, but I'm hopeful my jumping off points here will minimize the time you need to spend re-wiring things when your app gets cracked. And it will get cracked.

Some of this will seem like common sense to some of you. Others will think that it's all new and amazing. It will probably end up skewed towards objective-c, but the principles apply to anything. This is stuff that I've gathered and learned from releasing some shareware apps that have been cracked and trying to backfill in better security. Hopefully my experiences will make your software more secure.

Oh, and I'm not going to delve much into technicalities or the details of how a registration code should work because I'm scared to death of lots of copycat serial schemes showing up all over the internet that are all going to be susceptible to the same thing. I'll try to help with some of the tools, but you need your own unique design.

So, to begin with, I have written some little pieces of shareware at Basset Software. Far and away, my most popular program is Fish Bowl. It puts a little cartoon goldfish in a bowl on your desktop. Register for $3 and get 9 other fish. Somebody cracked it. Fish Bowl is, by all accounts, a silly little program. It has no practical value, no use, no nothing. But someone took the time to crack it. Kool Widget doesn't stand a chance.

How should your registration code work?

The simplest thing is to just have your user type in a registration password. Some word or phrase or something. Everybody that registers, you have them type in the word "rabbit" to your registration window, and they're registered. This all works fine until someone leaks that the password is "rabbit" out onto the internet, and then everyone knows what it is. You can't simply block the word "rabbit", since that would affect all of your legitimately registered users. And if you make them mad, you'll lose far more business than you would from a few cracking attempts.

Your next step is to uniquely link it to the user's name. Joe Public pays the $10 for Kool Widget, and you send him a unique code based upon his name, maybe incorporating in the product name as well. That way, you cleverly reason, you can re-use the same piece of code across multiple pieces of software (I did the same thing with my software). Saves you lots of time!

So let's try a sample registration scheme. Proper memory management is a task left to the reader.

int reg (const char* name, const char* product) { int namelength = strlen(name); int productlength = strlen(product); int i = 0; int key = 0; for (i = 0; i < namelength; i++) { key += name[i]; } for (i = 0; i < productlength; i++) { key += product[i]; }; return key; }

Bam. My registration code for "Jim Thomason" and "Kool Widget" is 2210. "Jim Thompson" can't use my code, because the code for his name is 2225. Likewise, my code doesn't work with "Kool Widget 2.0" because a valid code for that would be 2386.

Of course, you still need a way to validate your code so once the user types it in, you know that it's correct and they didn't just guess. That's easy too:

int validate (const char* name, const char* product, int givenkey) { int namelength = strlen(name); int productlength = strlen(product); int key = reg(name, product); return key == givenkey ? 1 : 0; }

Toss these both into a library, and you're ready to go. Just link to the library in your app (or if you're in cocoa, you can embed this into a framework (feel free to use an objective-c version)) and you're all set.

This is exactly what I did with my applications. One immediately problem is that if this library has a flaw in it, then if one of your programs is cracked, they're all cracked.

My poor software didn't stand a chance, this approach has at least two critical flaws. So how would some nefarious individual get around this approach?

They'd pull out my framework, link it into a new executable, and simply have that executable call the 'reg' function with their name and the product. Clueless little me didn't see it coming at all. And now all of my software is hosed.

Being careful to remove your header files, strip your binaries (using the strip command), and building without debugging symbols will go a long way towards making this tougher, but nowhere near impossible.

First step you take is to remove your register function from your library, and then re-write your validate function to contain it:

int validate (const char* name, const char* product, int givenkey) { int namelength = strlen(name); int productlength = strlen(product); int i = 0; int key = 0; for (i = 0; i < namelength; i++) { key += name[i]; } for (i = 0; i < productlength; i++) { key += product[i]; }; return key == givenkey ? 1 : 0; }

You're still susceptible to an easy crack to your program - swapping the library. What if our little hacker friend removed your validation library and added this one in its place:

int validate (const char* name, const char* product, int givenkey) { return 1; }

Congratulations, your program is now cracked. Any name and code that are typed in will now work, since the new function always returns 1. Cracks are also somewhat rarer than serial numbers and a little tougher to find. Again, I don't want to convey a false sense of security that cracked libraries don't exist for software, or that this one could even be distributed easily as a software note for the user to compile and install, but it's not quite as easy (or as trustworthy!) as typing in a serial number you've found.

This leads us to our next strategy - don't link to an external library, always embed your validation routines. They can still be reached, but it's a lot tougher than just poking around in your library or replacing it. Kool Widget v1.1 now doesn't allow the user to generate their own numbers.

But there's still a glaring problem, if the hacker is watching the memory of your program (running it in a debugger, for example), it probably won't take him long to figure out when your program enters its validation routine. It's probably going to be the same bit of code that runs sometime around startup, and once he clicks the "register" button. That narrows the search in memory, and when he's looking there, he'll see that when "L33t Hacker" "Kool Widget" is typed in, the code 1965 is generated. Type that into the box and bang - he's got a hot serial number.

With all of these hot serial numbers floating around, you're going to try to block them out. A small modification to your source code should cover it:

int validate (const char* name, const char* product, int givenkey) { int namelength = strlen(name); int productlength = strlen(product); int numinvalid = 1; int invalid[1] = {1965}; int i = 0; for (i = 0; i < numinvalid; i++) { if (invalid[i] == givenkey) { return 0; } } int key = 0; for (i = 0; i < namelength; i++) { key += name[i]; } for (i = 0; i < productlength; i++) { key += product[i]; }; return key == givenkey ? 1 : 0; }

This helps a little bit. Now when Kool Widget v1.2 is released, it blocks L33t Hacker's serial number. You're not out of the woods yet. First of all, the number 1965 is in your source code. A simple grep through it will reveal that 1965 is there. So the crack in this case is for the hacker to change that number - make it 1966, for instance. Now his original code still works, since your software thinks that 1965 is invalid. This is a very easy crack.

L33t Hacker can also avoid the issue by not upgrading his software, but you're kind of stuck on that front.

But it also misses the key part of the issue - your serial number scheme has been compromised. What does that mean, exactly? Well, it means that your scheme is no longer any good. At least someone out there knows how to generate fake codes for your program. For example, Kool Widget v1.0 left your register function available. Anybody that has a copy of Kool Widget v1.0 has access to your register function and can just generate a new serial number for themselves. Any time they need to. No matter how many you block. It's just too easy to make more.

And Kool Widget v1.2 can still be hacked in the same was as v1.1 - just watch the memory addresses and you'll get a code pre-made for you.

Hence, you need a new scheme. But you've also got a lot of people that registered Kool Widget v1.0, v1.1, and v1.2. They're not going to be happy about needing new registration numbers. So if you can be backwards compatible, that'd be best.

I tried this. I added an 80 character salt to my programs that sufficiently hid the product name in the register function. That way the Black Hats couldn't just type in "Fish Bowl", as the product, they needed to type in "fdajkfl;ewjkl;aafdafsafafrFKALFJA", which is a lot tougher to guess. But I left a backdoor - if an old code (with product "Fish Bowl") was already in the preferences, I'd allow it. Newly typed in codes had to be for the new product. That way, old users could continue to use it without hassle. Old users with cracked codes were kicked out due to the pirated number checker, and new users couldn't use those pirated codes. It wasn't a perfect scheme, but it minimized impact on my users.

It also failed miserably. I was still using an external library at the time. So how do you find the new product? You drop in your own library and override 'validate' again. Then you watch for the value of the product passed in. Once you know that, you're golden. Hand that value back into the old library from v1.0 that had the register function exposed (yes, I was dumb enough to do this) and you have your code. But the legitimate users didn't seem to notice, and that was good.

Further, it overlooked the fact that my scheme was cracked. All I did was change a parameter going in. That wasn't good enough. I needed something new.

I considered making all registrations phone home to my webserver and have that validate. Since the registrations came in through that, I had a record of people that paid. If a hacker tried to phone in, he'd be rejected and his code wouldn't work. Bliss.

This approach is generally frowned upon. Users don't seem to like it. For one thing, it needs to phone home every time it starts up to do the validation check. Would you like it if your notepad application opened up an internet connection every time you launched it? Sure, it's nice to be trusting and all, but you can't confirm what information it's transmitting about you. And if it doesn't need a connection, it shouldn't require one.

It gets worse if you enforce being able to make a connection. What if their internet connection is down? Or it's a laptop and they're on the road? Suddenly not being able to use Kool Widget (which doesn't do anything with the internet) simply because they're not connected is going to annoy people.

You fix it by modifying the program to do the cursory validation check as per the function above, and only phone home if connected to the internet. That way, users legitimately not online can use it. But this negates the internet functionality - the hacker gets around it simply by disconnecting when he launches the program.

More knowledgeable people will simply intercept the connection Kool Widget is trying to make and just return back whatever the value for "this code is valid" is. And your fancy internet scheme is worthless.

Basically, that approach is great in theory, but in practice it's still easy to get around, and there's greater chance for pissing off your users. I'd say to skip it, but it's your call.

Anyway, we still need something better. The trick is not to ever generate a valid serial number in memory. Generate something else that can somehow be used to validate.

As a side note, some people say you should just use PGP encryption (or the like) to encrypt a serial number, since it's secure and safe. Except for the fact that it's not. If your public key is visible in your source code, the hacker just needs to swap it out for his own public key and generate a code with his private one to peek inside. You'd need to sufficiently hide your public key (which can be done, by the way), but you still run the risk of annoying your users, simply because most encryption schemes tend to generate long strings of gibberish. It's not exactly user friendly for typing in numbers. Remember - you want to stop the bad guys, but keeping the good guys happy is much more important.

One way to do that is fairly easy:

int validate (const char* name, const char* product, int givenkey) { int namelength = strlen(name); int productlength = strlen(product); int numinvalid = 1; int invalid[1] = {1965}; int i = 0; for (i = 0; i < numinvalid; i++) { if (invalid[i] == givenkey) { return 0; } } int key = 0; for (i = 0; i < namelength; i++) { key += 100 - name[i]; } for (i = 0; i < productlength; i++) { key += 100 - product[i]; }; return (key + givenkey) / 100 == namelength + productlength ? 1 : 0; }

Now, instead of adding on the integer value of the character to the new key, we add on 100 - that value. The final result is that we've generated the number 100 * (namelength + productlength) - key, but not the actual key itself. The key, however, can solve that equation.

givenkey = 100 * (namelength + productlength) - key ... givenkey + key = 100 * (namelength + productlength) ... (givenkey + key) / 100 = namelength + productlength

Further, we can expand this to our invalid code lookup

int validate (const char* name, const char* product, int givenkey) { int namelength = strlen(name); int productlength = strlen(product); int numinvalid = 1; int invalid[1] = {-1865}; int i = 0; for (i = 0; i < numinvalid; i++) { if (givenkey + invalid[i] == 100) { return 0; } } int key = 0; for (i = 0; i < namelength; i++) { key += 100 - name[i]; } for (i = 0; i < productlength; i++) { key += 100 - product[i]; }; return (key + givenkey) / 100 == namelength + productlength ? 1 : 0; }

Here, we just subtracted our invalid code from 100 so that 1965 never appears in the source. This approach can immediately be made more secure by picking a number that's tougher to guess than 100. Subtract things from 8366, for example.

But the determined hacker can still watch what's going on. It takes some detective work to find this function, but it can be done. And your binary application can still be cracked by a programmer who knows what he's doing. Just insert an instruction to return earlier in the function, or change that equality to an inequality, and your function will always return true. It's fairly easy to guess that your function will always be called when the user clicks the "register" button, so that's where he'll look.

Make it harder to find. Instead of validating immediately, validate in a didEndSelector after your sheet closed. Or start up a timer to validate later. Or have a different thread wake up and do the validation. This won't stop him, but it'll slow him down.

He'll still find it, though. You can minimize the damage by getting rid of your validation function - inline all the code! Just use a macro:

  1. define m_validate(name,product,givenkey)\
int m_namelength = strlen(name);\ int m_productlength = strlen(product);\ int m_numinvalid = 1;\ int m_invalid[1] = {-1865};\ int m_i = 0;\ for (m_i = 0; m_i < m_numinvalid; m_i++) {\ if (givenkey + m_invalid[m_i] == 100) {\ return 0;\ }\ }\ int m_key = 0;\ for (m_i = 0; m_i < m_namelength; m_i++) {\ m_key += 100 - name[m_i];\ }\ for (m_i = 0; m_i < m_productlength; m_i++) {\ m_key += 100 - product[m_i];\ };\ if ((m_key + givenkey) / 100 == m_namelength + m_productlength ? 1 : 0)

Now the code is duplicated every single time you do a validation. You no longer validate like this,

if (validate(name, product, key)) { //you're valid! }

Now you do this:

m_validate(name, product, key) { //you're valid! }

It's about the same to you, but the compiled code is:

int m_namelength = strlen(name); int m_productlength = strlen(product); int m_numinvalid = 1; int m_invalid[1] = {-1865}; int m_i = 0; for (m_i = 0; m_i < m_numinvalid; m_i++) { if (key + m_invalid[m_i] == 100) { return 0; } } int m_key = 0; for (m_i = 0; m_i < m_namelength; m_i++) { m_key += 100 - name[m_i]; } for (m_i = 0; m_i < m_productlength; m_i++) { m_key += 100 - product[m_i]; }; if ((m_key + key) / 100 == m_namelength + m_productlength ? 1 : 0){ //you're valid! };

And that code is duplicated throughout your program. You no longer have a single point of entry to hack your code, now it must be hacked everywhere. The more places you check the code, the more places that much be cracked, and the more work that you've created for the hacker to get into your software.

Still. If he's inspecting those sections of code in detail, he may be able to figure out how the validation number is generated and how it compares to the one typed in, giving him a new one. We'd like to keep him out as much as possible. So we'll add a checksum to your serial numbers.

All this means is that before you do an actual validation of the number to determine if it is correct, you do a quick check to determine if it could be correct. We'll modify the routines as follows:

int reg (const char* name, const char* product) { int namelength = strlen(name); int productlength = strlen(product); int i = 0; int key = 0; for (i = 0; i < namelength; i++) { key += name[i] ; } for (i = 0; i < productlength; i++) { key += product[i] ; }; return key * 34 + 7; }
  1. define m_validate(name,product,givenkey)\
int m_namelength = strlen(name);\ int m_productlength = strlen(product);\ if ((int) (givenkey - 7) % 34 == 0) {\ int m_numinvalid = 1;\ int m_invalid[1] = {-1865};\ int m_i = 0;\ for (m_i = 0; m_i < m_numinvalid; m_i++) {\ if (givenkey + m_invalid[m_i] == 100) {\ return 0;\ }\ }\ int m_key = 0;\ for (m_i = 0; m_i < m_namelength; m_i++) {\ m_key += 100 - name[m_i];\ }\ for (m_i = 0; m_i < m_productlength; m_i++) {\ m_key += 100 - product[m_i];\ };\ if ((int) (m_key + (givenkey - 7) / 34) / 100 == m_namelength + m_productlength ? 1 : 0) #define end_m_validate }

call as:

m_validate(name,product,givenkey) { //I'm valid! } end_m_validate

All we've changed here is a minor hint to the registration number. We multiple the number generated by 34, and then add 7 to it. That's our checksum! We know that any valid serial number that comes in fits that pattern. So any number we're given, we subtract 7 from it. If that difference mod 34 is 0, then it might be a valid number, so we'll proceed. Oh, and since we now have two conditionals, we define another macro to simply contain that extra closing brace to indicate the end of validation.

Oh, and one final check you can perform. Only allow a serial number to pass your checksum check one time per program run. The chances of a user mistyping their serial number and still hitting a valid one are fairly remote, but the chances of a hacker trying a brute force attack to get a password are reasonable. If you only allow one valid checksum per launch, you'll force the would-be cracker to relaunch the app every time he tries a bogus number. This will add time to his attack.

And after all of this, your producct is finally secure, right?

Nope. Sorry, but again, referring back to the first few paragraphs - your program is never secure. Hackers already know about all of these techniques. An assembly programmer can still disassemble your app and look at the raw assembly, trace it, and figure it out. If someone can come up with a 100% reliable serial number scheme that doesn't alienate all of his legitimate users in the process, he'll end up a very wealthy man.

But you've slowed them down a lot. An awful lot. And you're hopefully restricting the number of people that could perform the hacks necessary to a much smaller subset. The script kiddie that linked into your external library to get your registration routine and generated a code will piss you off because you were dumb enough to allow it. The assembly programmer that disassembled your program and stepped through the source, and reverse engineered a registration routine from your validation routine? More power to him. Sure, ban that number in the next release, but take some comfort in the knowledge that he had to earn that serial number from you.

To review the important pieces -

  • Don't ever ever ever let your registration function escape into the wild. There's only one place that bit of code should ever be - your own computer.
  • Don't put your validation routine into an external library and link to it. Embed it into your code.
  • Don't release code with debugging symbols, headers, or without running strip or the like on it.
  • Don't generate a valid serial number in your validation routine, hide it somehow.
  • Do use macros or the equivalent to create as many points of entry into your validation routines as possible.
  • For numeric constants, do use random numbers that are tougher to guess than things like '10' or '100' or '25'. Use '17' or '84421' or '124772'.
  • Do use a checksum to help prevent entry into your validation routines.
  • Do keep track of pirated serial numbers in a secure fashion and disable them in future releases.
  • Very importantly - Expect to be hacked. It's a fact of life, and you're not going to stop it. The more you learn about how it works, the bigger the lock you're putting on your door. But it's still one person with one door holding back an army. When it happens, suck it up and deal, try to minimize the damage, replace your scheme if truly necessary, and move along.
  • Most importantly - Your users come first. If you spend six months writing the world's greatest validation scheme and don't spend nearly as much time on your app as a result, then people won't buy it, and you won't make any money. And, to add insult to injury, someone will still hack you.

03/21 - Addendums and errata:

Jay Kim pointed out a typo I had in my macro example. The for loops were trying to end on m_i < m_key, not m_i < m_namelength and m_i < m_productlength, respectively. The original version created an infinite loop.

"MacBugs" pointed out something I had MIA - preventing debugging with gdb. This is something that Apple uses with a lot of their apps to prevent you from starting up the program in gdb and doing a lot of the poking around in it that the Bad Guys would do. Personally, this is really over my head, but there are some notes on the issue on CocoaDev http://www.cocoadev.com/index.pl?CocoaInsecurityFollowUp. Of course, the issue can also be gotten around fairly easily by an experienced user, such as via http://steike.com/HowToDebugITunesWithGdb. Like everything else, it can and will slow someone down, but it won't stop them.

Kevin Ballard noted that I glossed over the fact that writing your validation routines in Objective-C makes things easier for the would-be hacker. You'll note that my examples are all written in C, and there's a reason for that. C variables, functions, macros, etc. don't show up in tools like class-dump. nm will see them if you forgot to strip. Your macros will remain hidden.

But if you're in Objective-C, the good hacker can write an input manager to drop into your program, manipulate the dynamic runtime environment, and behave how he chooses. If you're not generating the serial number in memory, you're still making his life much harder. But it's very easy for him to override a single "validateCode" method to always return true. C code forces him to get closer to the metal.

If you insist on using methods or functions, you may have some luck by choosing obfuscated names. Don't call your validate method "validateKey:forUser:andApplication:" call it "canMovePlayer:inDirection:onLevel:" or something else appropriate for your application. Security-wise, this is about the equivalent of putting a sign on your front door that says "This is not a door". This is a popular method, but I'm not convinced of the utility. Judicious use of debugging tools will determine that you're always entering this method at validation points, regardless of what it's named

There's the prospect of tying your serial number to the machine it was entered in to. This will lock down a serial number to a given machine and prevent it from being used elsewhere. As long as your program isn't cracked and the scheme bypassed, this can add to security. However, it's also far more likely to piss off your users. For one thing, what happens if their machine crashes? Or if they simply upgrade? They'll need to take the time to contact you, you'll need to sift through their records to verify, you'll need to transmit new information to them. It'll be a hassle.

And it will also prevent them from installing Kool Widget on their mother's computer. Now, of course we don't want users to install multiple copies of Kool Widget, we want them to buy each one. But let's be serious here, people are going to share serial numbers locally among family like that. Heck, a lot of people are going to share serial numbers locally among themselves. I have two machines, and if I bought a piece of shareware, I'd probably want to put it on both. I'm only using it on one machine at a time, so I feel it's justified. Locking down the program so I can only use it on my laptop and not my desktop would probably annoy me, even though it's technically not properly licensed. Someone installing two copies of a legally purchased app or sharing one with his mom is the least of your worries. Remember - pissing off your users will cost you sales in the long run.

Unexpected actions are useful - embed checks at random points. Software is fast enough at this point (especially C), that doing a few manipulations on some small strings (your registration name and serial number) won't take any time at all. Go ahead and embed those macros at random points in your code. Right in the middle of a completely unrelated action, have it do a validation check. If it finds that a bogus code has been entered, then handle it. The more checks you have in more places, the harder it is for the program to be cracked.

Do not but do not even think about damaging the user's computer. Occasionally someone comes up with the smart idea that if a user is typing in a pirated serial number, they deserve what they get. So they think it would be cool to teach them a lesson, and wipe out their home directory, or delete files, or otherwise damage their machines. It doesn't matter if the person is being a jerk by using a pirated serial number, that is never any excuse to do physical harm to a user's machine. If you do that to someone and it gets out onto the internet (and it will get out onto the internet), you can probably kiss almost all future sales of all of your software good-bye.

On the same token, some people suggest that it's good to cripple your program when a pirated number goes in. Have things work inconsistently. If it's a numerical program, introduce error into your calculations. Draw widgets slightly incorrectly. That sort of thing. That will surely teach your users a lesson about pirated numbers, right? Well, probably not. All the user is going to see is that your program is full of bugs and doesn't work right. That will lead to it being trashed and being badmouthed, and that's bad for business. The most that I'd recommend on this front is something simple - when your program hits one of those unexpected actions you embeded in your "movePlayer:onMap:" method, have it display an alert telling them that their serial number is pirated, wipe out the pirate number from the preferences, and quit the program. You don't want to encourage your users to stop using your software, you want to encourage them to buy it.

I'm not down on the concept of using public key cryptography except for simplicity for the user. Sending them a massively huge string of random characters and telling them it's their password won't score you points. But if you do want to take that approach, make it much tougher for the Black Hats to hack you. You'll have to include that public key in your program somewhere - make sure it's well hidden. Further, use some unexpected actions for it. If a hacker does find your public key and replaces it with his own, you've got a problem. So embed actions randomly throughout your program to decrypt random strings and phrases that you've embedded. This will force him to change your key AND all those other occurances of other strings. Again, not impossible, but it makes his job harder.

Varying up the macros can add some extra time to his effort. Two or three functionally identical but syntactically different macros make search and replaces that much harder to do.

Writings
> Horror
> Itty Bitty Horror
> Jim's Journal
03/13/2010 Mother Goose at the Library
10/08/2009 Story Time
08/02/2009 Soda free is the way to be
05/17/2009 We failed
03/05/2009 Alex's Development
02/21/2009 Nice parenting
01/15/2009 I got something
11/24/2008 A month with the G1
11/04/2008 A week with the G1
10/25/2008 Alex walks about
10/20/2008 Alex on the slide
10/01/2008 Alex crawls about
07/11/2008 Product Endorsement Corner
07/02/2008 Beware of special deals
04/29/2008 10,000 miles!
04/25/2008 I just don't get marriage
04/11/2008 My thyroid (again)
04/02/2008 My thyroid
03/22/2008 Guitar Hero; or why I hated marching band
02/21/2008 This is me
02/17/2008 Good bye, Clark; Hello, Kent
02/08/2008 Koka's famous
02/07/2008 Artificially what?
02/05/2008 Product Endorsement Corner
12/18/2007 Jim sent in from his phone...
10/12/2007 Router dust collection
10/08/2007 Yo Ronald! WTF?
09/19/2007 I think I fixed our plumbing
08/22/2007 James Alexander Thomason
08/17/2007 I conquered the roof
07/17/2007 Historical tidbit
07/14/2007 Well worth the cost
07/10/2007 The Visit, Fall 1994
07/10/2007 PiPs, Spring 1995
07/10/2007 The Beggar, Fall 1994
06/28/2007 Fun In Florida
06/01/2007 Mountain Don't
05/09/2007 Product Endorsement Corner
05/07/2007 Things I have burned myself on
03/29/2007 Basset Hound Town
03/28/2007 Technology is awesome
02/22/2007 New glasses
02/17/2007 Hooray for the government
02/02/2007 My flying machine
01/13/2007 Where'd it go wrong, Sailor Jack?
01/05/2007 My god it's full of stars
12/14/2006 Cartoons I've liked
11/17/2006 Vote for Stan
11/15/2006 T-Mobile lost points with me
11/11/2006 Memory exchange
11/01/2006 Election turnout
10/24/2006 They listened!
09/28/2006 I want to work for google.
09/23/2006 The accidental fractal
09/13/2006 I don't think I like Charlie Chaplin
09/12/2006 My web presence over the years
09/06/2006 So very sad
09/01/2006 House work
07/31/2006 Puppy dogs
07/27/2006 My letter to Best Buy
07/03/2006 Look at all the content
06/25/2006 Stuff I like
06/06/2006 Thanks a lot, Mr. Cheney
05/28/2006 Hot Time Tonight
05/15/2006 A link to some website
05/04/2006 George Lucas is a jackass
05/02/2006 Look! Up in the sky!
05/02/2006 I am so outta touch
04/26/2006 The re-pipe
04/21/2006 Stuff I miss
04/19/2006 Another site upgrade
04/16/2006 I'm quite the handy man, part 2
04/13/2006 Simply Awesome
04/06/2006 Lego Inflation
03/27/2006 Keep in sync with RSS
03/19/2006 I'm quite the handy man.
03/15/2006 How computers store numbers
03/02/2006 Already? Dammit.
03/01/2006 This is why I hate intelligence tests
02/28/2006 We're homeowners!
02/26/2006 Almost outta here
01/12/2006 I must be getting old.
12/29/2005 Wow
12/12/2005 Cheap bastards
11/20/2005 An analysis of the strength of paper clip chains
11/19/2005 So whaddaya think?
11/15/2005 Good luck, Ms. Eckerson
11/07/2005 Use harder passwords
11/01/2005 Buyer Beware
10/31/2005 Happy Halloween
10/19/2005 It ain't easy
10/05/2005 Murphy's House, Pt. 2
10/05/2005 Murphy's House
08/03/2005 For the postgres geeks
07/26/2005 Yuck for aspartame
07/20/2005 CS Students? Where?
07/02/2005 End of an era
06/28/2005 War of the Worlds
06/13/2005 Wow. I'm impressed
05/31/2005 Some iTunes tidbits
03/31/2005 My poor phone
03/21/2005 How to write more secure software
03/20/2005 Wonderfalls
03/05/2005 In the name of science
03/02/2005 Koka on her birthday
02/14/2005 I want to build a park
01/12/2005 I'm a convert
01/10/2005 How many was that?
01/03/2005 A missed marketing opportunity
12/29/2004 My new book
12/15/2004 The Music Box
12/10/2004 Camera, Part 2
12/07/2004 The new camera
11/01/2004 The Cabbie
10/25/2004 Bad marketing
10/14/2004 No more wisdom teeth
10/13/2004 Time to get bigger?
10/04/2004 It's harder to write
10/01/2004 Not with a bang, but a whimper
09/21/2004 What are your goals?
08/23/2004 Writing what lasts
08/13/2004 The future of Basset
08/10/2004 Faster faster faster
08/01/2004 The Big Race
07/23/2004 Poppycock
07/21/2004 Defeated!
06/08/2004 I want to ride my bicycle
05/24/2004 I fixed the dryer!
05/10/2004 An unwelcome visitor
05/04/2004 Viva Las Vegas
04/22/2004 Behold. Basset.
04/18/2004 Now officially basset powered
03/25/2004 I should switch games
03/12/2004 DirecTV is not for me
02/27/2004 So...freakin'....cool....
02/18/2004 Perl: There's only one way to do it.
02/17/2004 A great day in the history of war
01/20/2004 Why open source sucks
01/14/2004 Marketing the future
12/30/2003 Scary Cartoons
12/18/2003 You're not really special
12/07/2003 Product Endorsement Corner
12/01/2003 Our First Thanksgiving
11/22/2003 Just like riding a bike
11/19/2003 Tax Status B for me
11/18/2003 Work ethic, or rather the lack thereof
11/01/2003 Really big news
10/26/2003 Traumatic events from my youth
10/25/2003 17 years late, but here
10/23/2003 Change is good
10/22/2003 Product Advertisement Corner
10/21/2003 I need to branch out a bit
10/20/2003 Design Philosophy, Part 2
10/19/2003 Film has been good to me.
10/18/2003 When I win the lottery
10/17/2003 Our way or the highway
10/16/2003 A golden opportunity
10/15/2003 Life in 5 dimensions.
10/12/2003 And now...the middle...
10/11/2003 One less yellowjacket
10/10/2003 Why do I like what I do?
10/09/2003 Tip your barber!
10/08/2003 We continue to be lucky.
10/07/2003 Violating philosophy already
10/06/2003 Picky about unusual things
10/05/2003 Design Philosophy
10/04/2003 Sure to be interesting
10/03/2003 Priorities are outta whack
10/02/2003 On egomania and not being a jerk.
10/02/2003 I need to write more.
08/27/2003 And justice is served
07/29/2003 Another exciting bike ride!
07/19/2003 My 4 hours in jail
07/10/2003 I have pictures of the aliens!
07/08/2003 A very fine port
06/22/2003 Stay away from that house
05/28/2003 Interesting ride...
05/22/2003 Defending the house (updated 06/08/2003)
05/18/2003 I invented it first, dammit!
05/17/2003 All moved in (updated)
05/14/2003 Biking Log overhaul
04/26/2003 Adventures in moving!
03/22/2003 What a fun day!
03/22/2003 I just love a mystery, part 2
03/01/2003 I just love a mystery!
02/22/2003 Happy Birthday, Koka
02/19/2003 Adventures in driving
02/11/2003 Stupid fireplace
02/03/2003 Programmers shouldn't be creative
11/16/2002 What's in a year?
11/11/2002 Not as much fun as when I was a kid!
10/30/2002 I can drive! I can drive!
10/18/2002 Superman's cape
10/10/2002 twice in a lifetime
09/25/2002 Go ahead and laugh
09/01/2002 things that affect us.
08/27/2002 stupid, stupid toy
08/25/2002 Dangit!
08/22/2002 mean old dentist
08/19/2002 donating blood
08/19/2002 Lucky Us
08/18/2002 3 years and 2 months
08/16/2002 Planet Jim
08/15/2002 New toy for me
08/13/2002 Major site re-vamp
08/08/2002 haircut hassles
08/07/2002 coding, coding, coding
08/03/2002 The big race
08/02/2002 Signs
08/01/2002 toys toys toys toys
07/31/2002 Stayin' at home
07/28/2002 Goldmember
07/28/2002 Botcon!
07/25/2002 New homepage!
07/25/2002 First from jimandkoka.com
07/25/2002 My very first journal entry
> Koka's Journal
> Poetry
> Prose