"Random" Notes
I carpool with a friend in a nearby building to the climbing gym. To decide who drives, we play email roshambo. The HTML code for the Rock/Paper/Scissors choice is presented below:
We've done this for years, and we each end up driving about 50% of the time, with a few short streaks breaking up the routine. I decided it was too much effort to have to actually choose rock, paper or scissors if I didn't want to. It'd be nice for the website to randomly suggest one for me.
So I added the following PHP code:
There, now the web page will suggest a random throw when it loads. How convenient!
Except that my friend started destroying me in our challenges. From December 2009 through March 2010, he began winning over 75% of our challenges, and I had to keep driving the carpool. This was costing me money!
It turns out that while I was using the suggested random throw, he had a different strategy. He considered his suggested throw, and made the throw that would beat it.
So there was a correlation between the random throw suggested for me, and the one suggested for him! Even though our suggestions are generated from different pages (mine from index.php because I start the challenge, and his from throw.php because he responds to a challenge), the random number generator runs on the same server.
It's just not random enough, and it exposed an exploit that was costing me money!
There's a simple fix for this, use mt_rand() instead of rand(). So I made the following change:
Except it bothered me that both random numbers were seeded in a similar fashion, and there'd often be a constant offset between the localtime and uptimes of both machines. This wouldn't do. I needed better randomness. Luckly, there's a site for that.
Great! I'll have my Javascript make a quick call to random.org to make the suggestion.
A downside is that the proxy content can be cached, so I'd have to be sure to disable that.
Even worse is that my server is a shared server running at DreamHost, and I don't have access to httpd.conf. And one can't specify a ProxyPass in a .htaccess file either.
So the work-around is to have the PHP code make the call to random.org. Great! Just code that sucker up, ensure that we don't spam random.org by checking our quotas, and falling back on the Javascript implementation if we do go over quota. I won't show you all that, just the PHP snippet.
He said, "Oh, I've had better Fridays. I was RIFfed. We won't be carpooling anymore."
<SELECT name="p_throw">
<OPTION value="r">Rock</OPTION>
<OPTION value="p">Paper</OPTION>
<OPTION value="s">Scissors</OPTION>
</SELECT>
We've done this for years, and we each end up driving about 50% of the time, with a few short streaks breaking up the routine. I decided it was too much effort to have to actually choose rock, paper or scissors if I didn't want to. It'd be nice for the website to randomly suggest one for me.
So I added the following PHP code:
$suggest = rand(0, 2);
<SELECT name="p_throw">
<OPTION <? if ($suggest == 0) echo "SELECTED "; ?>value="r">Rock</OPTION>
<OPTION <? if ($suggest == 1) echo "SELECTED "; ?>value="p">Paper</OPTION>
<OPTION <? if ($suggest == 2) echo "SELECTED "; ?>value="s">Scissors</OPTION>
</SELECT>
There, now the web page will suggest a random throw when it loads. How convenient!
Except that my friend started destroying me in our challenges. From December 2009 through March 2010, he began winning over 75% of our challenges, and I had to keep driving the carpool. This was costing me money!
It turns out that while I was using the suggested random throw, he had a different strategy. He considered his suggested throw, and made the throw that would beat it.
So there was a correlation between the random throw suggested for me, and the one suggested for him! Even though our suggestions are generated from different pages (mine from index.php because I start the challenge, and his from throw.php because he responds to a challenge), the random number generator runs on the same server.
It's just not random enough, and it exposed an exploit that was costing me money!
There's a simple fix for this, use mt_rand() instead of rand(). So I made the following change:
$suggest = mt_rand(0, 2);Although I made the change to mt_rand(), the fact that the pseudo-random numbers were being made by the same physical generator bothered me. I decided that instead of generating the suggested throw on the server, it'd be best to generate the suggestion at the client computer, in Javascript. So I wrote the following code and deployed that:
That's better. Now his random suggestion is generated on an entirely different machine than mine...
function Set_suggested_throw()
{
var randomnumber = Math.floor( Math.random() * 3 )
document.rpsform.p_throw.selectedIndex=randomnumber
}
</head>
<body onLoad="Set_suggested_throw()">
Except it bothered me that both random numbers were seeded in a similar fashion, and there'd often be a constant offset between the localtime and uptimes of both machines. This wouldn't do. I needed better randomness. Luckly, there's a site for that.
Great! I'll have my Javascript make a quick call to random.org to make the suggestion.
Making a call to random.org from a page generated at rps.dlma.com runs afoul of the Same Origin Policy. My server is a Linux server, so all I have to do is add a ProxyPass to my httpd.conf and restart it.
function Set_suggested_throw()
{
var xmlhttp = null;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
if ( typeof xmlhttp.overrideMimeType != 'undefined') {
xmlhttp.overrideMimeType('text/xml');
}
} else if (window.ActiveXObject) {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
// Only if quota not exceeded. See http://www.random.org/clients/
xmlhttp.open( 'GET',
'/rnd_org/integers/?num=1&min=0&max=2&col=1&base=10&format=plain&rnd=new',
true );
xmlhttp.send( null );
xmlhttp.onreadystatechange = function() {
if ( this.readyState == 4 && this.status == 200 ) {
document.contactform.p_throw.selectedIndex=this.responseText
}
}
}
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
ProxyPass /rnd_org http://www.random.org
A downside is that the proxy content can be cached, so I'd have to be sure to disable that.
Even worse is that my server is a shared server running at DreamHost, and I don't have access to httpd.conf. And one can't specify a ProxyPass in a .htaccess file either.
So the work-around is to have the PHP code make the call to random.org. Great! Just code that sucker up, ensure that we don't spam random.org by checking our quotas, and falling back on the Javascript implementation if we do go over quota. I won't show you all that, just the PHP snippet.
Great! The next time we played Email Roshambo, I won! Phew! As he drove me to the gym, I asked him how he'd been since I last saw him.
$ctx = stream_context_create( array( 'http' => array( 'timeout' => 5 ) ) );
$url = "http://www.random.org/integers/?num=1&min=0&max=2&col=1&base=10&format=plain";
$result=file_get_contents( $url, 0, $ctx );
if ( !is_bool( $result ) || $result != false ) {
// set $suggest }
He said, "Oh, I've had better Fridays. I was RIFfed. We won't be carpooling anymore."
Comments