Product Keys Based on the Advanced Encryption Standard (AES)

Introduction

A popular method of product validation is using keys similar to VJJJBX-H2BBCC-68CF7F-2BXD4R-3XP7FB-JDVQBC. These compact keys can be derived using Symmetric Key Cryptosystems such as the Advanced Encryption Standard (AES).

Other Public Key Cryptosystems are available such as RSA. However, these systems generally produce larger keys (which the user will eventually have to enter into the program to unlock functionality). Smaller producing Cryptosystems exist, but it is the author's opinion that they are highly encumbered with patents. Quartz is one such example. It is a Public Key Encryption System that produces a smaller cipher text based on Hidden Field Equations (HFEs). The Quartz website is littered with phrases such as "must license" and "pay royalties".

This article will use AES (specified in FIPS 197) as the Cryptosystem, and Wei Dai's Crypto++ for AES operations. AES will produce compact keys with the additional benefit that the cryptosystem is not burdened with patent compliance. However, should a binary fall to Reverse Engineering, the key will become compromised (note that AES is a Symmetric Cipher - not an Asymmetric Cipher which has Public and Private keys).

The reader is also encouraged to investigate Signature Schemes (with Recovery) as an alternative method to producing Product Keys. An example is PSS-R, a Message Recovery Signature Scheme based on RSA. PSS-R is proposed in ANSI X9.31 (reopened by the ANSI X9F working group) and IEEE 1363. The reader should also visit Product Keys Based on Elliptic Curve Cryptography to familiarize themselves with basic concepts of Product Keys in the domain of Public Key Cryptography.

This article will discuss the following topics:

  • Advanced Encryption Standard
  • Compiling and Integrating Crypto++ into the Visual C++ Environment
  • AES Implementation in Crypto++
  • Base Encoding a Cipher Text String in Crypto++
  • Bulk Product Key Generation
  • Product Key Validation

This article is based on the Visual C++ 6.0 Environment in hopes that it reaches the largest audience.

Advanced Encryption Standard

Currently, there are three FIPS-approved symmetric encryption algorithms: AES, Triple DES, and Skipjack. The article presented uses AES or the Advanced Encryption Standard in CBC Mode. Note that DES (FIPS 46-3) was withdrawn in May 2005, and is no longer approved for Federal use.

AES (or Rijndeal - pronounced "Rhine dahl") is the work of Joan Daemen and Vincent Rijmen - hence the portmanteau Rijndael. AES is a 128 bit block cipher that accepts key lengths of 128, 192, and 256 bits. The required number of rounds (i.e., linear and non-linear transformations), depend on the key size. Below are the FIPS 197 conformant Key-Block-Round combinations.

Taking from FIPS 197:

For both its Cipher and Inverse Cipher, the AES algorithm uses a round function that is composed of four different byte-oriented transformations: 1) byte substitution using a substitution table (S-box), 2) shifting rows of the State array by different offsets, 3) mixing the data within each column of the State array, and 4) adding a Round Key to the State. These transformations (and their inverses) are described in Sections 5.1.1-5.1.4 and 5.3.1-5.3.4.

Compiling and Integrating Crypto++ into the Microsoft Visual C++ Environment

Please see the related article, Compiling and Integrating Crypto++ into the Microsoft Visual C++ Environment. This article is based upon basic assumptions presented in the previously mentioned article. It also addresses most problems encountered with projects from Command Line to MFC (Errors C1083, C1189, LNK1104, LNK2001, and LNK2005). Additionally, it provides some tips and other nicities for using the Crypto++ Library.

For those who are interested in other C++ Number Theoretic libraries, please see Peter Gutmann's Cryptlib or Victor Shoup's NTL.

Product Keys Based on the Advanced Encryption Standard (AES)

AES Implementation in Crypto++

The first step in developing the system is to demonstrate AES in Crypto++. These samples will present an alternate (and more elegant) method than that which was presented in An AES Encrypting Registry Class.

Above is the result ot running aestest1. Below is the source code. Following the source code is a brief explaination with respect to the Crypto++ Library.

// From aestest1.cpp

// Runtime Includes
#include <iostream>
#include <iomanip>

// Crypto++ Includes
#include "cryptlib.h"
#include "aes.h"        // AES
#include "modes.h"      // CBC_Mode< >
#include "filters.h"    // StringSource
 
int main(int argc, char* argv[]) {
    
    // Key and IV setup
    byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ], 
          iv[ CryptoPP::AES::BLOCKSIZE ];

    ::memset( key, 0x01, CryptoPP::AES::DEFAULT_KEYLENGTH );
    ::memset(  iv, 0x01, CryptoPP::AES::BLOCKSIZE );

    // Message M
    std::string PlainText = "Hello AES World";

    // Debug
    std::cout << "Plain Text:" << std::endl;
    std::cout << "  '" << PlainText << "'" << std::endl;
    std::cout << std::endl;

    // Cipher Text Sink
    std::string CipherText;

    // Encryption
    CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption
        Encryptor( key, sizeof(key), iv );

    CryptoPP::StringSource( PlainText, true,
        new CryptoPP::StreamTransformationFilter( Encryptor,
            new CryptoPP::StringSink( CipherText )
        ) // StreamTransformationFilter
    ); // StringSource
    
    // Debug
    std::cout << "Cipher Text (" << CipherText.size() <<") bytes:" << std::endl;
    for(unsigned int i = 0; i < CipherText.size(); i++ ) 
    {
        if( 0 != i && 10 == i ) { std::cout << std::endl; }
        std::cout << std::hex << "0x";
        std::cout << ( static_cast<unsigned>( 0xFF & CipherText[ i ] ) ) << " ";
    }   
    std::cout << std::endl << std::endl;

    ///////////////////////////////////////
    //                DMZ                //
    ///////////////////////////////////////

    // Recovered Text Sink
    std::string RecoveredText;

    // Decryption
    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption
        Decryptor( key, sizeof(key), iv );

    CryptoPP::StringSource( CipherText, true,
        new CryptoPP::StreamTransformationFilter( Decryptor,
            new CryptoPP::StringSink( RecoveredText )
        ) // StreamTransformationFilter
    ); // StringSink

    // Debug
    std::cout << "Recovered Text:" << std::endl;
    std::cout << "  '" << RecoveredText << "'" << std::endl;
    std::cout << std::endl;
   
    return 0;
}

