Great project - especially how it leverages what we already have!
And actually, perhaps PKI is not that good for this case all together. Instead we could extend the original idea with simple primitives like an infinite hash chain (https://ieeexplore.ieee.org/document/7509492). In this scheme, during every authentication round, a user reveals a pre-committed secret and simultaneously commits to a new one for the next interaction. This approach is already used on websites where authentication tokens are exchanged based on known hashes, and there are proven methods to keep these tokens continuously updated. It relies solely on hashes — just like your scheme — and can work by having both parties scan each other’s QR codes on every interaction, which both performs an authentication check and also updates the application’s state each round.
The beauty of this method compared to PKI is first, it is based on a weaker assumption, but more importantly is that even if an attacker intercepts the initial QR code, they cannot afford to miss any message exchange, or they’ll lose the ability to authenticate. Moreover, if an attacker ever impersonates a party by following the protocol, the genuine authentication sequence will break down, revealing a discrepancy that exposes the impersonation.
And it should not be too hard to build, so I might give it a try.
Actually, I thought a bit more about it, and it seems there is a trivial potential attack when an adversary opens two calls - one to each user - and deepfakes each user’s feed to the other.
If all authentication keys (QR codes, TOTP codes, even PKI) are exchanged in the communication channel and do not authenticate the communication channel feed itself, the attacker can simply forward them between the two victims, maintaining a perfect “bridge” with no obvious sign of tampering. Once the authentication phase is complete, they can terminate the redundant call and continue conversation with the target having passed the authentication.
It seems to me that the only way against it is to authenticate messages (text or feed) themselves, and for that we go back to regular MACs that are already used today.
I think that the scenario you describe requires one of two conditions:
1. The attacker knows the time and medium through which the two persons call each other, and have control over the medium, being able to inject themselves;
2. The attacker coerces one of the two persons to perform authentication.
You have a much bigger problem if any of the above is true.
This is a little stupid project that I created after seeing what AI can do nowadays.
In an "ideal" world:
- everybody should start using public/private key cryptography to authenticate each other, but that's still rather unwieldy nowadays. I'm not aware of any solution with a good UX;
- people would stop posting their photos/videos/audio recordings on the web, and also scrub anything that have been uploaded in the past.
We don't live in an "ideal" world, and TOTP is pretty widespread now, and you can easily read the TOTP code over the phone, etc. So this solution was born.
I’m glad you like it. The whole thing might be a bit paranoid, but it was a fun exercise trying to get an LLM to code for me (I used DeepSeek to code the base, and then manually fixed some stuff, and then asked DeepSeek to add i18n for me).
Isn't the authentication unidirectional? Alice can confirm its Bob, or Bob can confirm its Alice, but they can't both be sure they are talking to the real person.
"Malice" could ask Bob for his code, and lie about it matching (or maybe Malice has no code at all and is pretending to match), lulling Bob into thinking that authentication was successful based on taking Malice's word for it.
Seems like you would need two codes for mutual authentication. One for Alice to Bob, and one for Bob to Alice.
Maybe a bug: I selected "Generate" and didn't notice that the QR code had been generated as it was off-screen. So I re-selected "Generate" a few times before I realised what was happening. Only, now I have two long columns of QR codes.
Thank you for the feedback! I’ll try to see what I can do about it. Perhaps make the QR codes display in a modal in front of the original content. Alternatively, I can try having the page automatically scroll (with ease-in and ease-out) to the QR codes.
This has been fixed. After every click of the "Generate" button: 1. any existing QR codes are replaced; 2. the new QR codes scroll into view automatically.
Hmm, I wanted to require less fiddling around by the user once the QR code is scanned, which is why I generate two QR codes that encode the same secret but with different labels.
Looks like the problem is with the desktop Tor browser. I can see that the QR codes are displayed as two square images with coloured vertical stripes instead of real QR codes. That's such as weird problem.
In any case, I have just added the display of the base32 secret key.
I asked Google Gemini 2.0 Flash and Perplexity.ai, the QR code display problem on Tor Browser for desktop appears to have to do with the browser's anti-fingerprinting mechanism.
This is the explanation that the AIs gave me:
# begin explanation
The issue you're experiencing with QR codes showing up as vertical stripes in the Tor browser when using QRCode.js is due to Tor Browser's anti-fingerprinting feature.
Tor Browser is designed to protect user privacy, and one of the ways it does this is by randomizing the output of the HTML5 Canvas API. QRCode.js uses the Canvas API to draw QR codes. When Tor Browser randomizes the Canvas output, it distorts the QR code image, causing it to appear as vertical stripes instead of a readable QR code.
This is a known issue and is intentional behavior in Tor Browser to prevent fingerprinting. Fingerprinting is a technique websites use to uniquely identify and track users based on various browser characteristics, including how the browser renders content like Canvas elements.
Here are the solutions based on my research:
Lower Tor Browser's Security Level: The simplest workaround is to temporarily lower Tor Browser's security level from "Safer" or "Safest" to "Standard". You can do this in Tor Browser's settings under "Privacy & Security" -> "Security Level". Setting it to "Standard" disables the Canvas fingerprinting protection, which should allow QRCode.js to render QR codes correctly. However, this will slightly reduce your privacy while using Tor Browser.
Use a Different Browser: If you need to generate QR codes reliably and use them, the most straightforward solution is to use a different browser like Safari, Firefox, or Chrome, as you've already observed that the QR codes work correctly in these browsers. You can use a non-Tor browser specifically for generating and scanning QR codes, and then switch back to Tor Browser for your privacy-sensitive browsing.
Advanced Configuration (Not Recommended for Most Users): For advanced users, it might be possible to modify Tor Browser's about:config settings to disable specific anti-fingerprinting measures related to Canvas, such as canvas.poison or privacy.resistFingerprinting. However, this is strongly discouraged unless you fully understand the privacy implications, as it can weaken Tor Browser's privacy protections. It's generally better to use one of the first two options.
In summary, the striped QR code issue is a trade-off between strong privacy protection and website functionality in Tor Browser. Tor prioritizes privacy by randomizing Canvas output, which unfortunately breaks some features like QR code generation using libraries like QRCode.js. The recommended solutions are to either temporarily lower the security level in Tor Browser or use a different browser for QR code related tasks.
# end explanation
As well as the proposed solution:
# begin solution
The issue with QR codes appearing as vertical stripes in Tor Browser when using QRCode.js is due to Tor Browser's anti-fingerprinting feature, which randomizes the Canvas API output. This intentional behavior to protect privacy disrupts QRCode.js's ability to render QR codes correctly.
Solutions include:
1. Lowering Tor Browser's security level to "Standard".
2. Using a different browser for QR code generation.
3. Advanced users can modify `about:config` settings, but this is not recommended.
The recommended solutions are to temporarily lower the security level in Tor Browser or use a different browser for QR code related tasks.
# end solution
And actually, perhaps PKI is not that good for this case all together. Instead we could extend the original idea with simple primitives like an infinite hash chain (https://ieeexplore.ieee.org/document/7509492). In this scheme, during every authentication round, a user reveals a pre-committed secret and simultaneously commits to a new one for the next interaction. This approach is already used on websites where authentication tokens are exchanged based on known hashes, and there are proven methods to keep these tokens continuously updated. It relies solely on hashes — just like your scheme — and can work by having both parties scan each other’s QR codes on every interaction, which both performs an authentication check and also updates the application’s state each round.
The beauty of this method compared to PKI is first, it is based on a weaker assumption, but more importantly is that even if an attacker intercepts the initial QR code, they cannot afford to miss any message exchange, or they’ll lose the ability to authenticate. Moreover, if an attacker ever impersonates a party by following the protocol, the genuine authentication sequence will break down, revealing a discrepancy that exposes the impersonation.
And it should not be too hard to build, so I might give it a try.