The entropy in a session identifier is the entropy of the underlying number source; a PRNG might yield good random words of 32 bits, each of them having 32 bits of entropy (however, as @forrest pointed out, entropy doesn't "carry" - it will grow until it reaches the size of the PRNG's internal state, and theoretically that state could be back-calculated given sufficient output. From then on, the PRNG output is predictable, with an entropy of zero. A "truly random" generator has an internal state of infinite size).
This means that each bit is more or less completely unpredictable (the 'more or less' stems from the PRNG being a (P)seudo random number generator).
When this 32 bit number is encoded in a string identifier, usually by hex encoding in ascii or utf8, it becomes something like "1A9F72EF"
. Now these are eight bytes, so 64 bits, yet they still contain the same entropy as before, which was 32 bits.
If we had used base64 encoding, our identifier would have had 6 bits of entropy for each character, i.e. 8 bits; so the entropy would be 3/4ths of the length in bits of the identifier (or the identifier would be 8/6ths the length of its binary representation).
That's where OWASP's "1/2" came from.
Now, the entropy of a source cannot be determined by observing a single instance; usually you need to know with a certainty how the source works. A random source is by definition pure entropy. A constant source (always returns 42) has an entropy of zero even when "42" is represented by six or eight bits.
Using a very rough and untrustworthy approximation, the entropy is twice the number of bits that change on average from one representation to the next, provided that the changes are random and nonrepeating. A sequence of [ 00FF, FF00, 00FF, FF00 ... ] would have all its bits changing, but there's only two symbols in the sequence, so one bit is sufficient to describe it fully; the internal state of the generator is one bit wide.
The big problem is determining whether a source is "truly" (pseudo-)random.
If you can trust SecureRandom
(you probably can!), then yes, BigInteger(130, new SecureRandom())
will give you a number with 130 bits of entropy. You can encode it as a 260-bit hex sequence, or a 176-bit Base64 sequence.
Only be sure that the entropy pool feeding SecureRandom
is refreshed frequently enough with some true randomness, or your identifier might become predictable. An (in)famous case is the Mersenne Twister, where having some 624 sequential values out of the generator allows recostructing the 19967 bits of its internal state via linear algebra, and thence predicting with certainty all its future values.