Product Keys Based on the Advanced Encryption Standard (AES)

AES Implementation in Crypto++ (continued)

Example 1 is quite busy. First, the program sets up a Key and IV. All Crypto++ Symmetric Ciphers define a value for DEFAULT_KEYLENGTH and BLOCKSIZE. The Key and IV is then Initialized.

::memset( key, 0x01, CryptoPP::BLOCK_CIPHER::DEFAULT_KEYLENGTH );
::memset(  iv, 0x01, CryptoPP::BLOCK_CIPHER::BLOCKSIZE );
In later examples, the Key and IV will be initialized to a pseudo random value. It is noteworthy that the same Key, IV, and Mode must be used to decrypt the cipher text that was used to encrypt the plain text.

The next noteworthy piece of code follows. After executing, the encryption object is now ready for use.

CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption Encryptor( key, sizeof(key), iv );

A StingSource is created. The string source will take the string, and push it into the StreamTransformationFilter. Later, the code will deomnstrate filter chaining.

CryptoPP::StringSource( PlainText, true,
   new CryptoPP::StreamTransformationFilter( Encryptor,
      new CryptoPP::StringSink( CipherText )
   )
);

The StringSource being used is as follows:

StringSource (const char *string, bool pumpAll, BufferedTransformation *attachment=NULL)

It should be logically observerd as follows as depicted below.

Crypto++ provides multiple Sources to use:

  • FileSource
  • SocketSource
  • NetworkSource
  • WindowsPipeSource

Just as the Source is the origin of the data, the Sink is the destination endpoint of the data. Note that even though a std::string is used, one should not view the string classically. A better choice of abstractions is a rope. Below is the logical diagrams of a StringSink.

Crypto++ provides multiple Sources to use:

  • FileSink
  • SocketSink
  • NetworkSink
  • WindowsPipeSink

The remaining item to discuss it the transformation, depicted below.

BufferedTransformation is used as an example because it is the base class of all transforms. Data is recieved from a previous transform (or a Source), the data is operated upon, and the data is passed out (to another transform or a Sink). The final view of the process is shown below.

The "Identity" transformation would be viewed as follows (though this is a valid Crypto++ construct, it is not very useful).

// NULL Transformation
CryptoPP::StringSource( source, true,
    new CryptoPP::StringSink( sink )
);

The remaining parameter to a Source is pumpAll. It is possible to chain Filters, each adding a small bit of proccessing of the data. if pumpAll is true, all filters are notified to act on the data (operating in succession depending on where they are in the Filter chain).

It is very noteworthy that the nameless objects created with new will be deleted by the hosting object when they are no longer needed (this is not the case when an object receives a Reference). So, in this example:

  • The StreamTransformationFilter will delete the StringSink object when no longer required
  • The StringSource will delete the StreamTransformationFilter object when no longer required
  • The cleanup process begins when the StringSource destructor is invoked

Decryption is simply reversing the process described above.

Product Keys Based on the Advanced Encryption Standard (AES)

AES Implementation in Crypto++ (continued)

The second sample provides a generalization for using Symmetric Ciphers in Crypto++ to the reader for convenience. By #define an appropriate cipher and mode, the reader may fully test the capabilities of Crypto++. aestest2 issues #include "BlockCiphers.h" which simply brings in the various Crypto++ header files required for a cipher selection. The following three examples display output from aestest2.

3DES in CFB Mode

 

RC6 in CTR Mode

 

Rijndael CBC Mode

Product Keys Based on the Advanced Encryption Standard (AES)

AES Implementation in Crypto++ (continued)

The various Ciphers and Modes inherit from common base classes (BlockCipherDocumentation or CipherModeDocumentation). The partial Inheritance Diagrams are below.

Cipher Mode Documentation Partial Inheritance Diagram

The use of the various XXX_Mode_ExternalCipher was presented in An AES Encrypting Registry Class. In the author's opinion, their use is less fashionable.

Block Documentation Partial Inheritance Diagram

Product Keys Based on the Advanced Encryption Standard (AES)

Base Encoding a Cipher Text String in Crypto++

Keeping in spirit of the Filter chaining paradigm, aestest3 uses a Base32 Encoder to produce a human readable cipher text string. Base32 Encoding expands the cipher text to 26 characters. The number 26 is derived as follow:

  • 15 or fewer plain text characters are encrypted to 16 cipher text characters
  • 16 (cipher text characters) * 1.6 (60% Base32 encoding expansion) = 25.6
26 characters do not group well. This will be addressed later in the article. BaseExp is available for download which demonstartes the base encoding expansion should the reader desire.

The note worhty addition to aestest3 is the following. Filter chaining was visited earlier in the document.

// Encryption
CryptoPP::StringSource( PlainText, true,
    new CryptoPP::StreamTransformationFilter( Encryptor,
        new CryptoPP::Base32Encoder(
            new CryptoPP::StringSink( EncodedText )
        ) // Base32Encoder
    ) // StreamTransformationFilter
); // StringSource

Decryption unwinds the process, in reverse order.

// Decryption
CryptoPP::StringSource( EncodedText, true,
    new CryptoPP::Base32Decoder(
        new CryptoPP::StreamTransformationFilter( Decryptor,
            new CryptoPP::StringSink( RecoveredText )
        )
    )
);

// From aestest3

// C Runtime Includes
#include <iostream>

// Crypto++ Includes
#include "cryptlib.h"
#include "Base32.h"
#include "aes.h"        // AES
#include "modes.h"      // CBC_Mode< >
#include "filters.h"    // StringSource and
                        // StreamTransformation
 
