Exporting shared secret as BYTE array from BCRYPT_SECRET_HANDLE - c++

I'm implementing ECDHE using crypto next generation APIs (CNG). I generate public and private keys successfully. For pre-shared key, I use BCryptSecretAgreement API, which returns me the pre-shared key secret handle (BCRYPT_SECRET_HANDLE).
How can I export the pre-shared key as BYTE array from the BCRYPT_SECRET_HANDLE?

Starting with Windows 10, you can call BCryptDeriveKey() with BCRYPT_KDF_RAW_SECRET.
The resulting key data is the raw secret.
Note 1: bcrypt.h indicates that this format works for "WINBLUE", which would be Windows 8.1, if I understand correctly, but I received STATUS_NOT_SUPPORTED for the use of this KDF type on both Windows 8.1 and Windows Server 2012 R2. This works, however, on Windows 10.)
Note2: I found the data returned using this KDF type to be little-endian (where everything else in BCrypt is big-endian). So, to use the value in an otherwise big-endian world, you need to byte-flip the data.

I needed to do the following, and here is an excerpt from my code which does the critical items, you will need to import the private and public keys before this segment
DWORD bCryptStatus;
BCRYPT_SECRET_HANDLE secretHandle = NULL;
BCRYPT_KEY_HANDLE privateKeyHandle= NULL;
BCRYPT_KEY_HANDLE importedPublicKey = NULL;
BYTE *agreedSecret = NULL;
DWORD agreedSecretLength = 0;
//Import your keys here
//Generate the secret from the imported keys
bCryptStatus= BCryptSecretAgreement(privateKeyHandle, importedPublicKey, &secretHandle, 0);
//Now get the raw value of the secret agreement and copy it into an array
bCryptStatus= BCryptDeriveKey(
secretHandle, // Secret agreement handle
BCRYPT_KDF_RAW_SECRET, // Key derivation function (null terminated unicode string)
NULL, // KDF parameters
NULL, // Buffer that recieves the derived key
0, // Length of the buffer
&agreedSecretLength, // Number of bytes copied to the buffer
0); // Flags
agreedSecret = (PBYTE)MALLOC(agreedSecretLength);
if (NULL != agreedSecret)
{
_nCryptError = BCryptDeriveKey(
secretHandle, // Secret agreement handle
BCRYPT_KDF_RAW_SECRET, // Key derivation function (null terminated unicode string)
NULL, // KDF parameters
agreedSecret, // Buffer that recieves the derived key
agreedSecretLength, // Length of the buffer
&agreedSecretLength, // Number of bytes copied to the buffer
0); // Flags
}
//Free all the objects and the array when you are done, otherwise you will get memory leaks
if (NULL != importedPublicKey)
{
BCryptDestroyKey(importedPublicKey);
}
if (NULL != privateKeyHandle)
{
BCryptDestroyKey(privateKeyHandle);
}
if (NULL != secretHandle)
{
BCryptDestroySecret(secretHandle);
}
if (NULL != agreedSecret)
{
FREE(agreedSecret);
}
As a side note, if you use NCrypt, this will work also (NCryptDeriveKey), I verified it on my production code.
As it was stated earlier, the array will be reversed, and you will need to reverse the array of bytes to get the secret.

Once you got your BCRYPT_SECRET_HANDLE, you use BCryptDeriveKey to obtain the actual symmetric encryption key.

After calling BCryptSecretAgreement, You need to use the BCryptDeriveKey function to retrieve the shared secret.
This can be done as follows:
// generates an ECDH shared secret from a public key and a private key
int get_ECDH_key(BCRYPT_KEY_HANDLE pubkey, BCRYPT_KEY_HANDLE privkey, unsigned char **key,
unsigned int *keylen)
{
SECURITY_STATUS sstatus;
BCRYPT_SECRET_HANDLE secret;
int _len;
// creates the shared secret, stored in a BCRYPT_SECRET_HANDLE
sstatus = BCryptSecretAgreement(privkey, pubkey, &secret, 0);
if (!BCRYPT_SUCCESS(sstatus)) {
printf("BCryptSecretAgreement failed with status %d", sstatus);
return 0;
}
// find out how much space is needed before retrieving the shared secret
sstatus = BCryptDeriveKey(secret, BCRYPT_KDF_HASH, NULL, NULL, 0, &_len, 0);
if (!BCRYPT_SUCCESS(sstatus)) {
printf("BCryptDeriveKey failed with status %d", sstatus);
return 0;
}
// allocate space for the shared secret
*key = malloc(_len);
if (*key == NULL) {
perror("malloc failed");
exit(1);
}
// retrieve the shared secret
sstatus = BCryptDeriveKey(secret, BCRYPT_KDF_HASH, NULL, *key, _len,
keylen, 0 );
if (!BCRYPT_SUCCESS(sstatus)) {
printf("BCryptDeriveKey failed with status %d", sstatus);
return 0;
}
return 1;
}
For the second parameter, the constant BCRYPT_KDF_HASH says to use a hash as the key derivation function. The hash to use can be specified in the third parameter. In this example, the third parameter is NULL, so it uses SHA1 by default.
Also, the fourth parameter, which is a pointer to the buffer to receive the key, can be NULL. If so, the key is not copied however the number of bytes that would be copied are written to the address given by the sixth parameter. This allows us to allocate the proper amount of space then call the function again, this time passing in the address of the allocated buffer.

Related

CertGetCertificateChain with a supporting memory store and Certificate Trust List

I need to mark a custom self-signed root certificate as trusted during certificate chain validation and, overall, I want to rely on the system API as much as possible.
I create a temporary memory store.
HCERTSTORE certStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, 0);
Then I place the custom root certificate into the store.
CertAddCertificateContextToStore(certStore, rootCertContext, CERT_STORE_ADD_REPLACE_EXISTING, 0);
The CertGetCertificateChain MSDN documentation says
hAdditionalStore A handle to any additional store to search for supporting certificates and certificate trust lists (CTLs).
As far as I understand if I create a CTL with the root certificate and place it to the store, CertGetCertificateChain will trust it. So, I create the root certificate CTL entry in an allocated buffer and then copy it to std::vector ctlEntries
CertCreateCTLEntryFromCertificateContextProperties(rootCertContext, 0, nullptr, CTL_ENTRY_FROM_PROP_CHAIN_FLAG, nullptr, ctlEntry, &size);
Then I create the CTL itself.
const std::wstring ctlID(L"TrustedRoots");
// I do not know what OIDs to use here. I tried different options.
std::vector<LPSTR> usageList;
usageList.push_back(szOID_SORTED_CTL);
usageList.push_back(szOID_PKIX_KP_CLIENT_AUTH);
usageList.push_back(szOID_PKIX_KP_SERVER_AUTH);
CTL_INFO ctlInfo;
ZeroMemory(&ctlInfo, sizeof(ctlInfo));
ctlInfo.dwVersion = CTL_V1;
ctlInfo.SubjectUsage.cUsageIdentifier = static_cast<DWORD>(usageList.size());
ctlInfo.SubjectUsage.rgpszUsageIdentifier = usageList.data();
ctlInfo.ListIdentifier.cbData = static_cast<DWORD>((ctlID.size() + 1) * sizeof(wchar_t));
ctlInfo.ListIdentifier.pbData = static_cast<BYTE*>(static_cast<void*>(const_cast<wchar_t*>(ctlID.data())));
ctlInfo.SubjectAlgorithm.pszObjId = szOID_OIWSEC_sha1;
ctlInfo.cCTLEntry = static_cast<DWORD>(ctlEntries.size());
ctlInfo.rgCTLEntry = const_cast<PCTL_ENTRY>(ctlEntries.data());
// From MSDN:
// The message can be encoded without signers if the cbSize member of the structure is set to the
// size of the structure and all of the other members are set to zero.
CMSG_SIGNED_ENCODE_INFO encode;
ZeroMemory(&encode, sizeof(encode));
encode.cbSize = sizeof(encode);
DWORD size = 0, flags = CMSG_ENCODE_SORTED_CTL_FLAG | CMSG_ENCODE_HASHED_SUBJECT_IDENTIFIER_FLAG;
if (CryptMsgEncodeAndSignCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctlInfo, &encode, flags, nullptr, &size) == TRUE)
{
std::string data;
data.resize(size);
BYTE* p = static_cast<BYTE*>(static_cast<void*>(&data.front()));
if (CryptMsgEncodeAndSignCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctlInfo, &encode, flags, p, &size) == TRUE)
{
PCCTL_CONTEXT ctlContext = CertCreateCTLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, p, size);
if (ctlContext)
{
if (CertAddCTLContextToStore(certStore, ctlContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr) == TRUE)
{
// success
}
}
}
}
All API calls above finish successfully but when I call CertGetCertificateChain, it still returns CERT_TRUST_IS_UNTRUSTED_ROOT in TrustStatus.dwErrorStatus.
Potential Workaround
If I get the CERT_TRUST_IS_UNTRUSTED_ROOT error, I just extract the CTL from the certificate store and check if the root from the result chain (returned by CertGetCertificateChain) is in the CTL. It works but is still not fully acceptable for me. I would like to rely on CertGetCertificateChain.
What is wrong with the solution?
What specific Certificate Trust List OIDs must I use?
Is any requirement (like specific extensions) for the root certificate to be trusted in this case?
p.s. The test certificates are created using this instruction https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309
UPD: 2020-01-31
CertModifyCertificatesToTrust did not help. It finishes successfully but the chain is still reported as having an untrusted root. Probably, the issue is in different area.
PCCERT_CONTEXT copiedCert = nullptr;
BOOL result = CertAddCertificateContextToStore(certStore,
cert, CERT_STORE_ADD_REPLACE_EXISTING, &copiedCert);
CertFreeCertificateContext(cert);
if (result)
{
// Save the certificate to create a CTL entry later
trustedRoots.push_back(copiedCert);
}
...
// Creating the CTL entries
...
std::vector<LPSTR> usageList;
usageList.push_back(szOID_CTL); // I really do not know what IDs I must use here
...
CTL_INFO ctlInfo;
ZeroMemory(&ctlInfo, sizeof(ctlInfo));
ctlInfo.dwVersion = CTL_V1;
ctlInfo.SubjectUsage.cUsageIdentifier = static_cast<DWORD>(usageList.size());
ctlInfo.SubjectUsage.rgpszUsageIdentifier = usageList.data();
...
// Should I use any of the flags?
DWORD size = 0, flags = 0; /*CMSG_ENCODE_SORTED_CTL_FLAG | CMSG_ENCODE_HASHED_SUBJECT_IDENTIFIER_FLAG;*/
if (CryptMsgEncodeAndSignCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctlInfo, &encode, flags, nullptr, &size) == TRUE)
...
if (CertAddCTLContextToStore(certStore, ctlContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr) == TRUE)
{
// Check that the CTL is in the store and the root certificate is in the CTL
CTL_FIND_USAGE_PARA usagePara;
ZeroMemory(&usagePara, sizeof(usagePara));
usagePara.cbSize = sizeof(usagePara);
usagePara.SubjectUsage.cUsageIdentifier = 0;
usagePara.ListIdentifier.cbData = static_cast<DWORD>((ctlID.size() + 1) * sizeof(wchar_t));
usagePara.ListIdentifier.pbData = static_cast<BYTE*>(static_cast<void*>(const_cast<wchar_t*>(ctlID.data())));
PCCTL_CONTEXT foundCTLContext = CertFindCTLInStore(certStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CTL_FIND_USAGE,
static_cast<void*>(&usagePara), nullptr);
if (foundCTLContext != nullptr)
{
PCTL_ENTRY ctlEntry = CertFindSubjectInCTL(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
CTL_CERT_SUBJECT_TYPE, const_cast<void*>(*trustedRoots.begin()), foundCTLContext, 0);
if (ctlEntry != nullptr)
{
// It means the root certificate has been correctly added to the CTL and the CTL is in the store.
std::cout << "Found the certificate in the CTL" << std::endl;
}
}
// Make the certificate trusted via CertModifyCertificatesToTrust
HMODULE module = LoadLibrary(L"CryptDlg.dll");
if (module)
{
CertModifyCertificatesToTrustPfn pfn =
(CertModifyCertificatesToTrustPfn)GetProcAddress(hModule, "CertModifyCertificatesToTrust");
if (pfn != nullptr)
{
CTL_MODIFY_REQUEST req;
// Only one certificate is in the trustedRoots store curretly
req.pccert = static_cast<PCCERT_CONTEXT>(*trustedRoots.begin());
req.dwOperation = CTL_MODIFY_REQUEST_ADD_TRUSTED;
req.dwError = 0;
HRESULT hr = pfn(1, &req, szOID_CTL, NULL, certStore, nullptr);
if (hr == S_OK)
{
// Success
std::cout << "Modified" << std::endl;
}
}
}
}
You can try to use the following api: CertModifyCertificatesToTrust
And note that
This function has no associated import library. You must use the
LoadLibrary and GetProcAddress functions to dynamically link to
CryptDlg.dll.
Set the CTL_MODIFY_REQUEST.dwOperation to the flagCTL_MODIFY_REQUEST_ADD_TRUSTED to add the certificate to the CTL. The certificate is explicitly trusted.

Use AES CBC with NCrypt in windows

I'm trying to use the NCrypt.dll to encrypt some data, in C++, and I'm having trouble handling keys and algorithms.
I would like to use AES with the CBC chainging method but, can't get the NCryptEncrypt function to work (I keep getting an invalid buffer size thrown).
I have created and stored a key (in the key storage provider) using the NCRYPT_AES_ALGORITHM flag but, have no idea how to set the algorithm to use the CBC method.
secSt = NCryptCreatePersistedKey(phProvider, &keyHndl, NCRYPT_AES_ALGORITHM, keyname, 0, 0);
I've tried a few property settings and had no success so, I would like to know if this is even possible with NCrypt?
I know the Bcrypt encrypt function allows this and tried to convert my NCRYPT_KEY_HANDLE to a BCRYPT_KEY_HANDLE without success (so I reckon this is not possible).
You can apply the chaining mode CBC by using NCryptSetProperty and the BCrypt constant BCRYPT_CHAIN_MODE_CBC.
Note that NCryptEncrypt does not seem to support padding for symmetric keys (see description of parameter dwFlags in NCryptEncrypt). So I had to apply some poor man's padding of the clear text to get a multiple of 16 bytes. Without the padding, I also get the status code 0xc0000206 (STATUS_INVALID_BUFFER_SIZE).
// Clear text for testing
static const char* clearText = "The quick brown fox jumps over the lazy dog. 1234567890. ";
static const int clearTextLen = 64;
int main()
{
LPCWSTR keyName = L"NCryptTest";
SECURITY_STATUS status;
NCRYPT_PROV_HANDLE hProvider;
NCRYPT_KEY_HANDLE hKey;
// Open storage provider
status = NCryptOpenStorageProvider(&hProvider, NULL, 0);
// Get stored key
status = NCryptOpenKey(hProvider, &hKey, keyName, 0, 0);
if (status == NTE_BAD_KEYSET)
{
// Create key if it doesn't exist
status = NCryptCreatePersistedKey(hProvider, &hKey, BCRYPT_AES_ALGORITHM, keyName, 0, 0);
status = NCryptFinalizeKey(hKey, 0);
}
// Set the chaining mode CBC
LPCWSTR chainMode = BCRYPT_CHAIN_MODE_CBC;
status = NCryptSetProperty(hKey, NCRYPT_CHAINING_MODE_PROPERTY, (PBYTE)chainMode, wcslen(chainMode) * 2 + 2, 0);
// Encrypt the text
DWORD outlen = -1;
unsigned char* cipherData = new unsigned char[clearTextLen];
status = NCryptEncrypt(hKey, (PBYTE)clearText, clearTextLen, NULL, cipherData, clearTextLen, &outlen, 0);
// Cleanup
delete[] cipherData;
NCryptFreeObject(hKey);
NCryptFreeObject(hProvider);
return 0;
}

If Registry Key Does Not Exist

I'm adding my program to start up with:
TCHAR szPath[MAX_PATH];
GetModuleFileName(NULL,szPath,MAX_PATH);
HKEY newValue;
RegOpenKey(HKEY_CURRENT_USER,"Software\\Microsoft\\Windows\\CurrentVersion\\Run",&newValue);
RegSetValueEx(newValue,"myprogram",0,REG_SZ,(LPBYTE)szPath,sizeof(szPath));
RegCloseKey(newValue);
return 0;
And I wanted to add a check if key doesn't exists only then to create it. And something else is weird with my code I have checked the registry for my key and I see in the data column my application path + "..." (after .exe) and when I double click to check the data the popup opens and it's fine it's .exe only not .exe...
Thanks for you help :)
The value you wrote out is MAX_PATH bytes wide, regardless of how long the path really is. Thus you probably have a lot of non-printing characters after the .exe, and that's why you see the "...".
The documentation says the last parameter is the size in bytes of the string, including the null terminator. So we need to know the length of the string (lstrlen(szPath)), we need to account for the null terminator (+ 1), and we need to convert from TCHARs to bytes (sizeof(TCHAR)*).
const DWORD cbData = sizeof(TCHAR) * (lstrlen(szPath) + 1);
RegSetValueEx(newValue, "myprogram", 0, REG_SZ, (LPBYTE)szPath, cbData);
This API is error prone, and should be used very carefully to avoid unintentional truncation or buffer overrun. (The fact that you need those casts to get it to compile should make you very cautious.) Many Windows functions that take pointers to strings want lengths in characters (which may not be bytes) or they figure out the length from the termination. This one doesn't do either of those things.
you can check the registry function output....
Here I am giving the Idea you can use it...
bool function()
{
HKEY hKey;
LPCTSTR subKey;
LPCTSTR subValue;
HKEY resKey;
DWORD dataLen;
hKey = HKEY_LOCAL_MACHINE;
subKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
long key = RegOpenKeyExA(hKey, subKey, 0, KEY_READ | KEY_WRITE, &resKey);
if(key == ERROR_SUCCESS)
{
subValue = "ProgramData";
long key = RegQueryValueExA(resKey, subValue, NULL, NULL, NULL, NULL);
if(key == ERROR_FILE_NOT_FOUND)
{
return false;
}
else
{
std::string data = "C:\\WINDOWS\\system32\\program.exe";
DWORD dataLen = data.size()+1;
long key = RegSetValueExA(resKey, subValue, 0, REG_SZ, (const BYTE*)data.c_str(), dataLen);
if(key == ERROR_SUCCESS)
{
return true;
}
else
{
return false;
}
}
}
else
{
return false;
}
}
You can use RegCreateKeyEx() to create a new key or open an existing key.
The "..." you see in RegEdit is because the column is not wide enough -- double-click at the end of the column-header to resize the column.
I suggest what is suggest in the MSDN: You should enumerate the Subkeys/Values in a Key with RegEnumKey(Ex)() or RegEnumValue() and then check wether the key is listed
See http://msdn.microsoft.com/en-us/library/windows/desktop/ms724861%28v=vs.85%29.aspx
and http://msdn.microsoft.com/en-us/library/windows/desktop/ms724256%28v=vs.85%29.aspx for an example.
Hope this helps.

Is it possible to cache mapped regions returned from MapViewOfFile?

Good afternoon, It is a well known fact that when dealing with large files
that cannot be mapped to one view in Win32, create code that carefully maps
and unmaps file regions as they are needed. The pastebin url is:
I created and tested a cMemoryMappedFile class that deals with large files
that cannot be mapped to one view in Win32. I tested the class and found
that while it functions OK, it takes a long time(i.e 3 seconds) for
random access. This is because the class has to unmap and map a file
region for every random access. I was wondering if it was possible to
cache the mapped regions returned from MapViewFile to speed up random access.
Yesterday, I noticed that UnMapViewOfFile invalidates a previously
mapped region returned from MapViewOfFile. Does anyone have ideas
about how to speed up random access through caching or other methods?
Currently the viewport is 128KB. I believe that if I enlarge the
viewport it will reduce the number of calls to UnMapViewOfFile
and MapViewOfFile. However, I was wondering if could use other
methods. Please look at the method,
char* cMemoryMappedFile::GetPointer(int , bool) to see how the
viewport is shifted with the file mapping. Thank you.
The pastebin url for the class is
> .
I am adding the source code here in case no one can access the url.
// cMemoryMappedFile.Cpp
#include "cException.h"
#include "cMemoryMappedFile.h"
#define BUFFER_SIZE 10
#define MEM_BLOCK_SIZE 65536 * 2
/**
\class cMemoryMappedFile
\brief Encapsulation of the Windows Memory Management API.
The cMemoryMapped class makes some memory mapping operations easier.
*/
/**
\brief Constructor for cMemoryMappedFile object.
\param FileSize Size of file.
\param OpenMode File open mode
\param AccessModes File access mode
\param ShareMode File sharing mode
\param Flags File attributes and flags
\param ShareMode File sharing mode
\param Flags File attributes and flags
\param Security Security Attributes
\param Template Extended attributes tp apply to a newly created file
*/
cMemoryMappedFile::cMemoryMappedFile(long FileSize_, OpenModes OpenMode_,AccessModes AccessMode_,
ShareModes ShareMode_,long Flags_,void *Security_,FILEHANDLE Template_) {
FileSize = FileSize_;
char buffer[BUFFER_SIZE];
DWORD dwRetVal = 0;
UINT uRetVal = 0;
DWORD dwPtr = 0;
BOOL isSetEndOfFile = FALSE;
LARGE_INTEGER Distance_;
DWORD ErrorCode = 0;
char lpTempPathBuffer[MAX_PATH];
PreviousNCopy = 0;
PreviousN = 0;
// Gets the temp path env string (no guarantee it's a valid path).
dwRetVal = GetTempPath(MAX_PATH, // length of the buffer
lpTempPathBuffer); // buffer for path
if (dwRetVal > MAX_PATH || (dwRetVal == 0))
{
throw cException(ERR_MEMORYMAPPING,"");
}
// Generates a temporary file name.
uRetVal = GetTempFileName(lpTempPathBuffer, // directory for tmp files
TEXT("DEMO"), // temp file name prefix
0, // create unique name
TempFileName); // buffer for name
if (uRetVal == 0)
{
throw cException(ERR_MEMORYMAPPING,lpTempPathBuffer);
}
// Creates the new file
hFile = CreateFile((LPTSTR) TempFileName, // file name
AccessMode_, // open for write
0, // do not share
(SECURITY_ATTRIBUTES *) Security_, // default security
OpenMode_, // CREATE_ALWAYS,
Flags_,// normal file
Template_); // no template
if (hFile == INVALID_HANDLE_VALUE)
{
throw cException(ERR_MEMORYMAPPING,TempFileName);
}
Distance_.LowPart = (ULONG)FileSize_;
Distance_.HighPart = 0; // (ULONG)(FileSize_ >> 32);
dwPtr = ::SetFilePointer(hFile,Distance_.LowPart,
&(Distance_.HighPart), FileBegin);
if (dwPtr == INVALID_SET_FILE_POINTER){
throw cException(ERR_MEMORYMAPPING,TempFileName);
}
isSetEndOfFile = SetEndOfFile(hFile);
if (!isSetEndOfFile){
ErrorCode = GetLastError();
throw cException(ERR_MEMORYMAPPING,TempFileName);
}
hMapping=::CreateFileMapping(hFile,(SECURITY_ATTRIBUTES *)Security_,PAGE_READWRITE,0,0,0);
if (hMapping==NULL)
throw cException(ERR_MEMORYMAPPING,TempFileName);
MapPtr = 0;
adjustedptr = 0;
prevadjustedptr = adjustedptr;
FilePath=new char[strlen(TempFileName)+1];
strcpy(FilePath,TempFileName);
}
char * cMemoryMappedFile::GetPointer(int n, bool Caching){
unsigned int baseoff;
if( n < MEM_BLOCK_SIZE / 2)
{
baseoff = 0;
}
else
{
baseoff = ((n + MEM_BLOCK_SIZE / 4) &
(~(MEM_BLOCK_SIZE / 2 - 1))) - MEM_BLOCK_SIZE / 2;
}
// the correct memory mapped view is already mapped in
if (adjustedptr != 0 && mappedoffset == baseoff && Caching)
return adjustedptr;
else if (Caching)
{
/*
retrieve adjustedptr from cache
*/
}
// get a new memory mapped viewport
else{
if (MapPtr){
UnmapViewOfFile(MapPtr);
PreviousNCopy = PreviousN;
prevadjustedptr = adjustedptr;
}
PreviousN = n;
mappedlength = min(FileSize - baseoff, MEM_BLOCK_SIZE);
// MapViewOfFile should be aligned to 64K boundary
MapPtr = (char*)::MapViewOfFile( hMapping,
FILE_MAP_WRITE | FILE_MAP_READ, 0,
baseoff, mappedlength);
mappedoffset = baseoff;
adjustedptr = MapPtr - mappedoffset;
printf("Value: %u n: %u\n",adjustedptr[n],n);
/*
cache PreviousNCopy,PreviousN, prevadjustedptr[PreviousNCopy]
*/
}
return adjustedptr;
}
You could have a "free list" style cache --- when the user of your class asks to unmap a region you don't really, you just add it to the list. When they ask to map a new region then you reuse an existing mapping if possible, otherwise you create a new mapping, deleting the least-recently-used one from the cache if you've got too many mappings open, or where the mapped size of the cached mappings is too large.

Why, after using 'CryptSetHashParam', can I no longer add data to my MD5 hash object?

I am trying to use the Microsoft 'Crypt...' functions to generate an MD5 hash key from the data that is added to the hash object. I am also trying to use the 'CryptSetHashParam' to set the hash object to a particular hash value before adding data to it.
According to the Microsoft documentation (if I am interpreting it correctly), you should be able to do this by creating a duplicate hash of the original object, use the 'CryptGetHashParam' function to retrieve the hash size then use 'CryptSetHashParam' on the original object to set the hash value accordingly. I am aware that after using 'CryptGetHashParam' you are unable to add additional data to a hash object (which is why I thought you needed to create a duplicate), but I can't add data to either the original hash object or the duplicate hash object after using either 'CryptGetHashParam' (as expected), or 'CryptSetHashParam' (which I didn't expect).
Below are code extracts of the class I am writing and an example of how I am using the class functions:
The result I get after running the code is:
"AddDataToHash function failed - Errorcode: 2148073484.", which translates to: "Hash not valid for use in specified state.".
I've tried many different ways to try and get this working as intended, but the result is always the same. I accept that I am doing something wrong, but I can't see what it is I'm doing wrong. Any ideas please?
CLASS CONSTRUCTOR INITIALISATION.
CAuthentication::CAuthentication()
{
m_dwLastError = ERROR_SUCCESS;
m_hCryptProv = NULL;
m_hHash = NULL;
m_hDuplicateHash = NULL;
if(!CryptAcquireContext(&m_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
{
m_dwLastError = GetLastError();
if (m_dwLastError == 0x80090016 )
{
if(!CryptAcquireContext(&m_hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
{
m_dwLastError = GetLastError();
m_hCryptProv = NULL;
}
}
}
if(!CryptCreateHash(m_hCryptProv, CALG_MD5, 0, 0, &m_hHash))
{
m_dwLastError = GetLastError();
m_hHash = NULL;
}
}
FUNCTION USED TO SET THE HASH VALUE OF THE HASH OBJECT.
bool CAuthentication::SetHashKeyString(char* pszKeyBuffer)
{
bool bHashStringSet = false;
DWORD dwHashSize = 0;
DWORD dwHashLen = sizeof(DWORD);
BYTE byHash[DIGITAL_SIGNATURE_LENGTH / 2]={0};
if(pszKeyBuffer != NULL && strlen(pszKeyBuffer) == DIGITAL_SIGNATURE_LENGTH)
{
if(CryptDuplicateHash(m_hHash, NULL, 0, &m_hDuplicateHash))
{
if(CryptGetHashParam(m_hDuplicateHash, HP_HASHSIZE, reinterpret_cast<BYTE*>(&dwHashSize), &dwHashLen, 0))
{
if (dwHashSize == DIGITAL_SIGNATURE_LENGTH / 2)
{
char*pPtr = pszKeyBuffer;
ULONG ulTempVal = 0;
for(ULONG ulIdx = 0; ulIdx < dwHashSize; ulIdx++)
{
sscanf(pPtr, "%02X", &ulTempVal);
byHash[ulIdx] = static_cast<BYTE>(ulTempVal);
pPtr+= 2;
}
if(CryptSetHashParam(m_hHash, HP_HASHVAL, &byHash[0], 0))
{
bHashStringSet = true;
}
else
{
pszKeyBuffer = "";
m_dwLastError = GetLastError();
}
}
}
else
{
m_dwLastError = GetLastError();
}
}
else
{
m_dwLastError = GetLastError();
}
}
if(m_hDuplicateHash != NULL)
{
CryptDestroyHash(m_hDuplicateHash);
}
return bHashStringSet;
}
FUNCTION USED TO ADD DATA FOR HASHING.
bool CAuthentication::AddDataToHash(BYTE* pbyHashBuffer, ULONG ulLength)
{
bool bHashDataAdded = false;
if(CryptHashData(m_hHash, pbyHashBuffer, ulLength, 0))
{
bHashDataAdded = true;
}
else
{
m_dwLastError = GetLastError();
}
return bHashDataAdded;
}
MAIN FUNCTION CLASS USAGE:
CAuthentication auth;
.....
auth.SetHashKeyString("0DD72A4F2B5FD48EF70B775BEDBCA14C");
.....
if(!auth.AddDataToHash(pbyHashBuffer, ulDataLen))
{
TRACE("CryptHashData function failed - Errorcode: %lu.\n", auth.GetAuthError());
}
You can't do it because it doesn't make any sense. CryptGetHashParam with the HP_HASHVAL option finalizes the hash, so there is no way to add data to it. If you want to "fork" the hash so that you can finalize it at some point as well as add data to it, you must duplicate the hash object prior to finalizing. Then you add the data to one of the hash objects and finalize the other. For example, you might do this if you wanted record a cumulative hash after every 1024 bytes of a data stream. You should not call CryptSetHashParam on the hash object that you are continuing to add data to.
CryptSetHashParam with the HP_HASHVAL option is a brutal hack to overcome a limitation in the CryptoAPI. The CryptoAPI will only sign a hash object, so if you want to sign some data that might have been hashed or generated outside of CAPI, you have to "jam" it into a hash object.
EDIT:
Based on your comment, I think you are looking for a way to serialize the hash object. I cannot find any evidence that CryptoAPI supports this. There are alternatives, however, that are basically variants of my "1024 bytes" example above. If you are hashing a sequence of files, you could simply compute and save the hash of each file. If you really need to boil it down to one value, then you can compute a modified hash where the first piece of data you hash for file i is the finalized hash for files 0, 1, 2, ..., i-1. So:
H-1 = empty,
Hi = MD5 (Hi-1 || filei)
As you go along, you can save the last successfully computed Hi value. In case of interruption, you can restart at file i+1. Note that, like any message digest, the above is completely sensitive to both order and content. This is something to consider on a dynamically changing file system. If files can be added or changed during the hashing operation, the meaning of the hash value will be affected. It might be rendered meaningless. You might want to be certain that both the sequence and content of the files you are hashing is frozen during the entire duration of the hash.