Wednesday, October 28, 2009

Using Symmetric (Private) Key Encryption











 < Day Day Up > 





Using Symmetric (Private) Key Encryption



As mentioned previously symmetric key encryption uses a single private key for both encryption and decryption. To provide support for this type of encryption, the BCL defines the System::Security::Cryptography::SymmetricAlgorithm abstract class, which is the base class for all symmetric algorithm classes. There are wrapper classes for several popular symmetric cryptographic algorithms, all of which derive from this SymmetricAlgorithm class:



  • System::Security::Cryptography::DES

  • System::Security::Cryptography::RC2

  • System::Security::Cryptography::Rijndael

  • System::Security::Cryptography::TripleDES



Essentially each of those classes are abstract base classes for their respective algorithms, and any class that implements any of those algorithms must derive from these classes. For example, a class called DESCryptoServiceProvider (derived from the DES class) provides a wrapper for the DES implementation provided by the Cryptographic service provider.



Private Key Encrypting/Decrypting Demo Using DES



The CryptoStream class is used to perform cryptographic transformations on any data stream and is under the System::Security::Cryptography namespace. It derives from System::IO::Stream, and thus we can call any Stream methods on a CryptoStream object just as if it were a network or file stream object. The CryptoStream constructor is as follows:





CryptoStream(

Stream* stream,

ICryptoTransform* transform,

CryptoStreamMode mode

);



The first parameter is a stream object that may be a file stream, a memory stream, or a network stream, and it's on this stream that the cryptographic transformation is performed.



The second argument is an ICryptoTransform object that defines the cryptographic transform that is to be performed on the stream. Any class that derives from the SymmetricAlgorithm has a CreateEncryptor method that returns an ICryptoTransform object. Thus, to perform a DES transform on the data stream, instantiate a DESCryptoServiceProvider object and call CreateEncryptor on it, or, if you want to perform a Rijndael transformation, then instantiate a RijndaelManaged object and call CreateEncryptor. In fact you may also pass any class derived from the HashAlgorithm class, because they will all be implementing the ICryptoTransform interface, though the issue would be that hash algorithms are not key-based, and thus the security of the encryption would be considerably reduced.



The third and last parameter for the CryptoStream constructor is a CrytopStreamMode enumeration value that specifies either read mode (CryptoStreamMode::Read) or write mode (CryptoStreamMode::Write).



Now let's look at what it takes to encrypt a file. We'll look at a function with the following syntax:





void EncryptFile(

String* inputFile,

String* outputFile

);



As you can see, this file takes only two parameters�an input file name and an output file name. However, before I throw some code at you, let's look at the generic steps involved in encrypting a file and the details of how the EncryptFile accomplished each given step.





















  1. Acquire a key value:

    One of the first tasks is to obtain a key value used to encrypt the file. In the case of the EncryptFile function, this value is hard-coded in the function in the form of a String object. Since the Cryptography class works with byte arrays, this value is then converted to a byte array using the incredibly useful System::Text::Encoding class. (You should note that while this function has a hard-coded key value for the sake of simplicity, you might want to add this value to the parameter list.)



  2. Construct the FileStream objects for input and output:

    As you learned in Chapter 3, the FileStream object allows a generic means of reading and writing data to physical files. The EncryptFile function first constructs a FileStream object for the specified input file with read access. Then the function constructs a FileStream object for the specified output file name with create access.



  3. Construct a SymmetricAlgorithm-derived object:

    While you can obviously select whatever algorithm you need for your particular application, I've chosen the DESCryptoServiceProvider object to support the DES algorithm. (As you might guess from the class name, the DESCryptoServiceProvider is an encapsulation of the CryptoAPI support for DES.)



  4. Construct a symmetric encryptor object with the specified key and IV:

    All SymmetricAlgorithm-derived classes must implement the pure virtual CreateEncryptor function that takes key and IV parameters. This function returns an ICryptoTransform interface.



  5. Construct a CryptoStream object:

    At this point, we have two of the main ingredients needed to encrypt a file: a FileStream object for the output and an ICryptoTransform interface. These two objects (along with a CryptoStreamMode enumeration value of Write) are then passed to the CryptoStream constructor. As you might imagine from the discussion of streams in Chapter 3, the CryptoStream class handles all the details of how to output the data (in the specified encrypted form) to the indicated output file name.



  6. Read input data:

    Obviously this is application-specific, but, as mentioned in Step 2, the EncryptFile function presented here attaches a FileStream to the specified input file name. After construction of the CryptoStream object, the function simply reads through the input FileStream object, reading a block of 128 bytes at a time.



  7. Write data to the CryptoStream:

    As each of the 128-byte blocks are read, they are output to the CryptoStream using the CryptoStream::Write method. Internally, the CryptoStream object encrypts the data in the chosen format and outputs it to the underlying physical files.



  8. Close all opened Stream objects:

    In the case of the EncryptFile function, those would be the input and output FileStream objects and the CryptoStream object.



