11.21.1 Problem
You need
entropy in a low-entropy environment and can prompt the user to move
the mouse to collect it.
11.21.2 Solution
On Windows, process all mouse events. Mix into an entropy pool the
current position of the mouse pointer on the screen, along with the
timestamp at which each event was processed. Estimate entropy based
upon your operating environment; see the considerations in Recipe
11.19.
11.21.3 Discussion
There can be a reasonable amount of entropy in mouse movement. The
entropy comes not just from where the mouse pointer is on the screen,
but from when each movement was made. In fact, the mouse
pointer's position on the screen can have very
little entropy in it, particularly in an environment where there may
be very little interaction from a local user. Most of the entropy
will come from the exact timing of the mouse movements.
The basic methodology is to mix the on-screen position of the mouse
pointer, along with a timestamp, into the entropy pool. We will
provide an example implementation in this section, where that
operation is merely hashing the data into a running SHA1 context.
The big issue is in estimating the amount of entropy in each mouse
movement. The first worry is that it is common for Windows to send
multiple mouse event messages with the same mouse pointer position.
That is easy to thwart, though. You simply do not measure any entropy
at all, unless the mouse pointer has actually changed position.
Ultimately, the amount of entropy you estimate getting from each
mouse movement should be related to the resolution of the clock you
use to measure mouse movements. In addition, you must consider
whether other processes on the system may be recording similar
information. (See Recipe 11.19 for a detailed discussion of entropy
estimation.)
The following code captures mouse events, hashes mouse pointer
positions and timestamps into a SHA1 context, and repeats until it is
believed that the requested number of bits of entropy has been
collected. A progress bar is also displayed that shows how much more
entropy needs to be collected.
Here is the resource definition for the progress dialog:
#include <windows.h>
#define SPC_MOUSE_DLGID 102
#define SPC_PROGRESS_BARID 1000
#define SPC_MOUSE_COLLECTID 1002
#define SPC_MOUSE_STATIC 1003
SPC_MOUSE_DLGID DIALOG DISCARDABLE 0, 0, 287, 166
STYLE DS_MODALFRAME | DS_NOIDLEMSG | DS_CENTER | WS_POPUP | WS_VISIBLE |
WS_CAPTION
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "Progress1",SPC_PROGRESS_BARID,"msctls_progress32",
PBS_SMOOTH | WS_BORDER,5,125,275,14
LTEXT "Please move your mouse over this dialog until the progress \
bar reports 100% and the OK button becomes active.",
SPC_MOUSE_STATIC,5,5,275,20
PUSHBUTTON "OK",IDOK,230,145,50,14,WS_DISABLED
CONTROL "",SPC_MOUSE_COLLECTID,"Static",SS_LEFTNOWORDWRAP |
SS_SUNKEN | WS_BORDER | WS_GROUP,5,35,275,80
END
Call the function SpcGatherMouseEntropy(
)to begin the process of collecting entropy. It
has the same signature as SpcGatherKeyboardEntropy(
) from Recipe 11.20. This function has the following
arguments:
- hInstance
-
Application instance handle normally obtained from the first argument
to WinMain( ), the program's
entry point.
- hWndParent
-
Handle to the dialog's parent window. It may be
specified as NULL, in which case the dialog will
have no parent.
- pbOutput
-
Buffer into which the collected entropy will be placed.
- cbOutput
-
Number of bytes of entropy to place into the output buffer. The
output buffer must be sufficiently large to hold the requested amount
of entropy. The number of bytes of entropy requested should not
exceed the size of the hash function used, which is SHA1 in the code
provided. SHA1 produces a 160-bit or 20-byte hash. If the requested
entropy is smaller than the hash function's output,
the hash function's output will be truncated.
SpcGatherMouseEntropy( ) uses the CryptoAPI to
hash the data collected from the mouse. It first acquires a context
object, then creates a hash object. After the arguments are
validated, the dialog resource is loaded by calling
DialogBoxParam( ), which creates a modal dialog. A
modal dialog can be used for capturing mouse messages instead of the
modeless dialog that was required for gathering keyboard entropy in
Recipe 11.20, because normal dialog processing
doesn't eat mouse messages the way it eats keyboard
messages.
Once the dialog is successfully created, the message handling
procedure handles
WM_MOUSEMOVE messages, which will be received
whenever the mouse pointer moves over the dialog or its controls. The
position of the mouse pointer is extracted from the message,
converted to screen coordinates, combined with a timestamp, and fed
into the hash object. If the current pointer position is the same as
the previous pointer position, it is not counted as entropy but is
added into the hash anyway. As mouse movements are collected, the
progress bar is updated, and when the requested amount of entropy has
been obtained, the OK button is enabled.
When the OK button is clicked, the dialog is destroyed, terminating
the message loop. The output from the hash function is copied into
the output buffer from the caller, and internal data is cleaned up
before returning to the caller.
#include <windows.h>
#include <wincrypt.h>
#include <commctrl.h>
#define SPC_ENTROPY_PER_SAMPLE 0.5
#define SPC_MOUSE_DLGID 102
#define SPC_PROGRESS_BARID 1000
#define SPC_MOUSE_COLLECTID 1003
#define SPC_MOUSE_STATIC 1002
typedef struct {
double dEntropy;
DWORD cbRequested;
POINT ptLastPos;
DWORD dwLastTime;
HCRYPTHASH hHash;
} SPC_DIALOGDATA;
typedef struct {
POINT ptMousePos;
DWORD dwTickCount;
} SPC_MOUSEPOS;
static BOOL CALLBACK MouseEntropyProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
SPC_MOUSEPOS MousePos;
SPC_DIALOGDATA *pDlgData;
switch (uMsg) {
case WM_INITDIALOG:
pDlgData = (SPC_DIALOGDATA *)lParam;
SetWindowLong(hwndDlg, DWL_USER, lParam);
SendDlgItemMessage(hwndDlg, SPC_PROGRESS_BARID, PBM_SETRANGE32, 0,
pDlgData->cbRequested);
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) = = IDOK && HIWORD(wParam) = = BN_CLICKED) {
EndDialog(hwndDlg, TRUE);
return TRUE;
}
break;
case WM_MOUSEMOVE:
pDlgData = (SPC_DIALOGDATA *)GetWindowLong(hwndDlg, DWL_USER);
if (pDlgData->dEntropy < pDlgData->cbRequested) {
MousePos.ptMousePos.x = LOWORD(lParam);
MousePos.ptMousePos.y = HIWORD(lParam);
MousePos.dwTickCount = GetTickCount( );
ClientToScreen(hwndDlg, &(MousePos.ptMousePos));
CryptHashData(pDlgData->hHash, (BYTE *)&MousePos, sizeof(MousePos), 0);
if ((MousePos.ptMousePos.x != pDlgData->ptLastPos.x ||
MousePos.ptMousePos.y != pDlgData->ptLastPos.y) &&
MousePos.dwTickCount - pDlgData->dwLastTime > 100) {
pDlgData->ptLastPos = MousePos.ptMousePos;
pDlgData->dwLastTime = MousePos.dwTickCount;
pDlgData->dEntropy += SPC_ENTROPY_PER_SAMPLE;
SendDlgItemMessage(hwndDlg, SPC_PROGRESS_BARID, PBM_SETPOS,
(WPARAM)pDlgData->dEntropy, 0);
if (pDlgData->dEntropy >= pDlgData->cbRequested) {
EnableWindow(GetDlgItem(hwndDlg, IDOK), TRUE);
SetFocus(GetDlgItem(hwndDlg, IDOK));
MessageBeep(0xFFFFFFFF);
}
}
}
return TRUE;
}
return FALSE;
}
BOOL SpcGatherMouseEntropy(HINSTANCE hInstance, HWND hWndParent,
BYTE *pbOutput, DWORD cbOutput) {
BOOL bResult = FALSE;
BYTE *pbHashData = 0;
DWORD cbHashData, dwByteCount = sizeof(DWORD);
HCRYPTHASH hHash = 0;
HCRYPTPROV hProvider = 0;
SPC_DIALOGDATA DialogData;
if (!CryptAcquireContext(&hProvider, 0, MS_DEF_PROV, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT)) goto done;
if (!CryptCreateHash(hProvider, CALG_SHA1, 0, 0, &hHash)) goto done;
if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashData, &dwByteCount,
0)) goto done;
if (cbOutput > cbHashData) goto done;
if (!(pbHashData = (BYTE *)LocalAlloc(LMEM_FIXED, cbHashData))) goto done;
DialogData.dEntropy = 0.0;
DialogData.cbRequested = cbOutput * 8;
DialogData.hHash = hHash;
DialogData.dwLastTime = 0;
GetCursorPos(&(DialogData.ptLastPos));
bResult = DialogBoxParam(hInstance, MAKEINTRESOURCE(SPC_MOUSE_DLGID),
hWndParent, MouseEntropyProc, (LPARAM)&DialogData);
if (bResult) {
if (!CryptGetHashParam(hHash, HP_HASHVAL, pbHashData, &cbHashData, 0))
bResult = FALSE;
else
CopyMemory(pbOutput, pbHashData, cbOutput);
}
done:
if (pbHashData) LocalFree(pbHashData);
if (hHash) CryptDestroyHash(hHash);
if (hProvider) CryptReleaseContext(hProvider, 0);
return bResult;
}
There are other ways to achieve the same result on Windows. For
example, entropy could be collected throughout your entire program by
installing a message hook or by moving the entropy collection code
out of MouseEntropyProc(
) and placing it into your
program's main message processing loop.
SpcGatherMouseEntropy( ) could then be modified to
operate in global state, presenting a dialog only if there is not a
sufficient amount of entropy collected already.
Note that the dialog uses a progress bar control. While this control
is a standard control on Windows, it is part of the common controls,
so you must initialize common controls before instantiating the
dialog; otherwise, DialogBoxParam(
) will fail inexplicably
(GetLastError( ) will return 0, which obviously is
not very informative). The following code demonstrates initializing
common controls and calling SpcGatherMouseEntropy(
):
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nShowCmd) {
BYTE pbEntropy[20];
INITCOMMONCONTROLSEX CommonControls;
CommonControls.dwSize = sizeof(CommonControls);
CommonControls.dwICC = ICC_PROGRESS_CLASS;
InitCommonControlsEx(&CommonControls);
SpcGatherMouseEntropy(hInstance, 0, pbEntropy, sizeof(pbEntropy));
return 0;
}
11.21.4 See Also
Recipe 11.19, Recipe 11.20
