Scenario
I've written a simple keylogger using the modern RawInput technique registering the desired device for event/data interception.
About Raw Input
Then, I'm using basically all these Windows API member definitions:
Raw Input Functions
Raw Input Structures
Problem/Question
I'm using a non-English keyboard with a non-English O.S, then, my problem begins when I try to parse an special key of this keyboard like a ñ/Ñ character which is recognized as an System.Windows.Forms.Keys.OemTilde key,
or a ç/Ç character which is recognized as an System.Windows.Forms.Keys.OemQuestion key.
I would like to make my keylogger lenguage-specific aware (or at least, with proper character recognition for my current culture, es-ES), but I'm stuck because lack of knowledges to start retrieving properlly those characters.
Please, note that my intention is to learn how I can do it in an efficient/automated way like the O.S does with my keyboard when I press an Ñ character it types that Ñ, what I mean is that I'm totally aware of a solution that implies to perform a manual parsing of special characters like for example this:
Select Case MyKey
Case Keys.OemTilde
char = "ñ"c
End Select
That is not the behavior that I'm looking for, but I can understand that maybe I need additional "things" to reproduce a good recognition/translation of those chars for each kind of keayborad, but what "things" I need?.
Research
I'm not sure how to proceed, because as I said, I don't have the knowledges to know the answer to this problem (that's why I'm asking), but I imagine that the knowledge of the current keyboard layout will be involved, then, I know that I can retrieve the current keyboard layout with the CultureInfo.CurrentCulture.KeyboardLayoutId property.
I know that the keyboard layout for culture en-US is 1033, and for culture es-ES is 3082.
Also, note the documentation of the the MakeCode member of the RAWKEYBOARD structure, maybe it seems to be a hint for what I pretend to do, I don't know:
MakeCode
Type: USHORT
The scan code from the key depression.
The scan code for keyboard overrun is KEYBOARD_OVERRUN_MAKE_CODE.
but actually it is a guess work
Here is the code I found.
The correct solution is the ToUnicode WinAPI function:
[DllImport("user32.dll")]
public static extern int ToUnicode(uint virtualKeyCode, uint scanCode,
byte[] keyboardState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
StringBuilder receivingBuffer,
int bufferSize, uint flags);
static string GetCharsFromKeys(Keys keys, bool shift, bool altGr)
{
var buf = new StringBuilder(256);
var keyboardState = new byte[256];
if (shift)
keyboardState[(int) Keys.ShiftKey] = 0xff;
if (altGr)
{
keyboardState[(int) Keys.ControlKey] = 0xff;
keyboardState[(int) Keys.Menu] = 0xff;
}
WinAPI.ToUnicode((uint) keys, 0, keyboardState, buf, 256, 0);
return buf.ToString();
}
Console.WriteLine(GetCharsFromKeys(Keys.E, false, false)); // prints e
Console.WriteLine(GetCharsFromKeys(Keys.E, true, false)); // prints E
// Assuming British keyboard layout:
Console.WriteLine(GetCharsFromKeys(Keys.E, false, true)); // prints é
Console.WriteLine(GetCharsFromKeys(Keys.E, true, true)); // prints É
Related
I'm developing an online game which has to trade packets with a C++ server. I'm doing it using the Unity 5 engine. My life was getting hard when I started to write the packet structures in the C# code using Marshaling. Unity have serious bugs here, but ok for all of the Unity related bugs I'm used to implement any sort of workaround, but this bug I'm facing right now I think it could be some Marshal's limitation.
I have this C# struct:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MSG_SendNotice : IGamePacket
{
public const PacketFlag Opcode = (PacketFlag)1 | PacketFlag.Game2Client;
private PacketHeader m_Header;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 96)]
public string Text;
public PacketHeader Header { get { return m_Header; } }
}
It should works fine when calling Marshal.PtrToStructure. The problem is when some non-ascii character is sent on Text. The Marshal fails the conversion and assign null to Text. If I manually change this non-ascii character to any ascii character before converting the packet buffer the marshal works. The point is that I canno't format all the packets server-side to avoid send these non-ascii characters, I actually need them to display the correct string. Is there a way to set the encoding of this marshaled string (Text) in its definition?
A ny ideas are appreciated, thanks very much.
I would encode/decode the string manually:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)]
public byte[] m_Text;
public string Text
{
get
{
return m_Text != null ? Encoding.UTF8.GetString(m_Text).TrimEnd('\0') : string.Empty;
}
set
{
m_Text = Encoding.UTF8.GetBytes(value ?? string.Empty);
Array.Resize(ref m_Text, 96);
m_Text[95] = 0;
}
}
Note that on set I'm manually adding a final 0 terminator (m_Text[95] = 0) to be sure the string will be a correctly-terminated C string.
I've done a test: it works even in the case that m_Text is null.
I'm working with the Google DoubleClick ad exchange API. Their examples are in C++ and well, I'm pretty awful at C++. I'm trying to convert this to C# for something I'm working on and really, I think I just need some explanation of what is actually happening in certain blocks of this code sample. Honestly I kind of know what should happen over all but I'm not sure I am getting it 'right' and with encryption/decryption there isn't a 'sort of right'.
This is the full example from their API site:
bool DecryptByteArray(
const string& ciphertext, const string& encryption_key,
const string& integrity_key, string* cleartext) {
// Step 1. find the length of initialization vector and clear text.
const int cleartext_length =
ciphertext.size() - kInitializationVectorSize - kSignatureSize;
if (cleartext_length < 0) {
// The length can't be correct.
return false;
}
string iv(ciphertext, 0, kInitializationVectorSize);
// Step 2. recover clear text
cleartext->resize(cleartext_length, '\0');
const char* ciphertext_begin = string_as_array(ciphertext) + iv.size();
const char* const ciphertext_end = ciphertext_begin + cleartext->size();
string::iterator cleartext_begin = cleartext->begin();
bool add_iv_counter_byte = true;
while (ciphertext_begin < ciphertext_end) {
uint32 pad_size = kHashOutputSize;
uchar encryption_pad[kHashOutputSize];
if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
encryption_key.length(), (uchar*)string_as_array(iv),
iv.size(), encryption_pad, &pad_size)) {
printf("Error: encryption HMAC failed.\n");
return false;
}
for (int i = 0;
i < kBlockSize && ciphertext_begin < ciphertext_end;
++i, ++cleartext_begin, ++ciphertext_begin) {
*cleartext_begin = *ciphertext_begin ^ encryption_pad[i];
}
if (!add_iv_counter_byte) {
char& last_byte = *iv.rbegin();
++last_byte;
if (last_byte == '\0') {
add_iv_counter_byte = true;
}
}
if (add_iv_counter_byte) {
add_iv_counter_byte = false;
iv.push_back('\0');
}
}
Step 1 is quite obvious. This block is what I am really not sure how to interpret:
if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
encryption_key.length(), (uchar*)string_as_array(iv),
iv.size(), encryption_pad, &pad_size)) {
printf("Error: encryption HMAC failed.\n");
return false;
}
What exactly is happening in that if body? What would that look like in C#? There are a lot of parameters that do SOMETHING but it seems like an awful lot crammed in a small spot. Is there some stdlib HMAC class? If I knew more about that I might better understand what's happening.
The equivalent C# code for that block is:
using (var hmac = new HMACSHA1(encryption_key))
{
var encryption_pad = hmac.ComputeHash(iv);
}
It's computing the SHA1 HMAC of the initialization vector (IV), using the given encryption key.
The HMAC function is actually a macro from OpenSSL.
Just as a comment, I think it would be easier to implement this from their pseudocode description rather than from their C++ code.
This is my first attempt in using a card reader in C#, or basically anywhere.
I use ACS ACR122U PICC Interface 0 reader in Windows 7 64bit.
My first problem occurs when I tried to connect to the reader using
ModWinsCard.SCardConnect(hContext, cbReader.SelectedItem.ToString(), ModWinsCard.SCARD_SHARE_DIRECT, 0, ref hCard, ref Protocol);
It returns error code 6, but I googled and solved it by changing project's platform from Any CPU to X86.
Right after that I bumped to another issue, this time in controlling the reader.
I tried with :
_sentBuffer = new byte[]
{
0xFF,
0x00,
0x48,
0x00,
0x00
};
_receivedBuffer = new byte[10];
_receivedBuffer[0] = 0;
_returnCode = ModWinsCard.SCardControl(_hCard, _dwControlCode, ref _sentBuffer[0], _sentBuffer.Length, ref _receivedBuffer[0], _receivedBuffer.Length, ref bytesReturned);
The returned code is 1, which is weird because I can't found it in the documentation.
Really need a hand in this.
Thanks !
Doing some research myself about working with SCardControl and found I was getting the same return value of 1.
I found a list of Error Codes here which then states the below.
"Note Some return values may have the same value as existing Windows return values that signify a similar condition. For information about error codes not listed here, see System Error Codes."
And that documentation states that the error code value 1 is ERROR_INVALID_FUNCTION
I know this question is old but hopefully it will help someone in the future.
I somehow have it solved by downloading the latest driver from the provider's website, with uninstalling the driver that is included in the driver CD.
Still wonder what does return 1 means though..
I know this is an old topic, but I have the same problem on Windows 10 (x64), except I'm using VB6.
I have a lot working through using SCardTransmit etc, I can controle/read/write if there's a card on it. But I want to control the reader (ACR112U) without a card (turn off autodetection/beep) and that's only possible using SCardControl (as far as I know), cause when I connect with Directmode and then call our SetBuzzerCardDetection to turn it off, I get a ERROR_BAD_COMMAND, but when shared and a card it works.
Even the example from ACS themselves with IOCTL_GET_VERSIONS gives me return value ERROR_INVALID_FUNCTION
Dim vcVersion As VERSION_CONTROL
Dim lReturnedLength As Long
m_lResult = SCardControl(m_hCard, _
IOCTL_GET_VERSIONS, _
0, 0, _
vcVersion, 20, _
lReturnedLength)
If m_lResult <> SCARD_S_SUCCESS Then
I'm using the following
Declare Function SCardControl Lib "WinScard.dll" ( ByVal hCard As Long, ByVal dwControlCode As Long, ByRef lpInBuffer As Any, ByVal lSizeofBuffer As Long, ByRef lpReceiveBuffer As Any, ByVal lpReceiveBufferSize As Long, ByRef lpBytesReturned As Long) As Long
Const IOCTL_CCID_ESCAPE As Long = (&H42000000 + 3500)
'hCard is a valid handle returned by SCardConnect
'lReturn = SCardConnect(hContext, sReader, eShareMode, ePreferredProtocol, hCard, eActiveProtocol)
'With card on reader: eShareMode = SCARD_SHARE_SHARED, ePrefferedProtocol = SCARD_PROTOCOL_Tx
'Without card on reader: eShareMode = SCARD_SHARE_DIRECT, ePrefferedProtocol = SCARD_PROTOCOL_UNDEFINED
Dim abIn() As Byte
Dim abOut() As Byte
Dim lInLength As Long
Dim lOutLength As Long
Dim lReturn As Long
Dim lReturnedLength As Long
'Command for turning off Buzzer output during card detection
lInLength = 5
ReDim abIn(lInLength - 1)
abIn(0) = &HFF
abIn(1) = &H0
abIn(2) = &H52
abIn(3) = &H0
abIn(4) = &H0
lOutLength = 256
ReDim abOut(lOutLength - 1)
lReturn = SCardControlAny(hCard, _
IOCTL_CCID_ESCAPE, _
abIn(0), lInLength, _
abOut(0), lOutLength, _
lReturnedLength)
'with card on reader: lReturn = ERROR_BAD_COMMAND
'without card on reader: lReturn = ERROR_INVALID_FUNCTION
Mind you for using Escape I also added the ControlEscapeEnable to the registry (just for the sake of it I added it in several places)
First I did everything without loading a special driver (other than what Windows 10 itself already got), but due to Samuel Adam's remark I loaded the latest from ACS website but it didn't make a difference.
It's really driving me crazy, just like it's also driving me crazy that WebUSB doesn't allow it anymore so I would have been able to use a webbased version.
I'm sure there must be some documentation on MSDN somewhere, but I couldn't find it. It looks like some subset/variation of JSON. Really, this question grew out of something that has always bugged me: what do all the 8:s and 3:s mean? Is this some a version number of some kind? Maybe a typing scheme? Every VDPROJ excerpt I've ever seen is filled with these "eight-colon" and "three-colon" prefixes, but this is not the sort of question search engines are really good for.
"DeployProject"
{
"VSVersion" = "3:800"
"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}"
"IsWebType" = "8:FALSE"
"ProjectName" = "8:ProjectNameRedacted"
"LanguageId" = "3:1033"
"CodePage" = "3:1252"
"UILanguageId" = "3:1033"
"SccProjectName" = "8:"
"SccLocalPath" = "8:"
"SccAuxPath" = "8:"
"SccProvider" = "8:"
"Hierarchy"
{
"Entry"
{
"MsmKey" = "8:_02F97BB7BD104F1AAA1C97C854D5DC99"
"OwnerKey" = "8:_UNDEFINED"
"MsmSig" = "8:_UNDEFINED"
}
...
If anyone just wants to berate my pitiful Google-fu, that's fine too.
As #R. Matveev pointed out, the prefix numbers likely indicate the type of data stored in the property. This would be useful when deserializing the file into an object structure.
I doubt the source code which Visual Studio used to read/write the files was ever made open source, so it's no wonder that web searches returned nothing.
The best I could find was this page on OLE Automation data types, which may not have been the actual constants, but the data types seem to match the values in the *.vdproj file.
2.2.7 VARIANT Type Constants
typedef enum tagVARENUM
{
VT_EMPTY = 0x0000,
VT_NULL = 0x0001,
VT_I2 = 0x0002,
VT_I4 = 0x0003, // 4-byte signed integer
VT_R4 = 0x0004,
VT_R8 = 0x0005,
VT_CY = 0x0006,
VT_DATE = 0x0007,
VT_BSTR = 0x0008, // BSTR (string data)
VT_DISPATCH = 0x0009,
VT_ERROR = 0x000A,
VT_BOOL = 0x000B, // Boolean value
VT_VARIANT = 0x000C,
VT_UNKNOWN = 0x000D
...
} VARENUM;
I have devices with unique serial number (string incremetation) ex : AS1002 and AS1003.
I need to figure out an algorithm to produce a unique activation key for each serial number.
What would be the best approach for this ?
Thanks !
(This has to be done offline)
You have two things to consider here:
- Whatever key you generate must be able to be entered easily, so this eliminates some weird hash which may produce characters which will be cumbersome to type, although this can be overcome, it’s something you should consider.
- The operation as you stated must be done online
Firstly, there will be no way to say with absolute certainty that someone will not be able to decipher your key generation routine, no matter how much you attempt to obfuscate. Just do a search engine query for “Crack for Xyz software”.
This has been a long battle that will never end, hence the move to deliver software as services, i.e. online where the producer has more control over their content and can explicitly authorize and authenticate a user. In your case you want to do this offline. So in your scenario someone will attach your device to some system, and the accompanying software that you intend to write this routine on will make a check against the serial number of the device v/s user input.
Based on #sll’s answer, given the offline nature of your request. Your best, unfortunately would be to generate a set of random codes, and validate them when user’s call in.
Here is a method borrowed from another SO answer, I've added digits as well
private readonly Random _rng = new Random();
private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; //Added 1-9
private string RandomString(int size)
{
char[] buffer = new char[size];
for (int i = 0; i < size; i++)
{
buffer[i] = _chars[_rng.Next(_chars.Length)];
}
return new string(buffer);
}
So, generating one for each of your devices and storing them somewhere might be your only option because of the offline considerations.
This routine will produce strings like this when set to create a 10 digit string, which is reasonably random.
3477KXFBDQ
ROT6GRA39O
40HTLJPFCL
5M2F44M5CH
CAVAO780NR
8XBQ44WNUA
IA02WEWOCM
EG11L4OGFO
LP2UOGKKLA
H0JB0BA4NJ
KT8AN18KFA
Activation Key
Here is a simple structure of the activation key:
Part
Description
Data
A part of the key encrypted with a password. Contains the key expiration date and application options.
Hash
Checksum of the key expiration date, password, options and environment parameters.
Tail
The initialization vector that used to decode the data (so-called "salt").
class ActivationKey
{
public byte[] Data { get; set; } // Encrypted part.
public byte[] Hash { get; set; } // Hashed part.
public byte[] Tail { get; set; } // Initialization vector.
}
The key could represent as text format: DATA-HASH-TAIL.
For example:
KCATBZ14Y-VGDM2ZQ-ATSVYMI.
The folowing tool will use cryptographic transformations to generate and verify the key.
Generating
The algorithm for obtaining a unique activation key for a data set consists of several steps:
data collection,
getting the hash and data encryption,
converting activation key to string.
Data collection
At this step, you need to get an array of data such as serial number, device ID, expiration date, etc. This purpose can be achieved using the following
method:
unsafe byte[] Serialize(params object[] objects)
{
using (MemoryStream memory = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(memory))
{
foreach (object obj in objects)
{
if (obj == null) continue;
switch (obj)
{
case string str:
if (str.Length > 0)
writer.Write(str.ToCharArray());
continue;
case DateTime date:
writer.Write(date.Ticks);
continue;
case bool #bool:
writer.Write(#bool);
continue;
case short #short:
writer.Write(#short);
continue;
case ushort #ushort:
writer.Write(#ushort);
continue;
case int #int:
writer.Write(#int);
continue;
case uint #uint:
writer.Write(#uint);
continue;
case long #long:
writer.Write(#long);
continue;
case ulong #ulong:
writer.Write(#ulong);
continue;
case float #float:
writer.Write(#float);
continue;
case double #double:
writer.Write(#double);
continue;
case decimal #decimal:
writer.Write(#decimal);
continue;
case byte[] buffer:
if (buffer.Length > 0)
writer.Write(buffer);
continue;
case Array array:
if (array.Length > 0)
foreach (var a in array) writer.Write(Serialize(a));
continue;
case IConvertible conv:
writer.Write(conv.ToString(CultureInfo.InvariantCulture));
continue;
case IFormattable frm:
writer.Write(frm.ToString(null, CultureInfo.InvariantCulture));
continue;
case Stream stream:
stream.CopyTo(stream);
continue;
default:
try
{
int rawsize = Marshal.SizeOf(obj);
byte[] rawdata = new byte[rawsize];
GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
writer.Write(rawdata);
handle.Free();
}
catch(Exception e)
{
// Place debugging tools here.
}
continue;
}
}
writer.Flush();
byte[] bytes = memory.ToArray();
return bytes;
}
}
Getting the hash and data encryption
This step contains the following substeps:
create an encryption engine using a password and stores the initialization vector in the Tail property.
next step, expiration date and options are encrypted and the encrypted data is saved into the Data property.
finally, the hashing engine calculates a hash based on the expiration date, password, options and environment and puts it in the Hash property.
ActivationKey Create<TAlg, THash>(DateTime expirationDate,
object password,
object options = null,
params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
ActivationKey activationKey = new ActivationKey();
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
if (password == null)
{
password = new byte[0];
}
activationKey.Tail = cryptoAlg.IV;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), activationKey.Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
expirationDate = expirationDate.Date;
long expirationDateStamp = expirationDate.ToBinary();
using (ICryptoTransform transform = cryptoAlg.CreateEncryptor())
{
byte[] data = Serialize(expirationDateStamp, options);
activationKey.Data = transform.TransformFinalBlock(data, 0, data.Length);
}
using (HashAlgorithm hashAlg = Activator.CreateInstance<THash>())
{
byte[] data = Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
activationKey.Tail);
activationKey.Hash = hashAlg.ComputeHash(data);
}
}
return activationKey;
}
Converting to string
Use the ToString method to get a string containing the key text, ready to be transfering to the end user.
N-based encoding (where N is the base of the number system) was often used to convert binary data into a human-readable text. The most commonly used in
activation key is base32. The advantage of this encoding is a large alphabet consisting of numbers and letters that case insensitive. The downside is that this encoding is not implemented in the .NET standard library and you should implement it yourself. You can also use the hex encoding and base64 built into mscorlib. In my example base32 is used, but I will not give its source code here. There are many examples of base32 implementation on this site.
string ToString(ActivationKey activationKey)
{
if (activationKey.Data == null
|| activationKey.Hash == null
|| activationKey.Tail == null)
{
return string.Empty;
}
using (Base32 base32 = new Base32())
{
return base32.Encode(activationKey.Data)
+ "-" + base32.Encode(activationKey.Hash)
+ "-" + base32.Encode(activationKey.Tail);
}
}
To restore use the folowing method:
ActivationKey Parse(string text)
{
ActivationKey activationKey;
string[] items = text.Split('-');
if (items.Length >= 3)
{
using (Base32 base32 = new Base32())
{
activationKey.Data = base32.Decode(items[0]);
activationKey.Hash = base32.Decode(items[1]);
activationKey.Tail = base32.Decode(items[2]);
}
}
return activationKey;
}
Checking
Key verification is carried out using methodes GetOptions an Verify.
GetOptions checks the key and restores embeded data as byte array or null if key is not valid.
Verify just checks the key.
byte[] GetOptions<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
if (Data == null || Hash == null || Tail == null)
{
return null;
}
try
{
using (SymmetricAlgorithm cryptoAlg = Activator.CreateInstance<TAlg>())
{
cryptoAlg.IV = Tail;
using (DeriveBytes deriveBytes =
new PasswordDeriveBytes(Serialize(password), Tail))
{
cryptoAlg.Key = deriveBytes.GetBytes(cryptoAlg.KeySize / 8);
}
using (ICryptoTransform transform = cryptoAlg.CreateDecryptor())
{
byte[] data = transform.TransformFinalBlock(Data, 0, Data.Length);
int optionsLength = data.Length - 8;
if (optionsLength < 0)
{
return null;
}
byte[] options;
if (optionsLength > 0)
{
options = new byte[optionsLength];
Buffer.BlockCopy(data, 8, options, 0, optionsLength);
}
else
{
options = new byte[0];
}
long expirationDateStamp = BitConverter.ToInt64(data, 0);
DateTime expirationDate = DateTime.FromBinary(expirationDateStamp);
if (expirationDate < DateTime.Today)
{
return null;
}
using (HashAlgorithm hashAlg =
Activator.CreateInstance<THash>())
{
byte[] hash =
hashAlg.ComputeHash(
Serialize(expirationDateStamp,
cryptoAlg.Key,
options,
environment,
Tail));
return ByteArrayEquals(Hash, hash) ? options : null;
}
}
}
}
catch
{
return null;
}
}
bool Verify<TAlg, THash>(object password = null, params object[] environment)
where TAlg : SymmetricAlgorithm
where THash : HashAlgorithm
{
try
{
byte[] key = Serialize(password);
return Verify<TAlg, THash>(key, environment);
}
catch
{
return false;
}
}
Example
Here is a full example of generating the activation key using your own combination of any amount of data - text, strings, numbers, bytes, etc.
Example of usage:
string serialNumber = "0123456789"; // The serial number.
const string appName = "myAppName"; // The application name.
// Generating the key. All the parameters passed to the costructor can be omitted.
ActivationKey activationKey = new ActivationKey(
//expirationDate:
DateTime.Now.AddMonths(1), // Expiration date 1 month later.
// Pass DateTime.Max for unlimited use.
//password:
null, // Password protection;
// this parameter can be null.
//options:
null // Pass here numbers, flags, text or other
// that you want to restore
// or null if no necessary.
//environment:
appName, serialNumber // Application name and serial number.
);
// Thus, a simple check of the key for validity is carried out.
bool checkKey = activationKey.Verify((byte[])null, appName, serialNumber);
if (!checkKey)
{
MessageBox.Show("Your copy is not activated! Please get a valid activation key.");
Application.Exit();
}
By far the most secure way to do it is to have a centralized database of (serial number, activation key) pairs and have the user activate over the internet so you can check the key locally (on the server).
In this implementation, the activation key can be completely random since it doesn't need to depend on the serial number.
You want it to be easy to check, and hard to "go backwards". You'll see a lot of suggestions for using hashing functions, those functions are easy to go one way, but hard to go backwards. Previously, I phrased that as "it is easy to turn a cow into a hamburger, but hard to turn a hamburger into a cow". In this case, a device should know its own serial number and be able to "add" (or append) some secret (usually called "salt") to the serial and then hash or encrypt it.
If you are using reversible encryption, you want to add some sort of "check digit" to the serial numbers so that if someone does figure your encryption scheme out, there is another layer for them to figure out.
An example of a function that is easy enough to "go backwards" was one I solved with Excel while trying to avoid homework.
And you probably want to make things easier for your customers by making the encoding less likely to be messed up when the activation codes are handwritten (such as you write it down from the email then walk over to where the device is and punch the letters/digits in). In many fonts, I and 1, and 0 and O are similar enough that many encodings, such as your car's VIN do not use the letters i and o (and I remember older typewriters that lacked a key for the digit 1 because you were expected to use lowercase L). In such cases, Y, 4 and 7 can appear the same depending on some handwriting. So know your audience and what are their limits.
If your device has some secured memory which can not be read by connecting an programmator or an other device -you can store some key-code and then use any hashing algorithm like MD5 or SHA-1/2 to generate hash by:
HASH(PUBLIC_SERIALNUMBER + PRIVATE_KEYCODE)
And pairs of SERIALNUMBER + KEYCODE should be stored in local DB.
In this way: (offline)
Client calling you and asking for the Activation Code
You asking for a SERIALNUMBER of particular device
Then you search for a KEYCODE by a given SERIALNUMBER in your local DB and generate Activation Code (even using MD5 this will be sacure as long KEYCODE is privately stored in your DB)
Client enter Activation Code into the device, device able to generate hash
by own SERIALNUMBER and KEYCODE and then compare to Activation Code entered by user
This could be simplified by storing activation code itself if device has a secured memory onboard (like SmartCards has). In this way you can just keep own database of SerialCode - ActivationCode pairs.
How about: Invent a password that is not revealed to the user. Then concatenate this password with the serial number and hash the combination.
Anything you do can be broken by a dedicated enough hacker. The question is not, "Can I create absolutely unbreakable security?" but "Can I create security good enough to protect against unskilled hackers and to make it not worth the effort for the skilled hackers?" If you reasonably expect to sell 10 million copies of your product, you'll be a big target and there may be lots of hackers out there who will try to break it. If you expect to sell a few hundred or maybe a few thousand copies, not so much.