简体   繁体   中英

Why does CryptDecrpyt() from MS CryptoAPI not decrypt the first 16 bytes correctly on remote system?

I was trying to come up with how to use MS CryptoAPI to create a session key and transfer to a remote so they can encrypt data being sent over a private network (so not worried about man in the middle, just packet sniffer). Also need to support Windows XP. I think I figured out most of it and built a class to do the things I need. However, when I encrypt 2604 bytes of plaintext to 2608 of ciphertext and then decrypt it on that same machine ("Machine A") which imported the session key it works fine, however on the other machine (the one that actually created the session key, "Machine B") decrypting that data doesn't properly decrypt the first 16 bytes (first block).

What I do is have Machine A send a public key over to Machine B, Machine B creates a random session key and then exports it out and sends it over to Machine A, Machine A imports that key.

Machine A                                   Machine B
------------------------------------------  -------------------------------------------
Initialize();                               Initialize();
                                            CreateRandomSessionKey();
ExportPublicKey();
                                            ExportSessionKey();
ImportSessionKey();
CalcSizeForInPlaceEncryption(4604);
[create 4608 byte buffer with plaintext]
EncryptData(); *In Place*
DecryptData(); *New Buffer*                 
                                            DecryptData(); *In Place*
[Decrypted data matches.]                   [Decrypted data first 16 bytes invalid.]
                                            [Input of encrypted data matchines Machine A]

Here's the class I used to do that:

Header File:

#include <wincrypt.h>

class CMSCryptoAPI
{
  protected:
    HCRYPTPROV m_hCryptProv=NULL;        // crypto provider
    HCRYPTKEY m_hSessionKey=NULL;        // the symmetric key (session key)
    DWORD m_dwSessionKeyBlockSize=0;     // block size for encryption
  
    HCRYPTKEY CreateExchangeKey();
    bool ExportKey(HCRYPTKEY hkey, HCRYPTKEY hexpkey, DWORD blobtype, BYTE** pblob, DWORD* blobsize);
    DWORD GetSessionKeyBlockSize();

  public:
    virtual ~CMSCryptoAPI();

    // initialize before using class object
    bool Initialize();
    // uninitialize the initialization
    bool Uninitialize();

    bool DestroySessionKey();
    bool CreateRandomSessionKey();
    bool ExportSessionKey(const BYTE* publickeyblob, DWORD publickeyblobsize, BYTE** pblob, DWORD* blobsize);
    bool ImportSessionKey(const BYTE* pkeyblob, DWORD keyblobsize);
    bool ExportPublicKey(BYTE** pblob, DWORD* blobsize);
    bool EncryptData(BYTE* ppaintext, DWORD plaintextsize, BYTE** pciphertext, DWORD* ciphertextsize);
    bool DecryptData(BYTE* pciphertext, DWORD ciphertextsize, BYTE** ppaintext, DWORD *plaintextsize);
    bool SetIV(BYTE* pbiv=NULL);

    DWORD CalcSizeForInPlaceEncryption(DWORD plaintextsize);
};

Source File:

// Link with the Advapi32.lib file.
#pragma comment (lib, "advapi32")

#define KEYLENGTHSHIFT 16
#define KEYLENGTH      4096

//-------------------------------------------------------------------------
// Purpose: Destructor
//
// Input:   
//
// Output:    
//
// Notes:   
//
CMSCryptoAPI::~CMSCryptoAPI()
{
  Uninitialize();
}

//-------------------------------------------------------------------------
// Purpose: Initialize the object
//
// Input:   na
//
// Output:  true/false restult
//
// Notes:   must be called first before using any of the other functions
//
bool CMSCryptoAPI::Initialize()
{
  // acquire the provider to use
  if (!CryptAcquireContext(&m_hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0)) {
    // user may not have key set like the local system account so try machine set
    if (GetLastError()==NTE_BAD_KEYSET) {
      if (!CryptAcquireContext(&m_hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_MACHINE_KEYSET)) {
        if (GetLastError()==NTE_BAD_KEYSET) {
          if (!CryptAcquireContext(&m_hCryptProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET)) {
            CDebugPrint::DebugPrint(_T("Error %u during CryptAcquireContext for new keyset!\n"), GetLastError());
            return false;
          }
          else CDebugPrint::DebugPrint(_T("Created new keyset for CryptAcquireContext!\n"));
        }
        else {
          CDebugPrint::DebugPrint(_T("Error %u during CryptAcquireContext for machine set!\n"), GetLastError());
          return false;
        }
      }
      else CDebugPrint::DebugPrint(_T("Using the Machine Key Set\n"));
    }
    else {
      CDebugPrint::DebugPrint(_T("Error %u during CryptAcquireContext for user!\n"), GetLastError());
      return false;
    }
  }
  return true;
}

//-------------------------------------------------------------------------
// Purpose: Clean up object to release resources
//
// Input:   na
//
// Output:  true/false result
//
// Notes:   
//
bool CMSCryptoAPI::Uninitialize()
{
  if (DestroySessionKey()) {
  // now release crypto context
    if (m_hCryptProv) {
      if (!CryptReleaseContext(m_hCryptProv, 0)) {
        CDebugPrint::DebugPrint(_T("Error %u release context!\n"), GetLastError());
      }
      else m_hCryptProv=NULL;
    }
  }

  return m_hCryptProv==NULL;
}

//-------------------------------------------------------------------------
// Purpose: Destroy the session key if it exists
//
// Input:   na
//
// Output:  true / false result of it being destroyed
//
// Notes:   true is returned if it never existed
//
bool CMSCryptoAPI::DestroySessionKey()
{
  if (m_hSessionKey) {
    if (!CryptDestroyKey(m_hSessionKey)) {
      CDebugPrint::DebugPrint(_T("Error %u destroying session key!\n"), GetLastError());
    }
    else m_hSessionKey=NULL;
  }

  return m_hSessionKey==NULL;
}

//-------------------------------------------------------------------------
// Purpose: Create a session key using random data
//
// Input:   na
//
// Output:  true/false result
//
// Notes:   
//
bool CMSCryptoAPI::CreateRandomSessionKey()
{
  bool result=false;
  if (DestroySessionKey()) {
  // Create a random session key. 
    if (CryptGenKey(m_hCryptProv, CALG_AES_128, CRYPT_EXPORTABLE, &m_hSessionKey)) {
      // now populate IV
      result=SetIV();
    }
    else CDebugPrint::DebugPrint(_T("Error %u creating session key\n"), GetLastError());
  }

  return result;
}

//-------------------------------------------------------------------------
// Purpose: Import the session key from remote
//
// Input:   pkeyblob      - [i] the session key blob from remote
//          keyblobsize   - [i] size of the session key blob
//
// Output:  true/false result
//
// Notes:   
//
bool CMSCryptoAPI::ImportSessionKey(const BYTE* pkeyblob, DWORD keyblobsize)
{
  bool result=false;

  if (DestroySessionKey()) {
    // Get the handle to the exchange key. 
    HCRYPTKEY rsakeypair=CreateExchangeKey();
    if (rsakeypair) {
      // import the blob
      if (CryptImportKey(m_hCryptProv, pkeyblob, keyblobsize, rsakeypair, 0, &m_hSessionKey)) {
        result=true;
      }
      else CDebugPrint::DebugPrint(_T("Error %u importing session key\n"), GetLastError());
      // clean up
      if (!CryptDestroyKey(rsakeypair)) {
        CDebugPrint::DebugPrint(_T("Error %u destroying exchange key for import\n"), GetLastError());
      }
    }
  }

  return result;
}

//-------------------------------------------------------------------------
// Purpose: Export the session key for a remote to use
//
// Input:   publickeyblob       - [i] remote systems public key
//          publickeyblobsize   - [i] size of the public key
//          pblob               - [o] the created session key blob
//          blobsize            - [o] size of the created session key blob
//
// Output:  true/false result
//
// Notes:   
//
bool CMSCryptoAPI::ExportSessionKey(const BYTE* publickeyblob, DWORD publickeyblobsize, BYTE** pblob, DWORD *blobsize)
{
  // init output variables
  *pblob=NULL;
  *blobsize=0;

  bool result=false;

  // import the public key
  HCRYPTKEY hremotepubickey;
  if (!CryptImportKey(m_hCryptProv, publickeyblob, publickeyblobsize, NULL, AT_KEYEXCHANGE, &hremotepubickey)) {
    CDebugPrint::DebugPrint(_T("Error %u importing public key blob for keyexchange\n"), GetLastError());
  }
  else {
    // use that key to export the session key
    result=ExportKey(m_hSessionKey, hremotepubickey, SIMPLEBLOB, pblob, blobsize);

    // clean up
    if (!CryptDestroyKey(hremotepubickey)) {
      CDebugPrint::DebugPrint(_T("Error %u destroying remote public key\n"), GetLastError());
    }
  }
  return result;
}


//-------------------------------------------------------------------------
// Purpose: Create the RSA exchange key pair
//
// Input:   na
//
// Output:  the key handle or NULL if problem
//
// Notes:   Use CryptDestroyKey() when done with key
//
HCRYPTKEY CMSCryptoAPI::CreateExchangeKey()
{
  HCRYPTKEY rsakeypair=NULL;
  if (!CryptGetUserKey(m_hCryptProv, AT_KEYEXCHANGE, &rsakeypair)) {
    if (GetLastError()==NTE_NO_KEY) {
      // create a RSA private/public key pair
      CDebugPrint::DebugPrint(_T("Create RSA Key\n"));
      if (!CryptGenKey(m_hCryptProv, AT_KEYEXCHANGE, (KEYLENGTH<<KEYLENGTHSHIFT)|CRYPT_EXPORTABLE, &rsakeypair)) {
        CDebugPrint::DebugPrint(_T("Error %u creating RSA Key\n"), GetLastError());
      }
    }
    else CDebugPrint::DebugPrint(_T("Error %u getting user RSA key\n"), GetLastError());
  }

  return rsakeypair;
}


//-------------------------------------------------------------------------
// Purpose: Export a public key to a blob
//
// Input:   pbolb     - [o] the exported key blob
//          blobsize  - [o] size of the blob
//
// Output:  true/false result
//
// Notes:   The requester of a session key sends this over in order to
//          receive a protected session key
//
bool CMSCryptoAPI::ExportPublicKey(BYTE** pblob, DWORD* blobsize)
{
  bool result=false;

  // Get the handle to the exchange key. 
  HCRYPTKEY rsakeypair=CreateExchangeKey();
  if (rsakeypair) {
    // now export the public key
    result=ExportKey(rsakeypair, NULL, PUBLICKEYBLOB, pblob, blobsize);
    // clean up
    if (!CryptDestroyKey(rsakeypair)) {
      CDebugPrint::DebugPrint(_T("Error %u destroying rsa key\n"), GetLastError());
    }
  }

  return result;
}

//-------------------------------------------------------------------------
// Purpose: generic routine to export a key to a blob
//
// Input:   hkey        - [i] key to export
//          hexpkey     - [i] export key (remote public key)
//          blobtype    - [i] type of blob to create
//          pblob       - [o] blob created
//          blobsize    - [i] size of blob created
//
// Output:  true/false result
//
// Notes:   
//
bool CMSCryptoAPI::ExportKey(HCRYPTKEY hkey, HCRYPTKEY hexpkey, DWORD blobtype, BYTE** pblob, DWORD* blobsize)
{
  bool result=false;

  // get size of blob needed to export key
  if (!CryptExportKey(hkey, hexpkey, blobtype, 0, NULL, blobsize)) {
    CDebugPrint::DebugPrint(_T("Error %u getting size to export key with blob type %u\n"), GetLastError(), blobtype);
    // ensure stays zero
    *blobsize=0;
  }
  else {
    CDebugPrint::DebugPrint(_T("Export Key blob size %u\n"), *blobsize);
    // we have the size - create buffer for the exported session key
    if ((*pblob=new BYTE[*blobsize])!=NULL) {
      // success - now actually get the exported session key
      if (!CryptExportKey(hkey, hexpkey, blobtype, 0, *pblob, blobsize)) {
        CDebugPrint::DebugPrint(_T("Error %u exporting key with blob type %u\n"), GetLastError(), blobtype);
        // clean up
        delete[](*pblob);
        // return nothing
        *pblob=NULL;
        *blobsize=0;
      }
      else result=true;
    }
    else {
      CDebugPrint::DebugPrint(_T("Error allocating %u byte buffer to export key with blob type %u\n"), *blobsize, blobtype);
      *blobsize=0;
    }
  }
  return result;
}

//-------------------------------------------------------------------------
// Purpose: Encrypt a block of data
//
// Input:   pplaintext    - [i] plain text to encrypt
//          plaintextsize - [i] size of plain text
//          pciphertext   - [o][opt] buffer with cipher data
//          ciphertextsize- [io] size of cipher data
//
// Output:  true/false result
//
// Notes:   If pciphertext is NULL encryption is done in-place and the
//          ciphertextsize gives the total size of the input buffer
//
bool CMSCryptoAPI::EncryptData(BYTE* pplaintext, DWORD plaintextsize, BYTE** pciphertext, DWORD* ciphertextsize)
{
  bool result=false;

  if (pciphertext) {
    *ciphertextsize=0;
    *pciphertext=NULL;
    // Get the size of the output buffer needed
    if (CryptEncrypt(m_hSessionKey, NULL, TRUE, 0, NULL, ciphertextsize, 0)) {
      assert(*ciphertextsize>=plaintextsize);
      // create the output buffer
      if ((*pciphertext=new BYTE[*ciphertextsize])!=NULL) {
        // created buffer - copy over data
        memcpy(*pciphertext, pplaintext, plaintextsize);
      }
    }
  }
  else pciphertext=&pplaintext;

  if (*pciphertext!=NULL) {
    // do the encryption
    if (CryptEncrypt(m_hSessionKey, NULL, TRUE, 0, *pciphertext, &plaintextsize, *ciphertextsize)) {
      // ensure result of encryption matches size returned
      assert(plaintextsize==*ciphertextsize);
      result=true;
    }
    else CDebugPrint::DebugPrint(_T("Error %u encrypting data\n"), GetLastError());
  }
  else CDebugPrint::DebugPrint(_T("Unable to create buffer of %u bytes to encrypt data\n"), *ciphertextsize);
  
  return result;
}

//-------------------------------------------------------------------------
// Purpose: Decrypt a block of data
//
// Input:   pciphertext   - [i] buffer with cipher data
//          ciphertextsize- [i] size of cipher data
//          pplaintext    - [o][opt] plain text 
//          plaintextsize - [o] size of plain text
//
// Output:  true/false result
//
// Notes:   if plaintext is NULL the decryption occurs in-place
//
bool CMSCryptoAPI::DecryptData(BYTE* pciphertext, DWORD ciphertextsize, BYTE** pplaintext, DWORD* plaintextsize)
{
  bool result=false;

  *plaintextsize=0;
  if (pplaintext) {
    if ((*pplaintext=new BYTE[ciphertextsize])!=NULL) {
      // copy over data
      memcpy(*pplaintext, pciphertext, ciphertextsize);
    }
  }
  else pplaintext=&pciphertext;

  if (*pplaintext!=NULL) {
    // do the decryption
    *plaintextsize=ciphertextsize;
    if (CryptDecrypt(m_hSessionKey, NULL, TRUE, 0, *pplaintext, plaintextsize)) {
      result=true;
    }
    else CDebugPrint::DebugPrint(_T("Error %u decrypting data\n"), GetLastError());
  }
  else CDebugPrint::DebugPrint(_T("Unable to create buffer of %u bytes to decrypt data\n"), ciphertextsize);

  return result;
}

//-------------------------------------------------------------------------
// Purpose: Setup the IV of a key
//
// Input:   pbiv   - [i][opt] IV data to use
//
// Output:  true/false result
//
// Notes:   Random data is used if pbiv is not given
//
bool CMSCryptoAPI::SetIV(BYTE* pbiv)
{
  bool result=false;

  // check if we put in random data or use callers value
  if (pbiv==NULL) {
    // get block length
    DWORD dwblocklen=GetSessionKeyBlockSize();
    if (dwblocklen!=0) {
      // create buffer for random data
      BYTE* pbtemp;
      if ((pbtemp=new BYTE[dwblocklen])!=NULL) {
        // create random data
        if (CryptGenRandom(m_hCryptProv, dwblocklen, pbtemp)) {
          // set the IV data
          if (CryptSetKeyParam(m_hSessionKey, KP_IV, pbtemp, 0)) {
            result=true;
          }
          else CDebugPrint::DebugPrint(_T("Error %u on CryptSetKeyParam KP_IV\n"), GetLastError());
        }
        else CDebugPrint::DebugPrint(_T("Error %u on CryptGenRandom\n"), GetLastError());

        // clean up
        delete[] pbtemp;
      }
      else CDebugPrint::DebugPrint(_T("Unable to create buffer of %u bytes for rng\n"), dwblocklen);
    }
  }
  else {
    if (CryptSetKeyParam(m_hSessionKey, KP_IV, pbiv, 0)) {
      result=true;
    }
    else CDebugPrint::DebugPrint(_T("Error %u on CryptSetKeyParam KP_VI caller buffer\n"), GetLastError());
  }

  return result;
}

//-------------------------------------------------------------------------
// Purpose: Calculate size of buffer that can used for in place encryption
//
// Input:   plaintextsize  - [i] size of the plain text
//
// Output:  size of aligned buffer that can be used for in place encryption
//
// Notes:   
//
DWORD CMSCryptoAPI::CalcSizeForInPlaceEncryption(DWORD plaintextsize)
{
  // get the block size
  DWORD dwblocksize=GetSessionKeyBlockSize();
  // align up to it
  return (((plaintextsize+dwblocksize-1)/dwblocksize)*dwblocksize);
  // if always a factor of 2 then 
  // return ((plaintextsize+dwblocksize-1) & ~(dwblocksize-1));
}

//-------------------------------------------------------------------------
// Purpose: Get the block size for the session key
//
// Input:   na
//
// Output:  size of aligned buffer that can be used for in place encryption
//
// Notes:   
//
DWORD CMSCryptoAPI::GetSessionKeyBlockSize()
{
  // get block length
  DWORD dwblocklen=0;
  DWORD dwdatalen=sizeof(dwblocklen);
  if (CryptGetKeyParam(m_hSessionKey, KP_BLOCKLEN, (BYTE*) &dwblocklen, &dwdatalen, 0)) {
    // convert bits to bytes
    dwblocklen/=8;
  }
  else CDebugPrint::DebugPrint(_T("Error %u on CryptGetKeyParam KP_BLOCKLEN\n"), GetLastError());

  return dwblocklen;
}

Okay, I wondered if it could be the IV. I changed to not set it when creating the session key so it looks like:

bool CMSCryptoAPI::CreateRandomSessionKey()
{
  bool result=false;
  if (DestroySessionKey()) {
  // Create a random session key. 
    if (CryptGenKey(m_hCryptProv, CALG_AES_128, CRYPT_EXPORTABLE, &m_hSessionKey)) {
      // now populate IV
      //result=SetIV(); REMOVED
      result=true;
    }
    else CDebugPrint::DebugPrint(_T("Error %u creating session key\n"), GetLastError());
  }

  return result;
}

Now it works on both sides, but then that makes me wonder, should I be setting some fixed IV or does Windows handle that or is it always 0 or something like that?

By the way, enjoy the class for those like me who needed something, others may help improve on it. I tend to use nothrownew.obj but others probably using exception handling (which I'm not all that up to speed with).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM