// CoCrypt.cpp: implementation of the CCoCrypt class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "CoCrypt.h" #include "hmac.h" #include "BstrDebug.h" #include // ntohl, htonl #include #include #define PASSPORT_MAC_LEN 10 BOOL GenTextIV(unsigned char *pIV) // makes the assumption that IV is 8 bytes long // since the below code uses 3DES this is OK for now { int i; BOOL fResult; // generate a random IV fResult = RtlGenRandom(pIV, 8); if (!fResult) return FALSE; // castrate the random IV into base 64 characters (makes the IV only have // ~48 bits of entropy instead of 64 but that should be fine for (i = 0; i < 8; i++) { // mod the character to make sure its less than 62 pIV[i] = pIV[i] % 62; // add the appropriate character value to make it a base 64 character if (pIV[i] <= 9) pIV[i] = pIV[i] + '0'; else if (pIV[i] <= 35) pIV[i] = pIV[i] + 'a' - 10 ; else pIV[i] = pIV[i] + 'A' - 36 ; } return TRUE; } ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CBinHex CCoCrypt::m_binhex; CCoCrypt::CCoCrypt() : m_ok(FALSE) { } CCoCrypt::~CCoCrypt() { } BOOL CCoCrypt::Decrypt(LPWSTR rawData, UINT dataSize, BSTR *ppUnencrypted) { unsigned char *pb = NULL; unsigned char ivec[9]; ULONG lSize, i; unsigned char pad; unsigned char hmac[10]; HRESULT hr; BOOL fResult = FALSE; *ppUnencrypted = NULL; // must be kv + ivec + bh(hmac) long at LEAST if (sizeof(hmac) + sizeof(ivec) + 4 > dataSize) { goto Cleanup; } lSize = dataSize - sizeof(WCHAR); // allocate a buffer for the resulting data pb = new unsigned char[lSize]; if (NULL == pb) { goto Cleanup; } // decode from base 64 hr = m_binhex.PartFromWideBase64(rawData + 1 + 9, pb, &lSize); if (S_OK != hr) { goto Cleanup; } for (i = 0; i < 9; i++) ivec[i] = (unsigned char) rawData[i + 1]; pad = ivec[8]; // Now lsize holds the # of bytes outputted, which after hmac should be %8=0 if ((lSize - sizeof(hmac)) % 8 || lSize <= sizeof(hmac)) { goto Cleanup; } for (i = 0; i < lSize - sizeof(hmac); i+=8) { CBC(tripledes, DES_BLOCKLEN, pb + sizeof(hmac) + i, pb + sizeof(hmac) + i, &ks, DECRYPT, (BYTE*)ivec); } // Padding must be >0 and <8 //if (rawData[8]-65 > 7 || rawData[8] < 65) if((pad - 65) > 7 || pad < 65) { goto Cleanup; } // Now check hmac hmac_sha(m_keyMaterial, DES3_KEYSIZE, pb + sizeof(hmac), lSize - sizeof(hmac), hmac, sizeof(hmac)); if (memcmp(hmac, pb, sizeof(hmac)) != 0) { goto Cleanup; } // do a BSTR type allocation to accomodate calling code *ppUnencrypted = ALLOC_AND_GIVEAWAY_BSTR_BYTE_LEN((char*)(pb+sizeof(hmac)), lSize - sizeof(hmac) - (pad - 65)); if (NULL == *ppUnencrypted) { goto Cleanup; } fResult = TRUE; Cleanup: if (pb) { delete[] pb; } return fResult; } BOOL CCoCrypt::Encrypt(int keyVersion, LPSTR rawData, UINT dataSize, BSTR *ppEncrypted) { int cbPadding = 0; char ivec[9]; BOOL fResult; *ppEncrypted = NULL; // Find out how big the encrypted blob needs to be: // The final pack is: // BINHEX(HMAC+3DES(DATA+PAD)) // // So, we're concerned with the size of HMAC+3DES(DATA+PAD) // because BinHex will handle the rest if (dataSize % DES_BLOCKLEN) { cbPadding = (DES_BLOCKLEN - (dataSize % DES_BLOCKLEN)); // + PAD, if necessary } // gen the IV fResult = GenTextIV((unsigned char*)ivec); if (!fResult) { goto Cleanup; } encrypt(ivec, cbPadding, keyVersion, rawData, dataSize, ppEncrypted); fResult = TRUE; Cleanup: return fResult; } BOOL CCoCrypt::encrypt(char ivec[9], int cbPadding, int keyVersion, LPSTR rawData, UINT cbData, BSTR *ppEncrypted) { unsigned char *pb = NULL; char ivec2[8]; HRESULT hr; BOOL fResult = FALSE; // Compute HMAC+3DES(DATA+PAD) ivec[8] = (char) cbPadding + 65; // allocate a buffer for the resulting data // length of data + length of padding + size of HMAC + size of IV pb = new unsigned char[cbData + cbPadding + 10 + 8]; if (NULL == pb) { goto Cleanup; } memcpy(ivec2, ivec, DES_BLOCKLEN); memcpy(pb + PASSPORT_MAC_LEN, rawData, cbData); // copy data after the HMAC //randomize padding fResult = RtlGenRandom(pb + PASSPORT_MAC_LEN + cbData, cbPadding); if (!fResult) { fResult = FALSE; goto Cleanup; } // Compute HMAC hmac_sha(m_keyMaterial, DES3_KEYSIZE, pb + PASSPORT_MAC_LEN, cbData + cbPadding, pb, PASSPORT_MAC_LEN); for (int i = 0; i < (int)cbData + cbPadding; i+=8) { CBC(tripledes, DES_BLOCKLEN, pb + PASSPORT_MAC_LEN + i, pb + PASSPORT_MAC_LEN + i, &ks, ENCRYPT, (BYTE*)ivec2); } // Now we've got a buffer of blockSize ready to be binhexed, and have the key // version prepended keyVersion = keyVersion % 36; // 0 - 9 & A - Z char v = (char) ((keyVersion > 9) ? (55+keyVersion) : (48+keyVersion)); hr = m_binhex.ToBase64(pb, cbData + cbPadding + PASSPORT_MAC_LEN, v, ivec, ppEncrypted); if (S_OK != hr) { fResult = FALSE; goto Cleanup; } fResult = TRUE; Cleanup: if (NULL != pb) { delete[] pb; } return fResult; } void CCoCrypt::setKeyMaterial(BSTR newVal) { if (SysStringByteLen(newVal) != 24) { m_ok = FALSE; return; } memcpy(m_keyMaterial, (LPSTR)newVal, 24); tripledes3key(&ks, (BYTE*) m_keyMaterial); m_ok = TRUE; } unsigned char *CCoCrypt::getKeyMaterial(DWORD *pdwLen) { if (pdwLen) *pdwLen = 24; return m_keyMaterial; } int CCoCrypt::getKeyVersion(BSTR encrypted) { char c = (char) encrypted[0]; if (isdigit(c)) return (c-48); if(isalpha(c)) // Key version can be 0 - 9 & A - Z (36) { if(c > 'Z') //convert to uppert case w/o using rt lib. c -= ('a' - 'A'); return c - 65 + 10; //return (toupper(c)-65+10); } return -1; } int CCoCrypt::getKeyVersion(BYTE *encrypted) { char c = (char) encrypted[0]; if (isdigit(c)) return (c-48); if(isalpha(c)) // Key version can be 0 - 9 & A - Z (36) { if(c > 'Z') //convert to uppert case w/o using rt lib. c -= ('a' - 'A'); return c - 65 + 10; //return (toupper(c)-65+10); } return -1; } void CCoCrypt::setWideMaterial(BSTR kvalue) { m_bstrWideMaterial = kvalue; } BSTR CCoCrypt::getWideMaterial() { return m_bstrWideMaterial; }