That's it. Eight simple steps (about 50 lines of C++ code) to do something as advanced as taking an input file, opening it, reading every byte, encrypting every byte, and then writing the encrypted data to disk. At this point, let's see the actual C++ code for the EncryptFile (Listing 4-1). (Note that the comments in the code are in bold to make it easier to tie the aforementioned file-encryption steps to the actual implementation code.)



Listing 4-1. The EncryptFile function outputs encrypted data from a specified input file to a specified output using a supplied private key.






void EncryptFile(String* inputFileName, String* outputFileName)

{

#pragma push_macro("new")

#undef new

CryptoStream *cryptoStream;

FileStream* outputFileStream;

FileStream* inputFileStream;



try

{

// Step #1 : Acquire a keyByteArray value - in this case hard-coded.

String* keyString = S"KeyAbcGG";

Byte keyByteArray[] = Text::Encoding::Default->GetBytes(keyString);



// Step #2: Construct the FileStream objects for input and output

inputFileStream = new FileStream(inputFileName,

FileMode::Open,

FileAccess::Read);



outputFileStream = new FileStream(outputFileName,

FileMode::Create,

FileAccess::Write);



// Step #3: Construct a SymmetricAlgorithm-derived object

DESCryptoServiceProvider *serviceProvider =

new DESCryptoServiceProvider();



// Step #4: Construct a symmetric encryptor object with the

// specified key and IV

ICryptoTransform* encryptor =

serviceProvider->CreateEncryptor(keyByteArray,keyByteArray);



// Step #5: Construct a CryptoStream object

cryptoStream = new CryptoStream(outputFileStream,

encryptor, CryptoStreamMode::Write);



Byte bytesread[] = new Byte[129];



// Step #6: Read input data

while(int n = inputFileStream->Read(bytesread, 0, 128))

{

// Step #7: Write data to the CryptoStream

cryptoStream->Write(bytesread, 0, n);

}

}

catch(Exception* e)

{

throw e;

}

__finally

{

// Step 8: Close all opened Stream objects

cryptoStream->Close();

outputFileStream->Close();

inputFileStream->Close();

}

#pragma pop_macro("new")

}



Now, let's turn our attention to the task of decrypting data. For this operation, I've included a function called DecryptFile:





void DecryptFile(

String* encryptedfile,

String* decryptedfile

)



