Precautions for generating Bitcoin QR codes
Wed 15 February 2017Tagged: bitcoin, smsprivacy
A couple of people have been asking me to add QR codes to the payment page on SMS Privacy. I'd been putting it off for a while because I didn't want to do it in a way that opens up avenues for exploitation.
The first thing you want to do if displaying Bitcoin QR codes (and, indeed, Bitcoin addresses at all) is to serve them over HTTPS. Otherwise it is pretty easy for a man-in-the-middle to simply replace your addresses with their own, regardless of how otherwise-secure your web service is.
The second, and less obvious, thing is to make sure that you don't make it possible for a malicious attacker to determine whether a given Bitcoin address belongs to a user of your site.
If your solution is along the lines of "run qrencode when we generate the address, and put the PNG in public/qr-code/$address.png" then you make it trivially easy for an attacker, given a Bitcoin address, to determine whether or not it belongs to your web service (just test for the existence of the PNG).
If your solution is along the lines of "provide an API endpoint to generate QR codes at /qr-code?address=..." then this can work. But, crucially, it is only safe if you don't do too much validation of the input address. As soon as you start rejecting addresses that don't belong to your site, you again make it easy for an attacker to determine whether or not a Bitcoin address belongs to your service. Even if you still return a QR code either way, doing validation opens you up to timing attacks, and doesn't gain you anything.
Also, if you do that, make sure you don't cache the generated PNGs, otherwise you are again opened up to timing attacks (QR codes that belong to users will be cached, and QR codes that don't will not).
If your solution is along the lines of "provide an API endpoint to generate the current user's QR code at /qr-code", then I can't see any way to do this wrong. It doesn't provide any mechanism for an attacker to query you with arbitrary addresses. All anybody can do is retrieve the QR code for their current payment address. This is safe even if you cache the generated PNGs.
This last solution is what I did for SMS Privacy.
I'd be interested to hear if other people have other concerns or improvements.
Update 2017-11-27: An anonymous reader writes to suggest generating the QR code as an SVG and inlining the SVG tags to display it on the page. This way no API endpoints need to be created at all, and displaying a QR code is functionally almost equivalent to displaying the address in text. I think that's a great idea, and it would work almost as well by generating PNG files and inlining them with data URIs, e.g. <img src="data:image/png;base64,...
If you like my blog, please consider subscribing to the RSS feed or the mailing list: