Introduction
Here's a simple WPF control which will
check to see if an installed app is “registered”. If not, it will
prompt the user for an email address and then email a software key to
the user (to ensure a valid email address) all with no Website
Required!
Background
Suppose you have written a little app
and you're happy distributing it as freeware but you'd really like to
know who was actually using it. You don't want to spend a time
creating a website and adding the infrastructure of a “real”
registration system.
Here's a way to do it which can be
included in your app. You don't need a website at all and you'll
collect the email addresses of people who use your app. The control
relies on the fact that any computer can be an email sender and can
send email via any email account as long as it has the credentials.
In this example, I am using gmail.
When your user starts your application,
they will see a dialog like this:
When the user keys in his/her email
address, the application creates the key and acts as the email client
to generate the email and send it to the user. The user enters the
key and the application verifies that it matches the key it generated
internally. If it does, it is stored in a file (in
users/username/AppData) so the user doesn't need to reenter the key.
As an added feature, the key generation
algorithm incorporates the creation date/time of the folder
containing the .exe file which is most likely the installation
date/time of the application. This prevents a user from posting an
email/key combination which can be shared by all—every installation
requires a different key.
I call this "UltraLight"
because it trades off some degree of robustness in favor of
simplicity.
A possible source of problems is gmail.
If there are lots of registrations coming to gmail from all over the
world, gmail's anti-span sender may block the account and I'll have
to change the code to fix it. I have reviewed gmail's rules and don't
think there is any violation but you should probably review them as
well here;
If you are expecting lots of
registrations, you should choose an email service which will give you
better control (and may not be free).
A real strength of this approach is
that because the key is actually generated by the client, it can
utilize data which is unique to the client machine without sending it
anywhere (which would be a privacy issue). In this case, I use the
installation date to generate the key but I don't send the date
anywhere, just the key. This means I can store the registration
email/key on the client machine in clear text without a security
issue.
As the code is written, it is all or
nothing; the unregistered user can't start the program. With some
minor modification, your app could offer degraded operation to the
unregistered user instead.
Using the code
In order to use the source code, you need to follow the steps
below. To try it out, just download and run the .EXE file and send
yourself a registration key via my
gmail account (“SendKey3145”).
To adapt the control for your own application:
Create an email account for your
app on GMail. This is the account which will send out the
registration emails to your users.
Send yourself an email to make
sure the account is working.
Include the following code near
the beginning of your program, I put it in the MainWindow
constructor:
if (!RegistrationWindow.IsRegistered())
{
RegistrationWindow rw = new RegistrationWindow();
if (rw.ShowDialog() != true)
{
this.Close();
return;
}
}
If you want “degraded”
operation for unregistered users, don't close the window but
instead, check for RegistrationWindow.IsRegistered() anywhere in
your program to enable features.
Edit the source code to include
the gmail account name and password. Also, edit the privacy
statement, email body, etc.
BE SURE TO OBFUSCATE! Otherwise
your application will include your email account name and password
in clear text for anyone with a disassembler.
When your users use your
application, it will send them the registration key via the gmail
account.
You can log on to the gmail
account at any time and see the registration messages which have
been sent out and use the email addresses appropriately.
Points of Interest
How to send mail: This is pretty straightforward using the SmtpClient
object -- just set up all the parameters and call Send
.
public void SendMail(string toMail, string subject, string body)
{
var fromAddress = new MailAddress(senderEmailAddress, senderEmailFrom);
var toAddress = new MailAddress(toMail, toMail);
var smtp = new SmtpClient
{
Host = "smtp.gmail.com",
Port = 587,
EnableSsl = true,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Credentials = new NetworkCredential(fromAddress.Address, senderEmailPassword),
Timeout = 10000
};
var message = new MailMessage(fromAddress, toAddress){Subject = subject,Body =
try
{
this.Cursor = Cursors.Wait;
smtp.Send(message);
this.Cursor = Cursors.Arrow;
MessageBox.Show("Registration key requested. Check your email (and junk email) key.");
}
catch (Exception e)
{
this.Cursor = Cursors.Arrow;
MessageBox.Show("Email request failed: " + e.Message);
}
}
How to store/retrieve data in AppData: This was a bit tricky, the MSDN documentation is obscure and the various answers on the web are often in conflict. The trick is that if you are using VS 2010 (I am using VS Professional), if you right-click the project file and select "Properties", then go to the "Settings" tab, VS will do most of the work for you. You will need to edit the Settings.Settings file (back on the solution explorer under "Properties" and add the keys you intend to use. If you don't the program will throw an unhelpful XmlParser exception if you access a key which is not already in the file. I have put try-catch blocks around the accesses. You'll also need to edit "AssemblyInfo.cs" file and put in reasonble AssemblyCompany
, AssemblyProduct
, and AssemblyVersion
values as these are used in creating the file folders under the AppData folder.
private static void SetEmailAndKey(string email, string key)
{
try
{
Properties.Settings.Default["RegistrationEmail"] = email;
Properties.Settings.Default["RegistrationKey"] = key;
Properties.Settings.Default.Save();
}
catch (Exception e)
{
MessageBox.Show("Failure writing app configuration file. " + e.Message); }
}
private static void GetEmailAndKey(out string email, out string key)
{
try
{
email = (string)Properties.Settings.Default["RegistrationEmail"];
key = (string)Properties.Settings.Default["RegistrationKey"];
}
catch (Exception e)
{
email = "";
key = "";
MessageBox.Show("Failure reading app configuration file. "+e.Message); }
}
How to get the file installation date: I thought that using the file installation date would be useful but after installation, an .exe file still carries its own creation date. Using the creation date of the containing folder seems to do the trick and would allow the user to change versions without having to change keys.
static public DateTime InstallationDate
{
get
{
string codeBase = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
string directory = System.IO.Path.GetDirectoryName(path);
return System.IO.File.GetCreationTime(directory);
}
}
How (not) to generate a software key: There's not much point in being cryptic in a program when you distribute the source code and I suggest you replace this one with something a bit more sophisticated. The important thing to follow is that the software key is some mangling of the input string which contains the email address and some additional local information. I pass in a concatenation of the email address and the intallation date/time.
string calculatedKey = CreateKey(email + InstallationDate.ToLongTimeString());
public static string CreateKey(string inputString)
{
int i = 0;
foreach (char c in inputString)
{
i += (int)c;
}
return i.ToString();
}
History
Initial revision: 4/9/12.