Once again, I'll first list the basic steps of decrypting a file and outputting the results to another file and then present an implementation of that list in the form of the DecryptFile function.





















  1. Acquire a key value:

    As with encrypting a file using symmetric key encryption, a private key is needed to decrypt the file. Obviously, the key used to decrypt the file must match exactly the one that was used to encrypt. As with the EncryptFile function, I've hard-coded the value of the key in the DecryptFile for the sake of simplicity. (You'll want to change this key in your production environment.) This value is converted from its String object form to a byte array suitable for consumption by the BCL cryptography classes.



  2. Construct the FileStream objects for input and output:

    As mentioned earlier in this section the CryptoStream object that will be used to decrypt the data can be constructed from a Stream�including a file-based FileStream. Since our input is a physical file, we must first construct a FileStream using the specified input file name. At the same time, another FileStream object is created for the output.



  3. Construct a SymmetricAlgorithm-derived object:

    Obviously the algorithm used when decrypting a file must match the algorithm used when that file was encrypted. Since the EncryptFile function used the BCL wrapper for the CryptoAPI implementation of DES encryption, the DecryptFile function utilizes the DESCryptoServiceProvider here.



  4. Construct a symmetric decryptor object with the specified key and IV:

    All SymmetricAlgorithm-derived classes must implement the pure virtual CreateDecryptor function that takes key and IV parameters. This function returns an ICryptoTransform interface that will do the actual data decryption.



  5. Construct a CryptoStream object:

    At this point, we have two of the main ingredients needed to decrypt a file�a FileStream object for the input and an ICryptoTransform interface. These two objects (along with a CryptoStreamMode enumeration value of Read) are then passed to the CryptoStream constructor.



  6. Read input data:

    Utilizing the CryptoStream::Read method, data is read 128 bytes at a time into a Byte array. Once again, there is no decryption work on your end, as the transformer interface is doing the heavy lifting for you.



  7. Write data to the CryptoStream:

    This is obviously application-specific, but in the case of the DecryptFile function, each 128-byte block that is read via the CryptoStream is written to a FileStream attached to a physical file whose name was passed as a parameter to the function.



  8. Close all opened Stream objects:

    In the case of the DecryptFile function, those would be the input and output FileStream objects and the CryptoStream object.



Once again, we have eight easy-to-follow steps for reading data from a file, decrypting it, and writing it to another file. Listing 4-2 shows the code for the DecryptFile (again, the comments are in bold so that you can match these steps with the actual C++ implementation).



Listing 4-2. The DecryptFile function takes input from a physical file and outputs DES decrypted data to a specified file name using a supplied private key.






void DecryptFile(String* inputFileName, String* outputFileName)

{

#pragma push_macro("new")

#undef new

CryptoStream *cryptoStream;

FileStream* outputFileStream;

FileStream* inputFileStream;



try

{

// Step #1: Acquire a keyByteArray value - in this case hard-coded.

String* keyString = S"KeyAbcGG";

Byte keyByteArray[] = Text::Encoding::Default->GetBytes(keyString);



// Step #2: Construct the FileStream objects for input and output

inputFileStream = new FileStream(inputFileName,

FileMode::Open,

FileAccess::Read);



outputFileStream = new FileStream(outputFileName,

FileMode::Create,

FileAccess::Write);



// Step #3: Construct a SymmetricAlgorithm-derived object

DESCryptoServiceProvider* serviceProvider =

new DESCryptoServiceProvider();



// Step #4: Construct a symmetric decryptor object with the

// specified key and IV

ICryptoTransform* decryptor =

serviceProvider->CreateDecryptor(keyByteArray, keyByteArray);



// Step #5: Construct a CryptoStream object

cryptoStream = new CryptoStream(inputFileStream,

decryptor,

CryptoStreamMode::Read);



Byte bytesRead[] = new Byte[129];



// Step #6: Read input data

while(int n = cryptoStream->Read(bytesRead, 0, 128))

{

// Step #7: Write data to the CryptoStream

outputFileStream->Write(bytesRead,0,n);

}

}

catch(Exception* e)

{

throw e;

}

__finally

{

// Step 8: Close all opened Stream objects

cryptoStream->Close();

outputFileStream->Close();

inputFileStream->Close();

}

#pragma pop_macro("new")

}



It's one thing to present an allegedly helpful function (or two in this case), but let's take another step and see these functions at work in a demo application that allows you to both encrypt and decrypt files using a private key encryption algorithm�specifically, DES.





























  1. Create an MFC dialog-based application called DESDemo.

  2. Update the Project properties to support Managed Extensions.

  3. Open the stdafx.h file and add the following .NET support directives to the end of the file:





    #using <mscorlib.dll>

    #using <System.Windows.Forms.dll>

    #using <System.dll>

    using namespace System;

    using namespace System::Windows::Forms;

    using namespace System::IO;

    using namespace System::Security::Cryptography;

    #undef MessageBox

  4. Open the dialog template resource and add the controls as you see them in Figure 4-5. (Note that the control on the bottom�below the "Status:" static text�is a list box control.)



    Figure 4-5. Dialog resource for the DESDemo demo application


  5. For the larger edit control (below the "Result:" static text), set the ReadOnly property to True and the AutoHScroll property to False.

  6. Create the DDX member variables as shown in Table 4-3.

  7. Define the following member variable in the CDESDemoDlg class.





    class CDESDemoDlg : public CDialog

    {

    ...

    CString m_strWorkingDir;



    Table 4-3. DDX Variables for the DESDemo Demo

    Control

    Variable Type

    Variable Name

    File name edit

    CString

    m_strFileName

    File name edit

    CEdit

    m_edtFileName

    Result edit

    CString

    m_strFileContents

    Status list box

    CList box

    m_lbxStatus

  8. Add the following code to the OnInitDialog function just before the return statement.





    BOOL CDESDemoDlg::OnInitDialog()

    {

    ...

    TCHAR buff[MAX_PATH];

    GetModuleFileName(NULL, buff, MAX_PATH);

    m_strWorkingDir = System::IO::Path::GetDirectoryName(buff);

    return TRUE; // return TRUE unless you set the focus to a control

    }

  9. At this point, code the button with the ellipsis ("...") on it. This will allow users to use the Windows File Open dialog box to locate their files to encrypt and decrypt:





    void CDESDemoDlg::OnBnClickedFindFile()

    {

    UpdateData();



    CFileDialog dlg(TRUE);

    dlg.m_ofn.lpstrInitialDir = m_strWorkingDir;

    dlg.m_ofn.lpstrFilter =

    _T("Text Files (*.txt)\0*.txt\0"

    "Encrypted Files (*.enc)\0*.enc\0"

    "Decrypted Files (*.dec)\0*.dec\0"

    "All Files (*.*)\0*.*\0"

    );



    if (IDOK == dlg.DoModal())

    {

    // Update the dialog with the selected file.

    m_strFileName = dlg.GetPathName();

    UpdateData(FALSE);



    // Common trick to move the cursor to the end

    // of the edit control so that the user can

    // see the file name in a long name.

    m_edtFileName.SetSel(m_strFileName.GetLength(),

    m_strFileName.GetLength(),

    FALSE);

    }

    }

  10. Insert the EncryptFile and DecryptFile functions from Listings 4-1 and 4-2, respectively. These functions have been intentionally coded in such a way that they can be dropped into any mixed mode (MFC/MC++) application.

  11. Now it's time to use these functions. First add a handler for the Encrypt File button and code it as follows. In a full-production system, you would want to add more error-handling and centralize things like the hard-coded extensions I've chosen for this application (.enc and .dec for encrypted and decrypted files, respectively). However, I've tried to keep things as simple as possible here while still providing a practical, usable demo.



    As you can see, this function's main purpose in life is to determine the output file name (the original file is never overwritten) from the input file name supplied by the user, to call the EncryptFile function, and, upon return, to open and display the encrypted file. In addition, status messages are inserted into the list box in reverse order.



    Note that the input file name is automatically changed to the output name, as the next logical step for the user might be to decrypt the newly created file to test the encryption/decryption round trip.





    void CDESDemoDlg::OnBnClickedEncryptFile()

    {

    #pragma push_macro("new")

    #undef new

    CWaitCursor wc;





    UpdateData();

    if (0 < m_strFileName.GetLength())

    {

    FileStream* pFS;

    try

    {

    CString strStatus;

    m_strFileContents = _T("");



    // If extension ends in .enc, replace with .dec

    // to prevent continual enc/dec of same file resulting

    // in names like file.txt.enc.dec.enc.dec...

    // Otherwise just add .dec

    CString strOutputFile = m_strFileName;

    if (0 == strOutputFile.Right(4).CompareNoCase(

    _T(".dec")))

    {

    strOutputFile =

    strOutputFile.Left(strOutputFile.GetLength() - 4)

    + _T(".enc");

    }

    else

    {

    strOutputFile += _T(".enc");

    }



    // Call EncryptFile function passing just-formatted

    // output name

    strStatus.Format(_T("Encrypting file %s"),

    m_strFileName);

    EncryptFile(m_strFileName, strOutputFile);



    strStatus.Format(_T("%s successfully encrypted"

    "to file %s"),

    m_strFileName,

    strOutputFile);

    m_lbxStatus.InsertString(0, strStatus);



    // Using a FileStream open, read and display newly

    // encrypted file in hex format.

    m_lbxStatus.InsertString(0, _T("Attempting to"

    "read newly encrypted file"));



    pFS = new FileStream(strOutputFile,

    FileMode::Open,

    FileAccess::Read);



    if (pFS->CanRead)

    {

    Byte buffer __gc[] = new Byte __gc[pFS->Length];

    pFS->Read(buffer, 0, buffer->Length);



    for (int i = 0; i < buffer->Length; i++)

    {

    CString c;

    c.Format(_T("%02x "), buffer[i]);

    m_strFileContents += c;

    }

    m_lbxStatus.InsertString(0, _T("Data read back"

    "and displayed"));

    m_strFileName = strOutputFile;

    }

    }

    catch(Exception* e)

    {

    m_lbxStatus.InsertString(0, (CString)e->Message);

    }

    __finally

    {

    pFS->Close();

    }



    UpdateData(FALSE);

    }

    else

    {

    MessageBox::Show(S"You must supply an input file name.");

    }

    #pragma pop_macro("new")

    }

  12. Now, add a handler for the Decrypt File button and code it as follows. Much like the reciprocal Encrypt File button handler, this function's tasks are to determine the output file name from the input file name supplied by the user (so that the original file is not lost), call the DecryptFile function and, upon returning, open and display the decrypted file. In addition, status messages are inserted into the list box in reverse order.





    void CDESDemoDlg::OnBnClickedDecryptFile()

    {

    #pragma push_macro("new")

    #undef new

    CWaitCursor wc;



    UpdateData();

    if (0 < m_strFileName.GetLength())

    {

    StreamReader* pSR;

    try

    {

    CString strStatus;

    m_strFileContents = _T("");



    // If extension ends in .enc, replace with .dec

    // to prevent continual enc/dec of same file resulting

    // in names like file.txt.enc.dec.enc.dec...

    // Otherwise just add .dec

    CString strOutputFile = m_strFileName;

    if (0 == strOutputFile.Right(4).CompareNoCase(

    _T(".enc")))

    {

    strOutputFile =

    strOutputFile.Left(strOutputFile.GetLength() - 4)

    + _T(".dec");

    }

    else

    {

    strOutputFile += _T(".dec");

    }



    // Call DecryptFile function passing just-formatted

    // output name

    strStatus.Format(_T("Decrypting file %s"),

    m_strFileName);

    DecryptFile(m_strFileName, strOutputFile);



    strStatus.Format(_T("%s successfully decrypted to file

    %s"),

    m_strFileName,

    strOutputFile);

    m_lbxStatus.InsertString(0, strStatus);



    m_lbxStatus.InsertString(0, _T("Attempting to read "

    "newly decrypted file"));





    // Using a StreamReader - open, read and display

    // newly decrypted file.

    pSR = new StreamReader(strOutputFile);



    CString strCurrLine;



    while (0 < pSR->Peek())

    {

    strCurrLine = pSR->ReadLine();

    m_strFileContents += strCurrLine;

    m_strFileContents += _T("\r\n");

    }



    m_lbxStatus.InsertString(0, _T("Data read back"

    "and displayed"));

    m_strFileName = strOutputFile;

    }

    catch(Exception* e)

    {

    m_lbxStatus.InsertString(0, (CString)e->Message);

    }

    __finally

    {

    pSR->Close();

    }

    UpdateData(FALSE);

    }

    else

    {

    MessageBox::Show(S"You must supply an input file name.");

    }

    #pragma pop_macro("new")

    }

  13. That's all there is to writing a simple encryption/decryption file using these powerful classes. Now run the application and supply an input file name to encrypt. Once you've done that, decrypt the same file to see the original file's contents. You can use the status list box in case of a problem�for example, the specified input file does not exist or you don't have write privileges for the output file. Your results should be similar to those shown in Figures 4-6 and 4-7.



    Figure 4-6. Example of encrypting a file using the CD's DESDemo demo




    Figure 4-7. Example of decrypting an encrypted file using the CD's DESDemo demo




Now that you've seen how private key encryption works, let's have a look at public key encryption.













     < Day Day Up > 



    No comments: