123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- /*
- ------- Strong random data generation on a Macintosh (pre - OS X) ------
-
- -- GENERAL: We aim to generate unpredictable bits without explicit
- user interaction. A general review of the problem may be found
- in RFC 1750, "Randomness Recommendations for Security", and some
- more discussion, of general and Mac-specific issues has appeared
- in "Using and Creating Cryptographic- Quality Random Numbers" by
- Jon Callas (www.merrymeet.com/jon/usingrandom.html).
- The data and entropy estimates provided below are based on my
- limited experimentation and estimates, rather than by any
- rigorous study, and the entropy estimates tend to be optimistic.
- They should not be considered absolute.
- Some of the information being collected may be correlated in
- subtle ways. That includes mouse positions, timings, and disk
- size measurements. Some obvious correlations will be eliminated
- by the programmer, but other, weaker ones may remain. The
- reliability of the code depends on such correlations being
- poorly understood, both by us and by potential interceptors.
- This package has been planned to be used with OpenSSL, v. 0.9.5.
- It requires the OpenSSL function RAND_add.
- -- OTHER WORK: Some source code and other details have been
- published elsewhere, but I haven't found any to be satisfactory
- for the Mac per se:
- * The Linux random number generator (by Theodore Ts'o, in
- drivers/char/random.c), is a carefully designed open-source
- crypto random number package. It collects data from a variety
- of sources, including mouse, keyboard and other interrupts.
- One nice feature is that it explicitly estimates the entropy
- of the data it collects. Some of its features (e.g. interrupt
- timing) cannot be reliably exported to the Mac without using
- undocumented APIs.
- * Truerand by Don P. Mitchell and Matt Blaze uses variations
- between different timing mechanisms on the same system. This
- has not been tested on the Mac, but requires preemptive
- multitasking, and is hardware-dependent, and can't be relied
- on to work well if only one oscillator is present.
- * Cryptlib's RNG for the Mac (RNDMAC.C by Peter Gutmann),
- gathers a lot of information about the machine and system
- environment. Unfortunately, much of it is constant from one
- startup to the next. In other words, the random seed could be
- the same from one day to the next. Some of the APIs are
- hardware-dependent, and not all are compatible with Carbon (OS
- X). Incidentally, the EGD library is based on the UNIX entropy
- gathering methods in cryptlib, and isn't suitable for MacOS
- either.
- * Mozilla (and perhaps earlier versions of Netscape) uses the
- time of day (in seconds) and an uninitialized local variable
- to seed the random number generator. The time of day is known
- to an outside interceptor (to within the accuracy of the
- system clock). The uninitialized variable could easily be
- identical between subsequent launches of an application, if it
- is reached through the same path.
- * OpenSSL provides the function RAND_screen(), by G. van
- Oosten, which hashes the contents of the screen to generate a
- seed. This is not useful for an extension or for an
- application which launches at startup time, since the screen
- is likely to look identical from one launch to the next. This
- method is also rather slow.
- * Using variations in disk drive seek times has been proposed
- (Davis, Ihaka and Fenstermacher, world.std.com/~dtd/;
- Jakobsson, Shriver, Hillyer and Juels,
- www.bell-labs.com/user/shriver/random.html). These variations
- appear to be due to air turbulence inside the disk drive
- mechanism, and are very strongly unpredictable. Unfortunately
- this technique is slow, and some implementations of it may be
- patented (see Shriver's page above.) It of course cannot be
- used with a RAM disk.
- -- TIMING: On the 601 PowerPC the time base register is guaranteed
- to change at least once every 10 addi instructions, i.e. 10
- cycles. On a 60 MHz machine (slowest PowerPC) this translates to
- a resolution of 1/6 usec. Newer machines seem to be using a 10
- cycle resolution as well.
-
- For 68K Macs, the Microseconds() call may be used. See Develop
- issue 29 on the Apple developer site
- (developer.apple.com/dev/techsupport/develop/issue29/minow.html)
- for information on its accuracy and resolution. The code below
- has been tested only on PowerPC based machines.
- The time from machine startup to the launch of an application in
- the startup folder has a variance of about 1.6 msec on a new G4
- machine with a defragmented and optimized disk, most extensions
- off and no icons on the desktop. This can be reasonably taken as
- a lower bound on the variance. Most of this variation is likely
- due to disk seek time variability. The distribution of startup
- times is probably not entirely even or uncorrelated. This needs
- to be investigated, but I am guessing that it not a majpor
- problem. Entropy = log2 (1600/0.166) ~= 13 bits on a 60 MHz
- machine, ~16 bits for a 450 MHz machine.
- User-launched application startup times will have a variance of
- a second or more relative to machine startup time. Entropy >~22
- bits.
- Machine startup time is available with a 1-second resolution. It
- is predictable to no better a minute or two, in the case of
- people who show up punctually to work at the same time and
- immediately start their computer. Using the scheduled startup
- feature (when available) will cause the machine to start up at
- the same time every day, making the value predictable. Entropy
- >~7 bits, or 0 bits with scheduled startup.
- The time of day is of course known to an outsider and thus has 0
- entropy if the system clock is regularly calibrated.
- -- KEY TIMING: A very fast typist (120 wpm) will have a typical
- inter-key timing interval of 100 msec. We can assume a variance
- of no less than 2 msec -- maybe. Do good typists have a constant
- rhythm, like drummers? Since what we measure is not the
- key-generated interrupt but the time at which the key event was
- taken off the event queue, our resolution is roughly the time
- between process switches, at best 1 tick (17 msec). I therefore
- consider this technique questionable and not very useful for
- obtaining high entropy data on the Mac.
- -- MOUSE POSITION AND TIMING: The high bits of the mouse position
- are far from arbitrary, since the mouse tends to stay in a few
- limited areas of the screen. I am guessing that the position of
- the mouse is arbitrary within a 6 pixel square. Since the mouse
- stays still for long periods of time, it should be sampled only
- after it was moved, to avoid correlated data. This gives an
- entropy of log2(6*6) ~= 5 bits per measurement.
- The time during which the mouse stays still can vary from zero
- to, say, 5 seconds (occasionally longer). If the still time is
- measured by sampling the mouse during null events, and null
- events are received once per tick, its resolution is 1/60th of a
- second, giving an entropy of log2 (60*5) ~= 8 bits per
- measurement. Since the distribution of still times is uneven,
- this estimate is on the high side.
- For simplicity and compatibility across system versions, the
- mouse is to be sampled explicitly (e.g. in the event loop),
- rather than in a time manager task.
- -- STARTUP DISK TOTAL FILE SIZE: Varies typically by at least 20k
- from one startup to the next, with 'minimal' computer use. Won't
- vary at all if machine is started again immediately after
- startup (unless virtual memory is on), but any application which
- uses the web and caches information to disk is likely to cause
- this much variation or more. The variation is probably not
- random, but I don't know in what way. File sizes tend to be
- divisible by 4 bytes since file format fields are often
- long-aligned. Entropy > log2 (20000/4) ~= 12 bits.
-
- -- STARTUP DISK FIRST AVAILABLE ALLOCATION BLOCK: As the volume
- gets fragmented this could be anywhere in principle. In a
- perfectly unfragmented volume this will be strongly correlated
- with the total file size on the disk. With more fragmentation
- comes less certainty. I took the variation in this value to be
- 1/8 of the total file size on the volume.
- -- SYSTEM REQUIREMENTS: The code here requires System 7.0 and above
- (for Gestalt and Microseconds calls). All the calls used are
- Carbon-compatible.
- */
- /*------------------------------ Includes ----------------------------*/
- #include "Randomizer.h"
- // Mac OS API
- #include <Files.h>
- #include <Folders.h>
- #include <Events.h>
- #include <Processes.h>
- #include <Gestalt.h>
- #include <Resources.h>
- #include <LowMem.h>
- // Standard C library
- #include <stdlib.h>
- #include <math.h>
- /*---------------------- Function declarations -----------------------*/
- // declared in OpenSSL/crypto/rand/rand.h
- extern "C" void RAND_add (const void *buf, int num, double entropy);
- unsigned long GetPPCTimer (bool is601); // Make it global if needed
- // elsewhere
- /*---------------------------- Constants -----------------------------*/
- #define kMouseResolution 6 // Mouse position has to differ
- // from the last one by this
- // much to be entered
- #define kMousePositionEntropy 5.16 // log2 (kMouseResolution**2)
- #define kTypicalMouseIdleTicks 300.0 // I am guessing that a typical
- // amount of time between mouse
- // moves is 5 seconds
- #define kVolumeBytesEntropy 12.0 // about log2 (20000/4),
- // assuming a variation of 20K
- // in total file size and
- // long-aligned file formats.
- #define kApplicationUpTimeEntropy 6.0 // Variance > 1 second, uptime
- // in ticks
- #define kSysStartupEntropy 7.0 // Entropy for machine startup
- // time
- /*------------------------ Function definitions ----------------------*/
- CRandomizer::CRandomizer (void)
- {
- long result;
-
- mSupportsLargeVolumes =
- (Gestalt(gestaltFSAttr, &result) == noErr) &&
- ((result & (1L << gestaltFSSupports2TBVols)) != 0);
-
- if (Gestalt (gestaltNativeCPUtype, &result) != noErr)
- {
- mIsPowerPC = false;
- mIs601 = false;
- }
- else
- {
- mIs601 = (result == gestaltCPU601);
- mIsPowerPC = (result >= gestaltCPU601);
- }
- mLastMouse.h = mLastMouse.v = -10; // First mouse will
- // always be recorded
- mLastPeriodicTicks = TickCount();
- GetTimeBaseResolution ();
-
- // Add initial entropy
- AddTimeSinceMachineStartup ();
- AddAbsoluteSystemStartupTime ();
- AddStartupVolumeInfo ();
- AddFiller ();
- }
- void CRandomizer::PeriodicAction (void)
- {
- AddCurrentMouse ();
- AddNow (0.0); // Should have a better entropy estimate here
- mLastPeriodicTicks = TickCount();
- }
- /*------------------------- Private Methods --------------------------*/
- void CRandomizer::AddCurrentMouse (void)
- {
- Point mouseLoc;
- unsigned long lastCheck; // Ticks since mouse was last
- // sampled
- #if TARGET_API_MAC_CARBON
- GetGlobalMouse (&mouseLoc);
- #else
- mouseLoc = LMGetMouseLocation();
- #endif
-
- if (labs (mLastMouse.h - mouseLoc.h) > kMouseResolution/2 &&
- labs (mLastMouse.v - mouseLoc.v) > kMouseResolution/2)
- AddBytes (&mouseLoc, sizeof(mouseLoc),
- kMousePositionEntropy);
-
- if (mLastMouse.h == mouseLoc.h && mLastMouse.v == mouseLoc.v)
- mMouseStill ++;
- else
- {
- double entropy;
-
- // Mouse has moved. Add the number of measurements for
- // which it's been still. If the resolution is too
- // coarse, assume the entropy is 0.
- lastCheck = TickCount() - mLastPeriodicTicks;
- if (lastCheck <= 0)
- lastCheck = 1;
- entropy = log2l
- (kTypicalMouseIdleTicks/(double)lastCheck);
- if (entropy < 0.0)
- entropy = 0.0;
- AddBytes (&mMouseStill, sizeof(mMouseStill), entropy);
- mMouseStill = 0;
- }
- mLastMouse = mouseLoc;
- }
- void CRandomizer::AddAbsoluteSystemStartupTime (void)
- {
- unsigned long now; // Time in seconds since
- // 1/1/1904
- GetDateTime (&now);
- now -= TickCount() / 60; // Time in ticks since machine
- // startup
- AddBytes (&now, sizeof(now), kSysStartupEntropy);
- }
- void CRandomizer::AddTimeSinceMachineStartup (void)
- {
- AddNow (1.5); // Uncertainty in app startup
- // time is > 1.5 msec (for
- // automated app startup).
- }
- void CRandomizer::AddAppRunningTime (void)
- {
- ProcessSerialNumber PSN;
- ProcessInfoRec ProcessInfo;
-
- ProcessInfo.processInfoLength = sizeof(ProcessInfoRec);
- ProcessInfo.processName = nil;
- ProcessInfo.processAppSpec = nil;
-
- GetCurrentProcess (&PSN);
- GetProcessInformation (&PSN, &ProcessInfo);
- // Now add the amount of time in ticks that the current process
- // has been active
- AddBytes (&ProcessInfo, sizeof(ProcessInfoRec),
- kApplicationUpTimeEntropy);
- }
- void CRandomizer::AddStartupVolumeInfo (void)
- {
- short vRefNum;
- long dirID;
- XVolumeParam pb;
- OSErr err;
-
- if (!mSupportsLargeVolumes)
- return;
-
- FindFolder (kOnSystemDisk, kSystemFolderType, kDontCreateFolder,
- &vRefNum, &dirID);
- pb.ioVRefNum = vRefNum;
- pb.ioCompletion = 0;
- pb.ioNamePtr = 0;
- pb.ioVolIndex = 0;
- err = PBXGetVolInfoSync (&pb);
- if (err != noErr)
- return;
-
- // Base the entropy on the amount of space used on the disk and
- // on the next available allocation block. A lot else might be
- // unpredictable, so might as well toss the whole block in. See
- // comments for entropy estimate justifications.
- AddBytes (&pb, sizeof(pb),
- kVolumeBytesEntropy +
- log2l (((pb.ioVTotalBytes.hi - pb.ioVFreeBytes.hi)
- * 4294967296.0D +
- (pb.ioVTotalBytes.lo - pb.ioVFreeBytes.lo))
- / pb.ioVAlBlkSiz - 3.0));
- }
- /*
- On a typical startup CRandomizer will come up with about 60
- bits of good, unpredictable data. Assuming no more input will
- be available, we'll need some more lower-quality data to give
- OpenSSL the 128 bits of entropy it desires. AddFiller adds some
- relatively predictable data into the soup.
- */
- void CRandomizer::AddFiller (void)
- {
- struct
- {
- ProcessSerialNumber psn; // Front process serial
- // number
- RGBColor hiliteRGBValue; // User-selected
- // highlight color
- long processCount; // Number of active
- // processes
- long cpuSpeed; // Processor speed
- long totalMemory; // Total logical memory
- // (incl. virtual one)
- long systemVersion; // OS version
- short resFile; // Current resource file
- } data;
-
- GetNextProcess ((ProcessSerialNumber*) kNoProcess);
- while (GetNextProcess (&data.psn) == noErr)
- data.processCount++;
- GetFrontProcess (&data.psn);
- LMGetHiliteRGB (&data.hiliteRGBValue);
- Gestalt (gestaltProcClkSpeed, &data.cpuSpeed);
- Gestalt (gestaltLogicalRAMSize, &data.totalMemory);
- Gestalt (gestaltSystemVersion, &data.systemVersion);
- data.resFile = CurResFile ();
-
- // Here we pretend to feed the PRNG completely random data. This
- // is of course false, as much of the above data is predictable
- // by an outsider. At this point we don't have any more
- // randomness to add, but with OpenSSL we must have a 128 bit
- // seed before we can start. We just add what we can, without a
- // real entropy estimate, and hope for the best.
- AddBytes (&data, sizeof(data), 8.0 * sizeof(data));
- AddCurrentMouse ();
- AddNow (1.0);
- }
- //------------------- LOW LEVEL ---------------------
- void CRandomizer::AddBytes (void *data, long size, double entropy)
- {
- RAND_add (data, size, entropy * 0.125); // Convert entropy bits
- // to bytes
- }
- void CRandomizer::AddNow (double millisecondUncertainty)
- {
- long time = SysTimer();
- AddBytes (&time, sizeof(time), log2l (millisecondUncertainty *
- mTimebaseTicksPerMillisec));
- }
- //----------------- TIMING SUPPORT ------------------
- void CRandomizer::GetTimeBaseResolution (void)
- {
- #ifdef __powerc
- long speed;
-
- // gestaltProcClkSpeed available on System 7.5.2 and above
- if (Gestalt (gestaltProcClkSpeed, &speed) != noErr)
- // Only PowerPCs running pre-7.5.2 are 60-80 MHz
- // machines.
- mTimebaseTicksPerMillisec = 6000.0D;
- // Assume 10 cycles per clock update, as in 601 spec. Seems true
- // for later chips as well.
- mTimebaseTicksPerMillisec = speed / 1.0e4D;
- #else
- // 68K VIA-based machines (see Develop Magazine no. 29)
- mTimebaseTicksPerMillisec = 783.360D;
- #endif
- }
- unsigned long CRandomizer::SysTimer (void) // returns the lower 32
- // bit of the chip timer
- {
- #ifdef __powerc
- return GetPPCTimer (mIs601);
- #else
- UnsignedWide usec;
- Microseconds (&usec);
- return usec.lo;
- #endif
- }
- #ifdef __powerc
- // The timebase is available through mfspr on 601, mftb on later chips.
- // Motorola recommends that an 601 implementation map mftb to mfspr
- // through an exception, but I haven't tested to see if MacOS actually
- // does this. We only sample the lower 32 bits of the timer (i.e. a
- // few minutes of resolution)
- asm unsigned long GetPPCTimer (register bool is601)
- {
- cmplwi is601, 0 // Check if 601
- bne _601 // if non-zero goto _601
- mftb r3 // Available on 603 and later.
- blr // return with result in r3
- _601:
- mfspr r3, spr5 // Available on 601 only.
- // blr inserted automatically
- }
- #endif
|