|
|
|
|
User Registration by Mark Austen - MarkAusten@PoBox.Com
I have for some time now been concerned about software piracy, specifically the piracy of the applications that I've written and as a result of this I've been working on a suitable registration methodology for my software. Over the years I've come across many schemes, some simple, some complex, which try to address the thorny problem of trying to prevent the unauthorised use of software. These break down in to two main systems namely software and hardware. The hardware schemes include parallel port attachments and non-standard formatting of disks. These are of dubious value in my mind and I prefer the software solutions. There are, of course, many different ways of protecting your software including buying third party locking software, however, I decided to try and develop my own scheme, mainly because I like to understand as much about the difficulties and solutions to such problems. Before I discuss the solution I came up with I'd like to take a brief diversion in to the types of schemes that I've come across. The most basic and least secure is the production of two different versions of the software, the shareware and the registered versions. When the user registers the software they are sent the registered version, or a URI to download it and that's it. The registered software is not protected in any other way and relies on the honesty of the user not to give it away to all and sundry. The only restriction here is that the original user is the only one registered with the company/person who distributed/wrote the software and therefore is the only one entitled to support. I have to say that I wouldn't use this method myself, there are just too many dishonest people around and I sell few enough copies of my applications as it is. The next method is to send the user a registration key based on the username and, optionally, the company name. Now the application is locked to the username and possibly the company. The user may give it to someone else, but that second person will not be able to change the username and company. The last method I want to discuss is a refinement of the second method. Here the registration key is also based on the major part of the version number. All minor upgrades use the same key, but major upgrades require a new key. This allows the possibility of allowing the user to have the minor upgrades free but having to pay for major ones. Notice I said "the possibility", you don't have to charge for the major upgrades, but this last method gives you the option. The solution I chose to implement is the last one with the modification that all reports and screens have the name of the company printed on them. Thus, in the case of the Stock Control system, any invoices produced have the registered company printed on them as the invoicing company effectively preventing anyone else from using the software. Now, the problem may be stated as "To produce a unique number based on a string where any change in the string will affect the number and where the underlying encoding is computationally expensive to determine". In determining the solution I decided to use the application name, user's name and company and the major version number as the inputs to the registration routine. If the user chooses to leave the company blank, then I use my own company name in the routine instead. This doesn't get shown but serves to "muddle" the registration key a bit more. My degree is in electronics and one of the things I remember about it is that in communications there is the need for a way to determine small changes in the data stream in order to detect and prevent data corruption. The normal way of detecting this is to use a cyclic redundancy check, or CRC as it is quite often referred to. This is sufficiently good to be in use some 20 years after I took my degree. I don't know just when this algorithm was created but that very fact that it is still being used 20 years after I first came across it indicates that it can't be all that bad! I won't go in to the details of the why and how of CRC routines, if you want to know more about them then I suggest that you download "A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS" by Ross N. Williams. This may be found on the Ross Williams web site at http://www.ross.net. Select the CRC Pitstop icon. The upshot of all this is that I used a 32 bit CRC algorithm to create an 8 digit hexadecimal number as the Registration key. The problem with this is that it's just too easy. The string used to create the CRC is not obscured in any way, so I used the Delphi encryption routine I published on UNDU (More on Delphi Encryption) slightly modified to encrypt the data string, then, just to cause even more confusion to the string I appended the enciphered text to the end of the clear text and used this as the input to the CRC routine. I haven't tested this exhaustively but the testing I have done indicates that it is fairly secure especially as you cannot decrypt the registration key to get back to the clear text courtesy of the CRC routine and the fact that it is based on polynomial modulo maths. Now, before you chastise me for publishing my encryption routines and thus making them worthless, I may be an idiot, but I'm not stupid<g>. The routines above are different from the routines I actually use, but are sufficient to show the method. So, on to the routines. Notes. I use a prefix variable naming scheme as follows. Variables are named using "CamelCase" (sic) and one or two prefix characters to denote the scope and type. The type codes are:
All procedure and functions are prefixed with "SSL" to avoid clashes with other functions or procedures of the same name. This prefix has been registered with the Delphi Prefix Register. unit Registration;
{------------------------------------------------------------------------------}
interface
uses
SysUtils;
function Mod95
(
pnVal : Integer
): Integer; forward;
function SSLEncode
(
pcName : String;
pcCompany : String;
pcAppTitle : String;
pcMajor : String
): String; forward;
function SSLGenerateKey
(
pcName : String;
pcCompany : String;
pcApplication : String;
pcMajor : string
) : String; forward;
function SSLCrypt
(
pcString : PChar;
pnSeed : Integer = 0
) : String ; forward
function SSLCRCExecute
(
pcString : String
) : String ; forward;
var
gnKey : Integer;
gnSalt : Integer;
{------------------------------------------------------------------------------}
implementation
const
NULL_STRING = '';
const CRCTable : array [0..255] of LongWord =
( $00000000, $77073096, $ee0e612c, $990951ba,
$076dc419, $706af48f, $e963a535, $9e6495a3,
$0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988,
$09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91,
$1db71064, $6ab020f2, $f3b97148, $84be41de,
$1adad47d, $6ddde4eb, $f4d4b551, $83d385c7,
$136c9856, $646ba8c0, $fd62f97a, $8a65c9ec,
$14015c4f, $63066cd9, $fa0f3d63, $8d080df5,
$3b6e20c8, $4c69105e, $d56041e4, $a2677172,
$3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b,
$35b5a8fa, $42b2986c, $dbbbc9d6, $acbcf940,
$32d86ce3, $45df5c75, $dcd60dcf, $abd13d59,
$26d930ac, $51de003a, $c8d75180, $bfd06116,
$21b4f4b5, $56b3c423, $cfba9599, $b8bda50f,
$2802b89e, $5f058808, $c60cd9b2, $b10be924,
$2f6f7c87, $58684c11, $c1611dab, $b6662d3d,
$76dc4190, $01db7106, $98d220bc, $efd5102a,
$71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433,
$7807c9a2, $0f00f934, $9609a88e, $e10e9818,
$7f6a0dbb, $086d3d2d, $91646c97, $e6635c01,
$6b6b51f4, $1c6c6162, $856530d8, $f262004e,
$6c0695ed, $1b01a57b, $8208f4c1, $f50fc457,
$65b0d9c6, $12b7e950, $8bbeb8ea, $fcb9887c,
$62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65,
$4db26158, $3ab551ce, $a3bc0074, $d4bb30e2,
$4adfa541, $3dd895d7, $a4d1c46d, $d3d6f4fb,
$4369e96a, $346ed9fc, $ad678846, $da60b8d0,
$44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9,
$5005713c, $270241aa, $be0b1010, $c90c2086,
$5768b525, $206f85b3, $b966d409, $ce61e49f,
$5edef90e, $29d9c998, $b0d09822, $c7d7a8b4,
$59b33d17, $2eb40d81, $b7bd5c3b, $c0ba6cad,
$edb88320, $9abfb3b6, $03b6e20c, $74b1d29a,
$ead54739, $9dd277af, $04db2615, $73dc1683,
$e3630b12, $94643b84, $0d6d6a3e, $7a6a5aa8,
$e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1,
$f00f9344, $8708a3d2, $1e01f268, $6906c2fe,
$f762575d, $806567cb, $196c3671, $6e6b06e7,
$fed41b76, $89d32be0, $10da7a5a, $67dd4acc,
$f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5,
$d6d6a3e8, $a1d1937e, $38d8c2c4, $4fdff252,
$d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b,
$d80d2bda, $af0a1b4c, $36034af6, $41047a60,
$df60efc3, $a867df55, $316e8eef, $4669be79,
$cb61b38c, $bc66831a, $256fd2a0, $5268e236,
$cc0c7795, $bb0b4703, $220216b9, $5505262f,
$c5ba3bbe, $b2bd0b28, $2bb45a92, $5cb36a04,
$c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d,
$9b64c2b0, $ec63f226, $756aa39c, $026d930a,
$9c0906a9, $eb0e363f, $72076785, $05005713,
$95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38,
$92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21,
$86d3d2d4, $f1d4e242, $68ddb3f8, $1fda836e,
$81be16cd, $f6b9265b, $6fb077e1, $18b74777,
$88085ae6, $ff0f6a70, $66063bca, $11010b5c,
$8f659eff, $f862ae69, $616bffd3, $166ccf45,
$a00ae278, $d70dd2ee, $4e048354, $3903b3c2,
$a7672661, $d06016f7, $4969474d, $3e6e77db,
$aed16a4a, $d9d65adc, $40df0b66, $37d83bf0,
$a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9,
$bdbdf21c, $cabac28a, $53b39330, $24b4a3a6,
$bad03605, $cdd70693, $54de5729, $23d967bf,
$b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94,
$b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d
);
{------------------------------------------------------------------------------}
function SSLEncode
(
pcName : String;
pcCompany : String;
pcAppTitle : String;
pcMajor : String
): String;
begin
{ Encrypt the data using the major version number to seed the encryption
routine }
SSLCrypt( NULL_STRING, StrToIntDef( pcMajor, 0 ));
SSLCrypt( PChar( pcAppTitle ));
SSLCrypt( PChar( pcName ));
Result := SSLCrypt( PChar( pcCompany ));
end;
{------------------------------------------------------------------------------}
function SSLCrypt
(
pcString : PChar;
pnSeed : Integer = 0
) : String ;
var
nStringLen : Integer;
nCharCount : Integer;
nInterim : Byte;
nChar : Byte;
cChar : Char;
begin
nStringLen := length( pcString );
if nStringLen = 0 then begin
gnKey := pnSeed;
gnSalt := pnSeed * 100;
end
else begin
Result := '';
for nCharCount := 0 to nStringLen - 1 do begin
cChar := pcString[ nCharCount ];
{ only encipher printable characters }
if (( cChar >= ' ' ) and ( cChar <= '~' )) then begin
gnkey := ( gnkey and $1FFFFFFF ) xor (( gnkey shr 29 ) and $31 );
nChar := Byte( cChar );
nInterim := Mod95(( gnKey div 95 ) - ( nChar - 32 )) + 32;
Inc( gnSalt );
if ( gnSalt >= 20857 ) then begin
gnSalt := 0;
end;
gnKey := gnKey + gnKey + ( nInterim xor nChar ) + gnSalt;
Result := Result + Char( nInterim );
end;
end;
end;
end;
{------------------------------------------------------------------------------}
function Mod95
(
pnVal : Integer
): Integer;
begin
Result := pnVal;
while ( Result >= 9500 ) do begin
Result := Result - 9500;
end;
while ( Result >= 950 ) do begin
Result := Result - 950;
end;
while ( Result >= 95 ) do begin
Result := Result - 95;
end;
while ( Result < 0 ) do begin
Result := Result + 95;
end;
end;
{------------------------------------------------------------------------------}
function SSLGenerateKey
(
pcName : String;
pcCompany : String;
pcApplication : String;
pcMajor : string
) : String;
var
cKey : String;
begin
cKey
:= pcName + pcCompany + pcApplication + pcMajor
+ SSLEncode( pcName, pcCompany, pcApplication, pcMajor );
Result := SSLCRCExecute( cKey );
end;
{------------------------------------------------------------------------------}
function SSLCRCExecute
(
pcString : String
) : String ;
var
nX : Integer;
nCRCVal : LongWord;
begin
nCRCVal := 0;
for nX := 1 to length( pcString ) do begin
nCRCVal
:= CRCTable[ Byte( nCRCVal xor Byte( pcString[ nX ])) ]
xor (( nCRCVal shr 8 ) and $00FFFFFF );
end;
Result := LowerCase( IntToHex( nCRCVal, 8 ));
end;
{------------------------------------------------------------------------------}
end.
I've created a
registration key generator to give an idea of how this works. Obviously,
you'll need something like this in order to generate the keys for your clients.
In order to use the registration key in your application, include the Registration.pas unit in your project and call the routines as required. The way I use them is to check the contents of an INI file for the name, company and registration key. If any of these are blank I modally show a form asking the user to enter the name, company and key. Then I calculate what the key should be and compare it to the entered version. If they all agree I save the data in the INI file and continue the application. thereafter, all references to the name and company are taken from the values retrieved. If the values in the INI file are not blank II use these to calculate what the key should be as above. |
|
|
Copyright © 2007 undu.com Powered by Engineer Partner The One Stop Outsource
|