${AS YET UNNAMED IDENTITY VERIFICATION SYSTEM}
So, I have an idea.
It works like this:
When you create an account on some Internet service, be it social networking or web hosting or whatever, the service gives you a passphrase. This is randomly-generated, but tied to your account.
You enter this passphrase into a little applet—which could be a dashboard widget, or an iPhone app, or a J2ME MIDlet, or something else—and it displays a number. A 32-bit number, to be precise.
When you visit the login page of a service and enter your username, the JavaScript on the page goes away and asks the server what the verification code for your username is. If the server doesn’t recognise the username, it pretends that it did, to avoid leaking details of which usernames are valid and which aren’t (especially important if usernames are actually e-mail addresses).
The JavaScript then displays the code on the page. The code is the same 32-bit number, and you, the user, have had it beaten into you that if the code on the page doesn’t match the code on your device, you don’t proceed any further.
The code is derived from one half of the passphrase in combination with a timestamp. The UI of the client device would be constructed in such a way as to account for clock skew, by also showing the previous and next verification codes in sequence.
The server itself would employ rate-limting and a session-stored token to prevent the code-generation service being used automatically in a way which allows a potential phishing site to obtain the code for a given user.
That’s server validation covered.
In addition to supplying a passphrase to the user on registration, the server also asks the user for one. This is also entered into the client-side app.
Thus, the client can produce two codes: one for server verification which the user can verify, and is made up of 𝑓(timestamp + first half of server passphrase), and one for user authentication which the server can verify, and is made up of 𝑓(timestamp + second half of server passphrase + user passphrase). Both are 32-bit numbers which are easily generated if you’re the client or server, but not if you’re somebody else. 𝑓() is a truncated HMAC, incidentally, though I haven’t decided which one yet.
We don’t use the full server passphrase for both sides of the verification so as to make sure it’s not possible to derive the user code from the server code if you happen to have gained the user’s passphrase somehow.
It is, of course, possible to brute-force a 32-bit space quite quickly. For this reason, the server must implement effective rate-limiting and backoff controls (per-session and per-IP address), but this is relatively trivial. If the timestamp granularity was 30 seconds, a back-off delay of 10 seconds in the event of more than one unsuccessful login attempt is enough to throw a spanner in the brute-forcing works.
I plan to open source the whole affair (client-side app, server-side code, JavaScript) once written so that it can have as much scrutiny (and encourage adoption) as possible. If it (or something along similar lines) was widely-adopted, it would make phishing attacks very very difficult indeed.