Several comments and answers on this page mention that validating a CAPTCHA (Google reCAPTCHA or otherwise) on the client side is self-defeating, as this would require the server to send the solution to the CAPTCHA to the client - essentially giving the spammer exactly what he/she needs, without him/her having to solve the CAPTCHA.
This is not necessarily true. Instead of the server sending the solution to the client, the server can send a cryptographic hash of the solution to the client. Because cryptographic hash functions (such as SHA-256) are one-way functions, a spammer cannot easily reverse the hashed solution to come up with the solution. However, client-side code can easily verify if user solved the CAPTCHA correctly, by hashing the user's input and checking if it matches the hashed solution from the server.
For example, the solution to this CAPTCHA image is f753f
. Instead of the server sending f753f
to the client, the server sends SHA256('f753f'), which is 100e1bafe0235a0c268dc7d918802298de08a23293929607bc7ac8050fd1333c
. A spammer cannot easily reverse the SHA256 function to find that the input that produced the value above is f753f
. However, when the user attempts to solve the CAPTCHA, client-side code can easily take a SHA256 hash of the user's input, and check if the result is 100e1bafe0235a0c268dc7d918802298de08a23293929607bc7ac8050fd1333c
. If so, then the user entered the correct solution.
Of course, when the form is submitted, the server should do its own independent validation, and not rely on the client for validation. However, performing the validation on the client side as well provides for a nice user experience, as the user can instantly see when they've solved the CAPTCHA correctly, without the client having to make a round trip to the server for each keypress.
Below is a simple implementation of this concept using HTML and Javascript:
<!doctype html>
<html>
<body>
<h1>Client-Side CAPTCHA Validation</h1>
<table>
<tr>
<td>Enter CAPTCHA: <input id=txtCaptcha onkeyup='javascript:checkcaptcha()';></td>
<td width=5%><div id=divCheckcaptcha></div></td>
<td><img src=https://i.imgur.com/BpxtRrO.png></td>
</tr>
</table>
</body>
<script>
var solutionhash='100e1bafe0235a0c268dc7d918802298de08a23293929607bc7ac8050fd1333c';
function checkcaptcha() {
var plaintextbytes=new TextEncoder("utf-8").encode(txtCaptcha.value);
window.crypto.subtle.digest('SHA-256', plaintextbytes)
.then(function(result) {
var resultUint8Array=new Uint8Array(result);
var enteredcaptchahash=Uint8ArrayToHexString(resultUint8Array);
if(enteredcaptchahash==solutionhash) {
divCheckcaptcha.innerHTML='<font color=green>✔</font>';
} else {
divCheckcaptcha.innerHTML='<font color=red>✕</font>';
}
});
}
function Uint8ArrayToHexString(ui8array) {
var hexstring='', h;
for(var i=0; i<ui8array.length; i++) {
h=ui8array[i].toString(16);
if(h.length==1) { h='0'+h; }
hexstring+=h;
}
var p=Math.pow(2, Math.ceil(Math.log2(hexstring.length)));
hexstring=hexstring.padStart(p, '0');
return hexstring;
}
</script>
</html>