Secure web login with QR code with blockchain backed identity
QR codes are making a comeback with wider blockchain adoption
More and more we are seeing QR codes in many applications, in part thanks to many projects related to blockchain technology. Previously seen as Asian phenomena, (like waiters in Hong-Kong being tipped by their individual QR codes with Wechat), now QR codes are being widely used for authentication, payments, contact information exchange, etc.
Basic small amount of information transfer to mobile device using QR can be as simple as having to include a library and three lines of code. But when security is involved, this can become challenging to build convenient and secure solution based on QR codes. Also for most of the time you will also need a fallback solution, e.g. when authentication has to be performed without a second screen. Recently I worked on project involving online banking login with mobile device backed by identity stored on Ethereum blockchain and would like to share some insights on this topic.
For mobile development we are using C# and Xamarin Forms and targeting both Android and iOS devices with shared codebase. Identity data consumer — our online bank prototype is written in Ruby-on-rails. Mobile app is used to create and manage identity on Ethereum blockchain and for most part is out of scope of this post. App holds an Ethereum key, that is the subject for all the identity attributes (name, surname, nationality, etc.) and attestations from other parties (e.g. university diploma from a university).
What we want accomplish
As one of the requirements was to build this solution self-contained, just the mobile app and ID consumer. Usually authentication is done with help of outside arbiter, that ID consumer trusts, that sends data directly to consumer or provides validation, e.g. like OpenID Connect for login with Google works. In our case this was all done by means of asymmetric encryption and signing capabilities offered by blockchain architecture.
Blockchain in this case works as a secure storage space (in a sense that information published there cannot be changed) that works as lookup journal for involved parties. Public entities can have their details published there signed by themselves, e.g. like an online bank providing their name for user convenience. This can be done only by holder of the key of the address and coupled together with SSL certificate of website provides robust security and simple way of checking authenticity of ID information receiver for users.
For users the workflow is different as personal information cannot be public, so only the proof part is being published to blockchain and information is provided by some external validator, like our bank.
For users this allows simplified reuse of existing identity with different ID consumers, like banks and online merchants, without relying on a single centrally controlled entity. Enterprises also benefit from simple integration with this schema and ability to build web of trust with other entities by acquiring their validations.
The birds eye view of the authentication process is the following:
- User scans QR code with mobile app on ID consumer site. QR code contains information about ID consumer, URL where to retrieve ID consumer public key, security and attribute requirements.
- Mobile app loads public key of ID consumer, security key and URL where to publish identity data.
- Mobile app requests approval from user to share identity details with ID consumer identified by Ethereum address. Underlying blockchain protocol provides validation for this address and user can be sure about the trustworthiness of identity data receiver.
- Identity data is signed by user’s Ethereum key and encrypted using asymmetric encryption for public key of ID consumer.
- Encrypted payload is sent to ID consumer together with security key.
- ID consumer uses the security key to decrypt the private key, that is encrypted at rest and is specific to this login session.
- When private key is recovered, consumer uses it to decrypt the payload.
- The signature in decrypted payload is used to recover the signee Ethereum public key and address. If address matches the one in payload, we can assume successful login.
Nuts and bolts
As usually the fun part is described in inline comments with code, so check the code and comments, it is pretty simple.
QR code is generated by the following code. We are establishing a unique identifier for login session and specifying the receiver of identity information. As QR codes have a short lifespan, we also have to include timestamp, for mobile app to be able to show countdown timer when request expires.
URI schema kycex refers to schema registered by mobile app to be able to handle authentication requests from any QR code reader app. If you are implementing something similar, it would make sense also to add mobile browser detection in your ID consumer code and instead of QR code display a deeplink to fire up you app directly on device.
Mobile app then uses eu (encryption URL) parameter to retrieve a public key for payload encryption.
Encryption URL generates a new RSA public/private keypair. Private key is encrypted with 3DES symmetric encryption and stored locally. Public key is returned to mobile device, while discarded locally. This ensures that if local storage is breached, private keys cannot be read in plain and access to encrypted payloads can’t be gained before arriving on ID consumer. Using a new RSA key pair for each authentication also improves security, as reuse of the same key pair can lead to security risks, e.g. breaching one session, can impact others. We also use distinct keys for signature handling below, as reuse of same keys for signing and encryption operations will definitely degrade overall security.
On mobile side the request processing after getting confirmation looks the following. Check comments for details.
The RSA encryption part in C# is done with BouncyCastle nuget package — a swiss knife in encryption and signature handling.
The amount that can be encrypted with RSA encryption is defined by the selected key length. In our case this is 2048 bits that results in maximum input chunk size of 245 bytes. When using PKCS1 OAEP encoding this turns in 256 bytes per block, as encoding adds control structures and adds to overall security by making the cypher more robust due to increased entropy. If plaintext chunk is less than 245 bytes, it is padded by encoding to the correct needed length. This is the reason ciphertext length is always nx256 bytes long.
On decryption side in Ruby we use the same steps, just in reverse order.
So now we have decrypted a JSON structure that was encrypted in transit. Still we need to verify if the sender of this data actually is who he pretends to be. This is done by verifying that sender (Ethereum address) owns the private key for this address. Usually signatures are validated by providing public key and signature material and comparing output with provided signature value. For Ethereum we don’t have access to public key of the signee, so we provide signature material and signature to calculate the actual public key, that we use to derive Ethereum address and compare it to candidate. Actual signature validation is done in code below with help of Bitcoin and Eth gems.
So there we have it — a non-repudiable user information retrieval by web based consumer with payload being encrypted during transmission. As can be seen, blockchain technology offers a good foundation for security focused applications as the security aspect is already built into the protocol itself. On the other hand, while blockchain has been around for some time, what surprises me that there is a distinct lack of good tools and libraries for working with elliptic curve (EC) secp256k1 keys, that are common for both Ethereum and Bitcoin. One good example is signature validation for Ethereum — at least I was not able to find one for Ruby and had to fallback to using a Bitcoin gem that is based on 3 year old implementation of libsecp256k1 by Bitcoin foundation. Another potential area for improvement would be libraries for EC key based encryption of small payloads. While theoretically possible there are no high level wrappers for OpenSSL to do this, at least in Ruby.
On plus side having a single digital identity supported by blockchain infrastructure can be a major advantage over legacy systems with shared tokens. While currently the overall adoption is low and everyone seems more interested in playing the crypto markets, I see significant potential for decentralized and transparent authentication and identity data sharing with help of blockchain.