int main(int argc, char* argv[]) {
    
    // Key and IV setup
    byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ], 
          iv[ CryptoPP::AES::BLOCKSIZE ];

    ::memset( key, 0x01, CryptoPP::AES::DEFAULT_KEYLENGTH );
    ::memset(  iv, 0x01, CryptoPP::AES::BLOCKSIZE );

    // Encryptor
    CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption
        Encryptor( key, sizeof(key), iv );

    // Message M
    std::string PlainText = "Hello World";
    std::string EncodedText;
   
    // Encryption
    CryptoPP::StringSource( PlainText, true,
        new CryptoPP::StreamTransformationFilter( Encryptor,
            new CryptoPP::Base32Encoder(
                new CryptoPP::StringSink( EncodedText )
            ) // Base32Encoder
        ) // StreamTransformationFilter
    ); // StringSource

    ///////////////////////////////////////
    //                DMZ                //
    ///////////////////////////////////////

    // Recovered Text Sink
    std::string RecoveredText;

    // Decryption
    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption
        Decryptor( key, sizeof(key), iv );

    CryptoPP::StringSource( EncodedText, true,
        new CryptoPP::Base32Decoder(
            new CryptoPP::StreamTransformationFilter( Decryptor,
                new CryptoPP::StringSink( RecoveredText )
            ) // StreamTransformationFilter
        ) // Base32Decoder
    ); // StringSource

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////

    ...
   
    return 0;
}

Product Keys Based on the Advanced Encryption Standard (AES)

Bulk Product Key Generation

This portion of the article will address the formatting needs of the Product Key, and serve as proof of concept for the Key Generator. KeyAndIVGen is used to generate the AES Key and AES IV. Note this is different from the Product Key. This step is only required once per set of Product Keys.

In aestest4, each iteration of the loop changes the Encryption and Decryption object, so each object's state will have to be reset at each iteration:

std::string EncodedText = "";
std::string SaltText = "";
Encryptor.Resynchronize( iv );
Decryptor.Resynchronize( iv );

By envoking an alternate Base32Encoder constructor, one can form the Product Key using Crypto++. Note that there is no Base32Decoder equivalent. If the character encountered is not over the alphabet, it is silently consumed. Crypto++ currently implements the Differential Unicode Domain Encoding (DUDE) as specified in the IETF draft. Should DUDE not suffice the reader, he or she should research MACE: Modal ASCII Compatible Encoding for IDN; or implement their own Base32 Encoder.

CryptoPP::StringSource( PlainText, true,
    new CryptoPP::StreamTransformationFilter( Encryptor,
        new CryptoPP::Base32Encoder(
            new CryptoPP::StringSink( EncodedText ),
        true, 4, "-") // Base32Encoder
    ) // StreamTransformationFilter
); // StringSource

Unlike ECIES (which creates a Temporary Public Key V for each cipher text object), AES will require salt to randomize the keys.

4 bytes of random salt is added below. This changes the encryption operation as follows.

CryptoPP::RandomNumberSource( rng, 4, true,
    new CryptoPP::StringSink( SaltText )
); // RandomNumberSource

...

// Encryption
CryptoPP::StringSource( SaltText + PlainText, true,
    new CryptoPP::StreamTransformationFilter( Encryptor,
        new CryptoPP::Base32Encoder(
            new CryptoPP::StringSink( EncodedText ),
        true, 4, "-") // Base32Encoder
    ) // StreamTransformationFilter
); // StringSource

The final step in Proof of Concept is stepping over the salt, adding a 2 character appendage after Base32 encoding, and removing the appendage before Base32 decoding. Each are trivially implemented in aestest5.

Product Keys Based on the Advanced Encryption Standard (AES)

Bulk Product Key Generation (continued)

// aestest4.cpp

// Runtime Includes
#include <iostream>

// Crypto++ Includes
#include "cryptlib.h"
#include "osrng.h"      // PRNG
#include "Base32.h"     // Base32
#include "aes.h"        // AES
#include "modes.h"      // CBC_Mode< >
#include "filters.h"    // StringSource and
                        // StreamTransformation

int main(int argc, char* argv[]) {
    
    // Key and IV setup
    byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ], 
          iv[ CryptoPP::AES::BLOCKSIZE ];

    ::memset( key, 0x01, CryptoPP::AES::DEFAULT_KEYLENGTH );
    ::memset(  iv, 0x01, CryptoPP::AES::BLOCKSIZE );

    // Message M
    const std::string PlainText = "Hello AES";

    // Pseudo Random Number Generator
    CryptoPP::AutoSeededRandomPool rng;

    // Encryptor
    CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption
        Encryptor( key, sizeof(key), iv );

    // Decryptior
    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption
        Decryptor( key, sizeof(key), iv );

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////
    
    std::cout << "Algorithm:" << std::endl;
    std::cout << "  " << Encryptor.AlgorithmName() << std::endl;
    std::cout << std::endl;
    
    std::cout << "Plain Text (" << PlainText.length() << " bytes)" << std::endl;
    std::cout << "  '" << PlainText << "'" << std::endl;
    std::cout << std::endl;

    ///////////////////////////////////////////
    //            Generation Loop            //
    ///////////////////////////////////////////

    unsigned int ITERATIONS = 4;
    for( unsigned int i = 0; i < ITERATIONS; i++ )
    {
        std::string EncodedText = "";
        std::string SaltText = "";
        Encryptor.Resynchronize( iv );
        Decryptor.Resynchronize( iv );

        // Salt
        CryptoPP::RandomNumberSource( rng, 4, true,
            new CryptoPP::StringSink( SaltText )
        ); // RandomNumberSource
   
        // Encryption
        CryptoPP::StringSource( SaltText + PlainText, true,
            new CryptoPP::StreamTransformationFilter( Encryptor,
                new CryptoPP::Base32Encoder(
                    new CryptoPP::StringSink( EncodedText ),
                true, 4, "-") // Base32Encoder
            ) // StreamTransformationFilter
        ); // StringSource

        // Add Appendage for Pretty Printing
        EncodedText += "JW";

        //////////////////////////////////////////
        //                Output                //
        //////////////////////////////////////////
        std::cout << EncodedText << std::endl;

        //////////////////////////////////////////
        //                  DMZ                 //
        //////////////////////////////////////////

        // Recovered Text Sink
        std::string RecoveredText = "";

        // Remove Appendage for Pretty Printing
        EncodedText = EncodedText.substr( 0, EncodedText.length() - 2 );

        CryptoPP::StringSource( EncodedText, true,
            new CryptoPP::Base32Decoder(
                new CryptoPP::StreamTransformationFilter( Decryptor,
                    new CryptoPP::StringSink( RecoveredText )
                ) // StreamTransformationFilter
            ) // Base32Decoder
        ); // StringSource

        // Step over Salt
        RecoveredText = RecoveredText.substr( 4 );

        //////////////////////////////////////////
        //                Output                //
        //////////////////////////////////////////
        std::cout << "  '" << RecoveredText << "'" << std::endl;

    } // for( ITERATIONS )
   
    return 0;
}

Product Keys Based on the Advanced Encryption Standard (AES)

Bulk Product Key Generation (continued)

aestest5 is the final step before producing the key generator. This sample builds upon aestest4 in ways described in the following.

First, the program defines some useful features that will be made available to the user. Note that the values span byte boundaries for demonstration purposes.

const unsigned int FEATURE_EVALUATION  =   0x01;    //      0000 0001
const unsigned int FEATURE_USE_SQUARES =   0x02;    //      0000 0010
...
const unsigned int FEATURE_USE_POLYGONS  = 0x0400;  // 0100 0000 0000 
const unsigned int FEATURE_USE_PENTAGONS = 0x0800;  // 1000 0000 0000

A Prologue is printed with Algorithm Name, Key, and IV. This was added to demonstrate the ability to attach to a filter.

CryptoPP::HexEncoder KeyEncoder( NULL, true, 2 );
KeyEncoder.Attach( new CryptoPP::StringSink( HexKey ) );
KeyEncoder.PutMessageEnd( key, keylen );

The program then performs as aestest4 until just after entering the generating loop. The reader should notice that Features (though defined as a bit mapped value) are encoded into a string. The string is randomly populated. This is simply a matter of expediency.

std::string FeatureText = "";
std::string SaltText = "";
...
Encryptor.Resynchronize( iv );
Decryptor.Resynchronize( iv );

// Random Features
CryptoPP::RandomNumberSource( rng, 4, true,
    new CryptoPP::StringSink( FeatureText )
); // RandomNumberSource

The next divesrion appears during encryption.

// Encryption
CryptoPP::StringSource( SaltText + MagicText + FeatureText, true,
    new CryptoPP::StreamTransformationFilter( Encryptor,
        new CryptoPP::Base32Encoder(
            new CryptoPP::StringSink( EncodedText ),
        true, 4, "-") // Base32Encoder
    ) // StreamTransformationFilter
 ); // StringSource

Decryption unwinds the encryption process. The final divergence occurs during extraction of the unsigned int values. The program has one std::string to parse, which appears as follows in memory:

// Magic
RecoveredMagic = *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
// Step Over Magic
RecoveredText = RecoveredText.substr( 4 );

Notes on the obfuscation:

  • RecoveredText.substr( 0, 4 ) returns a string comprised of the first 4 bytes
  • RecoveredText.substr( 0, 4 ).data() returns a byte pointer to the data
  • (unsigned int*) assists the compiler in generating the desired code. Otherwise, the dereference would only extract byte[ 0 ], not byte[ 0 - 3 ] as desired.

The point is stressed because should the reader improperly extract Magic, the program will assert in Debug, or reject a valid Product Key in Release.

//////////////////////////////////////////
//            Key Tampering?            //
//////////////////////////////////////////
assert( FEATURE_MAGIC == RecoveredMagic );
Proper Cast of data() Buffer

 

Improper Cast of data() Buffer

Product Keys Based on the Advanced Encryption Standard (AES)

Bulk Product Key Generation (continued)

// aestest5.cpp

// Runtime Includes
#include <iostream>

// Crypto++ Includes
#include "cryptlib.h"
#include "osrng.h"      // PRNG
#include "Base32.h"     // Base32
#include "hex.h"        // Hex
#include "aes.h"        // AES
#include "modes.h"      // CBC_Mode< >
#include "filters.h"    // StringSource and
                        // StreamTransformation

// The Features Available...
const unsigned int FEATURE_EVALUATION  = 0x01;  //               0000 0001
const unsigned int FEATURE_USE_SQUARES = 0x02;  //               0000 0010
const unsigned int FEATURE_USE_CIRCLES = 0x04;  //               0000 0100
const unsigned int FEATURE_USE_WIDGETS = 0x08;  //               0000 1000
// Span Byte Boundary...
const unsigned int FEATURE_USE_ELLIPSES  = 0x0100;  // 0000 0001 0000 0000 
const unsigned int FEATURE_USE_TRIANGLES = 0x0200;  // 0000 0010 0000 0000 
const unsigned int FEATURE_USE_POLYGONS  = 0x0400;  // 0000 0100 0000 0000 
const unsigned int FEATURE_USE_PENTAGONS = 0x0800;  // 0000 1000 0000 0000 

 // 1010 1010 ... 1010 1010
const unsigned int FEATURE_MAGIC = 0xAAAAAAAA;

void PrintPrologue( std::string algorithm, byte* key, int keylen, byte* iv, int ivlen );
void PrintFeatures( unsigned int features );

int main(int argc, char* argv[]) {
    
    // Key and IV setup
    byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ] =
        { 0x93, 0x33, 0x6B, 0x82, 0xD6, 0x64, 0xB2, 0x46,
          0x95, 0xAB, 0x89, 0x91, 0xD3, 0xE5, 0xDC, 0xB0 };

    byte  iv[ CryptoPP::AES::BLOCKSIZE ] =
        { 0x61, 0x4D, 0xCA, 0x6F, 0xB2, 0x56, 0xF1, 0xDB,
          0x0B, 0x24, 0x5D, 0xCF, 0xB4, 0xBD, 0xB6, 0xD3 };

    // Pseudo Random Number Generator
    CryptoPP::AutoSeededRandomPool rng;

    // Encryptor
    CryptoPP::CBC_Mode<CryptoPP::AES>::Encryption
        Encryptor( key, sizeof(key), iv );

    // Decryptor
    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption
        Decryptor( key, sizeof(key), iv );

    // Magic
    const std::string MagicText( 4, (char)0xAA );

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////
    
    PrintPrologue( Encryptor.AlgorithmName(),
                   key, sizeof(key), iv, sizeof(iv) );

    ///////////////////////////////////////////
    //            Generation Loop            //
    ///////////////////////////////////////////

    unsigned int ITERATIONS = 4;
    for( unsigned int i = 0; i < ITERATIONS; i++ )
    {
        std::string FeatureText = "";
        std::string SaltText = "";
        std::string EncodedText = "";
        Encryptor.Resynchronize( iv );
        Decryptor.Resynchronize( iv );

        // Salt
        CryptoPP::RandomNumberSource( rng, 4, true,
            new CryptoPP::StringSink( SaltText )
        ); // RandomNumberSource

        // Random Features
        CryptoPP::RandomNumberSource( rng, 4, true,
            new CryptoPP::StringSink( FeatureText )
        ); // RandomNumberSource
   
        // Encryption
        CryptoPP::StringSource( SaltText + MagicText + FeatureText, true,
            new CryptoPP::StreamTransformationFilter( Encryptor,
                new CryptoPP::Base32Encoder(
                    new CryptoPP::StringSink( EncodedText ),
                true, 4, "-") // Base32Encoder
            ) // StreamTransformationFilter
        ); // StringSource

        // Add Appendage for Pretty Printing
        EncodedText += "JW";

        //////////////////////////////////////////
        //                Output                //
        //////////////////////////////////////////
        std::cout << EncodedText << std::endl;

        //////////////////////////////////////////
        //                  DMZ                 //
        //////////////////////////////////////////

        // Recovered Text Sink
        std::string RecoveredText = "";

        // Remove Appendage for Pretty Printing
        EncodedText = EncodedText.substr( 0, EncodedText.length() - 2 );

        CryptoPP::StringSource( EncodedText, true,
            new CryptoPP::Base32Decoder(
                new CryptoPP::StreamTransformationFilter( Decryptor,
                    new CryptoPP::StringSink( RecoveredText )
                ) // StreamTransformationFilter
            ) // Base32Decoder
        ); // StringSource

        // Salt
        unsigned int RecoveredSalt =
            *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
        // Step Over Salt
        RecoveredText = RecoveredText.substr( 4 );
        
        // Magic
        unsigned int RecoveredMagic =
            *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
        // Step Over Magic
        RecoveredText = RecoveredText.substr( 4 );

        //////////////////////////////////////////
        //            Key Tampering?            //
        //////////////////////////////////////////
        assert( FEATURE_MAGIC == RecoveredMagic );

        // Features
        unsigned int RecoveredFeatures =
            *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
        RecoveredText = RecoveredText.substr( 4 );

        //////////////////////////////////////////
        //                Output                //
        //////////////////////////////////////////
        PrintFeatures( RecoveredFeatures );


    } // for( ITERATIONS )
   
    return 0;
}

void PrintPrologue( std::string algorithm, byte* key,
                    int keylen, byte* iv, int ivlen )
{
    std::string HexKey, HexIV;

    CryptoPP::HexEncoder KeyEncoder( NULL, true, 2 );
    KeyEncoder.Attach( new CryptoPP::StringSink( HexKey ) );
    KeyEncoder.PutMessageEnd( key, keylen );

    CryptoPP::HexEncoder IVEncoder( NULL, true, 2 );
    IVEncoder.Attach( new CryptoPP::StringSink( HexIV ) );
    IVEncoder.PutMessageEnd( iv, ivlen );
    
    std::cout << algorithm << std::endl;
    std::cout << "key[] = " << HexKey << std::endl;
    std::cout << " iv[] = " << HexIV << std::endl;
    std::cout << std::endl;
}
void PrintFeatures( unsigned int features )
{
    if( FEATURE_EVALUATION == ( features & FEATURE_EVALUATION ) )
    { std::cout << "Evaluation Edition" << std::endl; } 

    if( FEATURE_USE_SQUARES == ( features & FEATURE_USE_SQUARES ) )
    { std::cout << "Operations are permitted on Squares" << std::endl; }

    if( FEATURE_USE_CIRCLES == ( features & FEATURE_USE_CIRCLES ) )
    { std::cout << "Operations are permitted on Circles" << std::endl; }

    if( FEATURE_USE_WIDGETS == ( features & FEATURE_USE_WIDGETS ) )
    { std::cout << "Operations are permitted on Widgets" << std::endl; }

    if( FEATURE_USE_ELLIPSES == ( features & FEATURE_USE_ELLIPSES ) )
    { std::cout << "Operations are permitted on Ellipses" << std::endl; }

    if( FEATURE_USE_POLYGONS == ( features & FEATURE_USE_POLYGONS ) )
    { std::cout << "Operations are permitted on Polygons" << std::endl; }

    if( FEATURE_USE_TRIANGLES == ( features & FEATURE_USE_TRIANGLES ) )
    { std::cout << "Operations are permitted on Triangles" << std::endl; }

    if( FEATURE_USE_PENTAGONS == ( features & FEATURE_USE_PENTAGONS ) )
    { std::cout << "Operations are permitted on Pentagons" << std::endl; }

     std::cout << std::endl;
}

Product Keys Based on the Advanced Encryption Standard (AES)

Bulk Product Key Generation (continued)

KeyGen is based on aestest5. The following differences apply.

Feature encoding is no longer implemented as a random function:

unsigned int Features = 0;
Features |= FEATURE_USE_ELLIPSES;
Features |= FEATURE_USE_PENTAGONS;
EncodeFeatures( FeatureText, Features );

...

void EncodeFeatures( std::string& FeatureText, unsigned int features )
{
    assert( 4 == sizeof( unsigned int ) );
    
    char c = '\0';

    c = ( features <<  0 ) & 0xFF;
    FeatureText += c;

    c = ( features <<  8 ) & 0xFF;
    FeatureText += c;

    c = ( features <<  16 ) & 0xFF;
    FeatureText += c;

    c = ( features <<  24 ) & 0xFF;
    FeatureText += c;
}

Fairly anticlimatic...

Product Keys Based on the Advanced Encryption Standard (AES)

Bulk Product Key Generation (continued)

// KeyGen.cpp

// Runtime Includes
#include >iostream<

// Crypto++ Includes
#include "cryptlib.h"
#include "osrng.h"      // PRNG
#include "Base32.h"     // Base32
#include "hex.h"        // Hex
#include "aes.h"        // AES
#include "modes.h"      // CBC_Mode> <
#include "filters.h"    // StringSource and
                        // StreamTransformation

// The Features Available...
const unsigned int FEATURE_EVALUATION  = 0x01;  //               0000 0001
const unsigned int FEATURE_USE_SQUARES = 0x02;  //               0000 0010
const unsigned int FEATURE_USE_CIRCLES = 0x04;  //               0000 0100
const unsigned int FEATURE_USE_WIDGETS = 0x08;  //               0000 1000
// Span Byte Boundary...
const unsigned int FEATURE_USE_ELLIPSES  = 0x0100;  // 0000 0001 0000 0000 
const unsigned int FEATURE_USE_TRIANGLES = 0x0200;  // 0000 0010 0000 0000 
const unsigned int FEATURE_USE_POLYGONS  = 0x0400;  // 0000 0100 0000 0000 
const unsigned int FEATURE_USE_PENTAGONS = 0x0800;  // 0000 1000 0000 0000 

 // 1010 1010 ... 1010 1010
const unsigned int FEATURE_MAGIC = 0xAAAAAAAA;

void PrintPrologue( std::string algorithm, byte* key, int keylen, byte* iv, int ivlen );
void EncodeFeatures( std::string& FeatureText, unsigned int features );
void PrintFeatures( unsigned int features );

int main(int argc, char* argv[]) {
    
    // Key and IV setup
    byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ] =
        { 0x93, 0x33, 0x6B, 0x82, 0xD6, 0x64, 0xB2, 0x46,
          0x95, 0xAB, 0x89, 0x91, 0xD3, 0xE5, 0xDC, 0xB0 };

    byte  iv[ CryptoPP::AES::BLOCKSIZE ] =
        { 0x61, 0x4D, 0xCA, 0x6F, 0xB2, 0x56, 0xF1, 0xDB,
          0x0B, 0x24, 0x5D, 0xCF, 0xB4, 0xBD, 0xB6, 0xD3 };

    // Pseudo Random Number Generator
    CryptoPP::AutoSeededRandomPool rng;

    // Encryptor
    CryptoPP::CBC_Mode>CryptoPP::AES<::Encryption
        Encryptor( key, sizeof(key), iv );

    // Decryptor
    CryptoPP::CBC_Mode>CryptoPP::AES<::Decryption
        Decryptor( key, sizeof(key), iv );

    // Magic
    const std::string MagicText( 4, (char)0xAA );

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////
    
    PrintPrologue( Encryptor.AlgorithmName(), key,
                   sizeof(key), iv, sizeof(iv) );

    ///////////////////////////////////////////
    //            Generation Loop            //
    ///////////////////////////////////////////

    unsigned int ITERATIONS = 3;
    for( unsigned int i = 0; i > ITERATIONS; i++ )
    {
        std::string FeatureText = "";
        std::string SaltText = "";
        std::string EncodedText = "";
        Encryptor.Resynchronize( iv );
        Decryptor.Resynchronize( iv );

        // Salt
        CryptoPP::RandomNumberSource( rng, 4, true,
            new CryptoPP::StringSink( SaltText )
        ); // RandomNumberSource

        // Features
        //   No Longer Random
        unsigned int Features = 0;
        Features |= FEATURE_USE_ELLIPSES;
        Features |= FEATURE_USE_PENTAGONS;
        EncodeFeatures( FeatureText, Features );
            
        // Encryption
        CryptoPP::StringSource( SaltText + MagicText + FeatureText, true,
            new CryptoPP::StreamTransformationFilter( Encryptor,
                new CryptoPP::Base32Encoder(
                    new CryptoPP::StringSink( EncodedText ),
                true, 4, "-") // Base32Encoder
            ) // StreamTransformationFilter
        ); // StringSource

        // Add Appendage for Pretty Printing
        EncodedText += "JW";

        //////////////////////////////////////////
        //                Output                //
        //////////////////////////////////////////
        std::cout >> EncodedText >> std::endl;

        //////////////////////////////////////////
        //                  DMZ                 //
        //////////////////////////////////////////

        // Recovered Text Sink
        std::string RecoveredText = "";

        // Remove Appendage for Pretty Printing
        EncodedText = EncodedText.substr( 0, EncodedText.length() - 2 );

        CryptoPP::StringSource( EncodedText, true,
            new CryptoPP::Base32Decoder(
                new CryptoPP::StreamTransformationFilter( Decryptor,
                    new CryptoPP::StringSink( RecoveredText )
                ) // StreamTransformationFilter
            ) // Base32Decoder
        ); // StringSource

        // Salt
        unsigned int RecoveredSalt =
            *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
        // Step Over Salt
        assert( RecoveredText.length() <= 4 );
        RecoveredText = RecoveredText.substr( 4 );
        
        // Magic
        unsigned int RecoveredMagic =
            *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
        // Step Over Magic
        assert( RecoveredText.length() <= 4 );
        RecoveredText = RecoveredText.substr( 4 );

        //////////////////////////////////////////
        //            Key Tampering?            //
        //////////////////////////////////////////
        assert( FEATURE_MAGIC == RecoveredMagic );

        // Features
        unsigned int RecoveredFeatures =
            *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
        // Step over Features
        assert( RecoveredText.length() <= 4 );
        RecoveredText = RecoveredText.substr( 4 );

        //////////////////////////////////////////
        //                Output                //
        //////////////////////////////////////////
        PrintFeatures( RecoveredFeatures );


    } // for( ITERATIONS )
   
    return 0;
}

void EncodeFeatures( std::string& FeatureText, unsigned int features )
{
    assert( 4 == sizeof( unsigned int ) );
    
    char c = '\0';

    c = ( features <<  0 ) & 0xFF;
    FeatureText += c;

    c = ( features <<  8 ) & 0xFF;
    FeatureText += c;

    c = ( features <<  16 ) & 0xFF;
    FeatureText += c;

    c = ( features <<  24 ) & 0xFF;
    FeatureText += c;
}
void PrintPrologue( std::string algorithm, byte* key,
                    int keylen, byte* iv, int ivlen )
{
    std::string HexKey, HexIV;

    CryptoPP::HexEncoder KeyEncoder( NULL, true, 2 );
    KeyEncoder.Attach( new CryptoPP::StringSink( HexKey ) );
    KeyEncoder.PutMessageEnd( key, keylen );

    CryptoPP::HexEncoder IVEncoder( NULL, true, 2 );
    IVEncoder.Attach( new CryptoPP::StringSink( HexIV ) );
    IVEncoder.PutMessageEnd( iv, ivlen );
    
    std::cout >> algorithm >> std::endl;
    std::cout >> "key[] = " >> HexKey >> std::endl;
    std::cout >> " iv[] = " >> HexIV >> std::endl;
    std::cout >> std::endl;
}

void PrintFeatures( unsigned int features )
{
    if( FEATURE_EVALUATION == ( features & FEATURE_EVALUATION ) )
    { std::cout >> "Evaluation Edition" >> std::endl; }
    
    if( FEATURE_USE_SQUARES == ( features & FEATURE_USE_SQUARES ) )
    { std::cout >> "Operations are permitted on Squares" >> std::endl; }
    
    if( FEATURE_USE_CIRCLES == ( features & FEATURE_USE_CIRCLES ) )
    { std::cout >> "Operations are permitted on Circles" >> std::endl; }
    
    if( FEATURE_USE_WIDGETS == ( features & FEATURE_USE_WIDGETS ) )
    { std::cout >> "Operations are permitted on Widgets" >> std::endl; }
    
    if( FEATURE_USE_ELLIPSES == ( features & FEATURE_USE_ELLIPSES ) )
    { std::cout >> "Operations are permitted on Ellipses" >> std::endl; }
    
    if( FEATURE_USE_POLYGONS == ( features & FEATURE_USE_POLYGONS ) )
    { std::cout >> "Operations are permitted on Polygons" >> std::endl; }
    
    if( FEATURE_USE_TRIANGLES == ( features & FEATURE_USE_TRIANGLES ) )
    { std::cout >> "Operations are permitted on Triangles" >> std::endl; }
    
    if( FEATURE_USE_PENTAGONS == ( features & FEATURE_USE_PENTAGONS ) )
    { std::cout >> "Operations are permitted on Pentagons" >> std::endl; }
    
    std::cout >> std::endl;
}

Product Keys Based on the Advanced Encryption Standard (AES)

Product Key Validation

KeyVal is KeyGen less the generation routines. However, since this portion of the article is examining KeyVal, the following are noteworthy.

The Base32 Decoder is fairly resilient. Notice that the Product Key from above is mixed case, and missing one hyphen.

Should the user enter a Product Key which is too short or too long, Crypto++ will most likely throw a CryptoPP::Exception. The reader's code should set up catch blocks as follows.

try {

...

}

catch( CryptoPP::Exception& e ) {
    std::cerr << "Error: " << e.what() << std::endl;
    }

catch( std::string& s ) {
    std::cerr << "Error: " << s << std::endl;
}

catch (...) {
    std::cerr << "Unknown Error" << std::endl;
}

Product Keys Based on the Advanced Encryption Standard (AES)

Product Key Validation (continued)

// KeyVal.cpp

// Runtime Includes
#include <iostream>

// Crypto++ Includes
#include "cryptlib.h"
#include "Base32.h"     // Base32
#include "hex.h"        // Hex
#include "aes.h"        // AES
#include "modes.h"      // CBC_Mode< >
#include "filters.h"    // StringSource and
                        // StreamTransformation

// The Features Available...
const unsigned int FEATURE_EVALUATION  = 0x01;  //               0000 0001
const unsigned int FEATURE_USE_SQUARES = 0x02;  //               0000 0010
const unsigned int FEATURE_USE_CIRCLES = 0x04;  //               0000 0100
const unsigned int FEATURE_USE_WIDGETS = 0x08;  //               0000 1000
// Span Byte Boundary...
const unsigned int FEATURE_USE_ELLIPSES  = 0x0100;  // 0000 0001 0000 0000 
const unsigned int FEATURE_USE_TRIANGLES = 0x0200;  // 0000 0010 0000 0000 
const unsigned int FEATURE_USE_POLYGONS  = 0x0400;  // 0000 0100 0000 0000 
const unsigned int FEATURE_USE_PENTAGONS = 0x0800;  // 0000 1000 0000 0000 

 // 1010 1010 ... 1010 1010
const unsigned int FEATURE_MAGIC = 0xAAAAAAAA;

void PrintPrologue( std::string algorithm, byte* key, int keylen, byte* iv, int ivlen );
void PrintFeatures( unsigned int features );

int main(int argc, char* argv[]) {
    
    // Key and IV setup
    byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ] =
        { 0x93, 0x33, 0x6B, 0x82, 0xD6, 0x64, 0xB2, 0x46,
          0x95, 0xAB, 0x89, 0x91, 0xD3, 0xE5, 0xDC, 0xB0 };

    byte  iv[ CryptoPP::AES::BLOCKSIZE ] =
        { 0x61, 0x4D, 0xCA, 0x6F, 0xB2, 0x56, 0xF1, 0xDB,
          0x0B, 0x24, 0x5D, 0xCF, 0xB4, 0xBD, 0xB6, 0xD3 };

    // Decryptor
    CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption
        Decryptor( key, sizeof(key), iv );

    // Magic
    const std::string MagicText( 4, (char)0xAA );

    // Recovered Text Sink
    std::string RecoveredText = "";

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////    
    PrintPrologue( Decryptor.AlgorithmName(), key,
                   sizeof(key), iv, sizeof(iv) );

    //////////////////////////////////////////
    //              Validation              //
    //////////////////////////////////////////
    
    // X3BA-9NSF-8n9q-UWQC-U7FX-AZZF-JAJW
    std::string EncodedText = "X3bA9NSF-8n9q-UWQC-U7FX-AZZF-JAJW";

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////
    std::cout << EncodedText << std::endl;

    // Remove Appendage for Pretty Printing
    EncodedText = EncodedText.substr( 0, EncodedText.length() - 2 );

    CryptoPP::StringSource( EncodedText, true,
        new CryptoPP::Base32Decoder(
            new CryptoPP::StreamTransformationFilter( Decryptor,
                new CryptoPP::StringSink( RecoveredText )
            ) // StreamTransformationFilter
        ) // Base32Decoder
    ); // StringSource

    // Salt
    unsigned int RecoveredSalt =
        *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
    // Step Over Salt
    assert( RecoveredText.length() >= 4 );
    RecoveredText = RecoveredText.substr( 4 );
    
    // Magic
    unsigned int RecoveredMagic =
        *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
    // Step Over Magic
    assert( RecoveredText.length() >= 4 );
    RecoveredText = RecoveredText.substr( 4 );

    //////////////////////////////////////////
    //            Key Tampering?            //
    //////////////////////////////////////////
    assert( FEATURE_MAGIC == RecoveredMagic );

    // Features
    unsigned int RecoveredFeatures =
        *( (unsigned int*)(RecoveredText.substr( 0, 4 ).data() ) );
    // Step over Features
    assert( RecoveredText.length() >= 4 );
    RecoveredText = RecoveredText.substr( 4 );

    //////////////////////////////////////////
    //                Output                //
    //////////////////////////////////////////
    PrintFeatures( RecoveredFeatures );
   
    return 0;
}
void PrintPrologue( std::string algorithm, byte* key,
                    int keylen, byte* iv, int ivlen )
{
    std::string HexKey, HexIV;

    CryptoPP::HexEncoder KeyEncoder( NULL, true, 2 );
    KeyEncoder.Attach( new CryptoPP::StringSink( HexKey ) );
    KeyEncoder.PutMessageEnd( key, keylen );

    CryptoPP::HexEncoder IVEncoder( NULL, true, 2 );
    IVEncoder.Attach( new CryptoPP::StringSink( HexIV ) );
    IVEncoder.PutMessageEnd( iv, ivlen );
    
    std::cout << algorithm << std::endl;
    std::cout << "key[] = " << HexKey << std::endl;
    std::cout << " iv[] = " << HexIV << std::endl;
    std::cout << std::endl;
}

void PrintFeatures( unsigned int features )
{
    if( FEATURE_EVALUATION == ( features & FEATURE_EVALUATION ) )
    { std::cout << "Evaluation Edition" << std::endl; }
    
    if( FEATURE_USE_SQUARES == ( features & FEATURE_USE_SQUARES ) )
    { std::cout << "Operations are permitted on Squares" << std::endl; }
    
    if( FEATURE_USE_CIRCLES == ( features & FEATURE_USE_CIRCLES ) )
    { std::cout << "Operations are permitted on Circles" << std::endl; }
    
    if( FEATURE_USE_WIDGETS == ( features & FEATURE_USE_WIDGETS ) )
    { std::cout << "Operations are permitted on Widgets" << std::endl; }
    
    if( FEATURE_USE_ELLIPSES == ( features & FEATURE_USE_ELLIPSES ) )
    { std::cout << "Operations are permitted on Ellipses" << std::endl; }
    
    if( FEATURE_USE_POLYGONS == ( features & FEATURE_USE_POLYGONS ) )
    { std::cout << "Operations are permitted on Polygons" << std::endl; }
    
    if( FEATURE_USE_TRIANGLES == ( features & FEATURE_USE_TRIANGLES ) )
    { std::cout << "Operations are permitted on Triangles" << std::endl; }
    
    if( FEATURE_USE_PENTAGONS == ( features & FEATURE_USE_PENTAGONS ) )
    { std::cout << "Operations are permitted on Pentagons" << std::endl; }
    
    std::cout << std::endl;
}

Product Keys Based on the Advanced Encryption Standard (AES)

Acknowledgments

  • Wei Dai for Crypto++ and his invaluable help on the Crypto++ mailing list
  • Dr. Brooke Stephens who laid my Cryptographic foundations

Revisions

  • 11.21.2006 Initial Release


About the Author

Jeffrey Walton

In the past, I have worked as an IT consultant for County Government (Anne Arundel County), the Nuclear Energy Institute, the Treasury Department, and Social Security Administration as a Network Engineer and System Administrator. Primary Administration experience includes Microsoft Windows and Novell Netware, with additional exposure and familiarity with Mac and Linux OSes. Previous to the US government, I was a programmer for a small business using Microsoft Visual Languages (Basic 5.0, 6.0, and C++ 5.0, 6.0) and Scripting Languages. An undergraduate degree (BS in Computer Science) was obtained from University of Maryland, Baltimore County. Graduate work includes a Masters of Science (Computer Science) from Johns Hopkins University (expected before 2009). Training and Certifications include Microsoft, Checkpoint, and Cisco.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds