Reputation:
There is a message handler (in C++ Builder) like this:
void __fastcall TMainForm::HandleMessages(tagMSG &Msg, bool &Handled)
{
RAND_event(Msg.message, Msg.wParam, Msg.lParam);
//...
}
Can RAND_event()
cause little-freezes in UI?
Thanks!
EDIT:
RAND_event()
is from OpenSSL, and here is its description:
RAND_event()
collects the entropy from Windows events such as mouse movements and other user interaction. It should be called with the iMsg, wParam and lParam arguments of all messages sent to the window procedure. It will estimate the entropy contained in the event message (if any), and add it to the PRNG. The program can then process the messages as usual.
Upvotes: 1
Views: 84
Reputation: 102376
Can RAND_event() cause little-freezes in UI?
Maybe. Entropy is added via RAND_add
, which means the entropy is extracted and then digested. So a hash function is being invoked on each call to RAND_add
.
If you are using the default RAND engine, then the engine used is the one from <openssl src>\crypto\rand\md_rand.c
. That means RAND_add
calls ssleay_rand_add
. The function is shown below.
If a lot of Windows messages are being sent, then I could envision a situation where a desktop slows down while digesting the entropy. The issue probably becomes more acute on a mobile operating system. A profiling tool should be able to give you more information.
I think an optimization may be available for RAND_event
on Windows... Messages should be accumulated and then added in batch. That is, accumulate the entropy and then call RAND_add
on the 8th or 16th or 32nd invocation.
The health of the generator should not suffer since there's so many Windows messages sent to an application. You can use Spy++ to get an idea of the number of messages being sent.
If you are using RAND_poll
somewhere, then you could see a noticeable block. For details on this, see Issue #2100: RAND_poll can be incredibly slow on Windows7 due to Heap32Next on the OpenSSL bug tracker.
static void ssleay_rand_add(const void *buf, int num, double add)
{
int i, j, k, st_idx;
long md_c[2];
unsigned char local_md[MD_DIGEST_LENGTH];
EVP_MD_CTX m;
int do_not_lock;
if (!num)
return;
/*
* (Based on the rand(3) manpage)
*
* The input is chopped up into units of 20 bytes (or less for
* the last block). Each of these blocks is run through the hash
* function as follows: The data passed to the hash function
* is the current 'md', the same number of bytes from the 'state'
* (the location determined by in incremented looping index) as
* the current 'block', the new key data 'block', and 'count'
* (which is incremented after each use).
* The result of this is kept in 'md' and also xored into the
* 'state' at the same locations that were used as input into the
* hash function.
*/
/* check if we already have the lock */
if (crypto_lock_rand) {
CRYPTO_THREADID cur;
CRYPTO_THREADID_current(&cur);
CRYPTO_r_lock(CRYPTO_LOCK_RAND2);
do_not_lock = !CRYPTO_THREADID_cmp(&locking_threadid, &cur);
CRYPTO_r_unlock(CRYPTO_LOCK_RAND2);
} else
do_not_lock = 0;
if (!do_not_lock)
CRYPTO_w_lock(CRYPTO_LOCK_RAND);
st_idx = state_index;
/*
* use our own copies of the counters so that even if a concurrent thread
* seeds with exactly the same data and uses the same subarray there's
* _some_ difference
*/
md_c[0] = md_count[0];
md_c[1] = md_count[1];
memcpy(local_md, md, sizeof md);
/* state_index <= state_num <= STATE_SIZE */
state_index += num;
if (state_index >= STATE_SIZE) {
state_index %= STATE_SIZE;
state_num = STATE_SIZE;
} else if (state_num < STATE_SIZE) {
if (state_index > state_num)
state_num = state_index;
}
/* state_index <= state_num <= STATE_SIZE */
/*
* state[st_idx], ..., state[(st_idx + num - 1) % STATE_SIZE] are what we
* will use now, but other threads may use them as well
*/
md_count[1] += (num / MD_DIGEST_LENGTH) + (num % MD_DIGEST_LENGTH > 0);
if (!do_not_lock)
CRYPTO_w_unlock(CRYPTO_LOCK_RAND);
EVP_MD_CTX_init(&m);
for (i = 0; i < num; i += MD_DIGEST_LENGTH) {
j = (num - i);
j = (j > MD_DIGEST_LENGTH) ? MD_DIGEST_LENGTH : j;
MD_Init(&m);
MD_Update(&m, local_md, MD_DIGEST_LENGTH);
k = (st_idx + j) - STATE_SIZE;
if (k > 0) {
MD_Update(&m, &(state[st_idx]), j - k);
MD_Update(&m, &(state[0]), k);
} else
MD_Update(&m, &(state[st_idx]), j);
/* DO NOT REMOVE THE FOLLOWING CALL TO MD_Update()! */
MD_Update(&m, buf, j);
/*
* We know that line may cause programs such as purify and valgrind
* to complain about use of uninitialized data. The problem is not,
* it's with the caller. Removing that line will make sure you get
* really bad randomness and thereby other problems such as very
* insecure keys.
*/
MD_Update(&m, (unsigned char *)&(md_c[0]), sizeof(md_c));
MD_Final(&m, local_md);
md_c[1]++;
buf = (const char *)buf + j;
for (k = 0; k < j; k++) {
/*
* Parallel threads may interfere with this, but always each byte
* of the new state is the XOR of some previous value of its and
* local_md (itermediate values may be lost). Alway using locking
* could hurt performance more than necessary given that
* conflicts occur only when the total seeding is longer than the
* random state.
*/
state[st_idx++] ^= local_md[k];
if (st_idx >= STATE_SIZE)
st_idx = 0;
}
}
EVP_MD_CTX_cleanup(&m);
if (!do_not_lock)
CRYPTO_w_lock(CRYPTO_LOCK_RAND);
/*
* Don't just copy back local_md into md -- this could mean that other
* thread's seeding remains without effect (except for the incremented
* counter). By XORing it we keep at least as much entropy as fits into
* md.
*/
for (k = 0; k < (int)sizeof(md); k++) {
md[k] ^= local_md[k];
}
if (entropy < ENTROPY_NEEDED) /* stop counting when we have enough */
entropy += add;
if (!do_not_lock)
CRYPTO_w_unlock(CRYPTO_LOCK_RAND);
}
Upvotes: 0
Reputation: 2516
When in doubt - verify the source code.
From \crypto\rand\rand_win.c
(OpenSSL 1.0.2f)
int RAND_event(UINT iMsg, WPARAM wParam, LPARAM lParam)
{
double add_entropy = 0;
switch (iMsg) {
case WM_KEYDOWN:
{
static WPARAM key;
if (key != wParam)
add_entropy = 0.05;
key = wParam;
}
break;
case WM_MOUSEMOVE:
{
static int lastx, lasty, lastdx, lastdy;
int x, y, dx, dy;
x = LOWORD(lParam);
y = HIWORD(lParam);
dx = lastx - x;
dy = lasty - y;
if (dx != 0 && dy != 0 && dx - lastdx != 0 && dy - lastdy != 0)
add_entropy = .2;
lastx = x, lasty = y;
lastdx = dx, lastdy = dy;
}
break;
}
readtimer();
RAND_add(&iMsg, sizeof(iMsg), add_entropy);
RAND_add(&wParam, sizeof(wParam), 0);
RAND_add(&lParam, sizeof(lParam), 0);
return (RAND_status());
}
As you can see, there are no loops, waits or similar stuff going on here, just some basic comparisons, assignments and additions. You can also verify for yourself some of the functions used inside RAND_event()
, like readtimer()
, but they also don't contain anything that could cause any significant performance decrease (at least when compared to using UI).
edit: Aww, I saw Remy's comment just now, my bad. Anyway, I'm keeping this just as an expanded answer (already existing as a comment) from a little (very little) bit different view.
Upvotes: 0