Wat zit er achter de QR-code van een sportschool?

Je opent de app, er verschijnt een QR-code en je loopt naar binnen. Simpel voor de gebruiker, maar technisch gebeurt er verrassend veel. Een kijkje achter de schermen.

Share

Je opent de app van je sportschool, er verschijnt een QR-code en een paar seconden later sta je binnen.

Voor de gebruiker is het niet ingewikkelder dan dat.

Toch gebeurt er achter die ene QR-code verrassend veel. De code moet bijvoorbeeld bij het juiste abonnement horen, mag niet eindeloos geldig blijven en moet door het toegangssysteem gecontroleerd kunnen worden.

Ik wilde weten hoe zo'n systeem technisch in elkaar zit. Daarom heb ik gekeken naar een mobiele sportschoolapp, de QR-code zelf, het netwerkverkeer van de iOS-app en de manier waarop de app intern met deze gegevens omgaat.

Daarvoor gebruikte ik onder andere Stream op mijn iPhone en Jadx voor het bekijken van de Android-app.

In dit artikel leg ik uit wat ik tegenkwam, zonder dat je programmeerkennis nodig hebt.

Het begint met een simpele QR-code

Een QR-code ziet eruit als een willekeurig patroon van zwarte en witte blokjes.

Maar zo'n QR-code bevat gewoon informatie.

Dat kan bijvoorbeeld een website zijn, een tekst, een serienummer of een combinatie van verschillende gegevens.

Bij een toegangssysteem van een sportschool kan de inhoud er conceptueel ongeveer zo uitzien:

TYPE:LID:TEMP:Tijd:CONTROLE

Voor een gebruiker is dat vooral een vreemde reeks tekens.

Voor het toegangspoortje zijn het verschillende onderdelen die samen iets vertellen over de code.

Denk bijvoorbeeld aan:

  • welk soort toegangscode het is;
  • bij welk abonnement de code hoort;
  • een tijdelijke waarde;
  • wanneer de code is gemaakt;
  • een controlewaarde waarmee kan worden gecontroleerd of alles klopt.

Dat verklaart meteen waarom zo'n QR-code ingewikkelder is dan alleen een lidnummer in een afbeelding.

Waarom verandert de QR-code?

Een van de eerste dingen die opvalt wanneer je zo'n app gebruikt, is dat de QR-code kan veranderen.

Dat is bewust.

Stel dat je voor altijd exact dezelfde QR-code zou hebben. Dan zou de afbeelding zelf eigenlijk je toegangsbewijs zijn.

Een systeem kan dat slimmer aanpakken door bijvoorbeeld het huidige tijdstip onderdeel te maken van de code.

Simpel voorgesteld:

Abonnement 123
om 10:30

wordt een andere combinatie dan:

Abonnement 123
om 10:35

Omdat een deel van de informatie verandert, verandert uiteindelijk ook de QR-code.

De code is daardoor meer een tijdelijk toegangsbewijs dan een digitale versie van een vaste ledenpas.

Er zit meer achter dan alleen een lidnummer

Tijdens het onderzoek werd duidelijk dat de QR-code niet op zichzelf staat.

Op de achtergrond kent de app allerlei gegevens over de gebruiker en het apparaat.

Twee interessante voorbeelden daarvan zijn:

subscriptionId

en:

deviceId

Dat klinkt technisch, maar het idee is vrij eenvoudig.

Wat is een subscriptionId?

Een subscriptionId is een unieke identificatie van een abonnement.

Computers werken liever met unieke nummers en codes dan met namen.

Stel bijvoorbeeld dat een sportschool twee klanten heeft die allebei Jan de Vries heten. Alleen een naam gebruiken zou dan behoorlijk onhandig zijn.

Intern kan het systeem daarom iets gebruiken zoals:

subscriptionId: 842193

Daarmee weet het systeem precies over welk abonnement het gaat.

Voor de gebruiker is zo'n nummer normaal gesproken niet interessant. Het wordt vooral door de app en de servers gebruikt.

Je kunt het zien als het interne volgnummer van je abonnement.

Wat is een deviceId?

Een deviceId heeft een vergelijkbare functie, maar is gekoppeld aan het apparaat.

Bijvoorbeeld:

Abonnement:
842193

Apparaat:
telefoon-7F21

De echte waarden zien er meestal een stuk ingewikkelder uit, maar het principe blijft hetzelfde.

Het systeem weet hierdoor niet alleen welk abonnement wordt gebruikt, maar ook welk geregistreerd apparaat erbij hoort.

Dat is handig bij een toegangssysteem waarbij de mobiele telefoon eigenlijk de digitale ledenpas is geworden.

Hoe ik die gegevens tegenkwam

Voor het onderzoeken van de iOS-app heb ik op mijn iPhone Stream gebruikt.

Een mobiele app communiceert voortdurend met servers.

Wanneer je bijvoorbeeld:

  • inlogt;
  • je account opent;
  • gegevens vernieuwt;
  • of een QR-code opvraagt,

stuurt de app verzoeken naar een server en ontvangt hij informatie terug.

Dat proces kun je heel simpel zo voorstellen:

Telefoon
   |
   | vraagt informatie
   v
Server
   |
   | stuurt informatie terug
   v
Telefoon

Stream maakte het mogelijk om tijdens het gebruik van de app beter te bekijken welke soorten informatie daarbij werden uitgewisseld.

Zo kwamen onder andere gegevens als een subscriptionId en deviceId naar voren.

Dat maakte duidelijk dat de QR-code onderdeel is van een groter systeem waarin het abonnement en het geregistreerde apparaat aan elkaar gekoppeld zijn.

Waarom alleen naar het netwerk kijken niet genoeg is

Netwerkverkeer vertelt je wat een app verstuurt en ontvangt.

Maar daarmee weet je nog niet altijd wat de app vervolgens met die gegevens doet.

Stel dat je ziet dat de server deze informatie terugstuurt:

subscriptionId
deviceId
lidnummer

Dan weet je dat de app die gegevens ontvangt.

Je weet alleen nog niet automatisch waarom de app ze nodig heeft.

Daarom heb ik ook gekeken naar hoe de applicatie zelf is opgebouwd.

De Android-app bekijken met Jadx

Voor dat gedeelte gebruikte ik Jadx.

Jadx is een programma waarmee je de inhoud van een Android-app beter kunt bekijken.

Een Android-app wordt normaal gesproken als een pakket op je telefoon geïnstalleerd. In dat pakket zitten allerlei bestanden die nodig zijn om de app te laten werken.

Met Jadx kun je een deel van die structuur weer zichtbaar maken.

Je kunt het vergelijken met het uit elkaar halen van een apparaat.

Aan de buitenkant zie je alleen:

App openen
QR-code tonen
Scannen

Maar wanneer je de app uit elkaar haalt, zie je ineens allerlei losse onderdelen die samen dat resultaat produceren.

Eén app voor iPhone en Android

Tijdens het bekijken van de applicatie kwam ook React Native naar voren.

React Native is een technologie waarmee bedrijven een groot gedeelte van een mobiele app één keer kunnen ontwikkelen en daarna op zowel iPhone als Android kunnen gebruiken.

Dat bespaart veel dubbel werk.

In plaats van:

volledige iPhone-app
+
volledige Android-app

kun je een groot deel van de logica delen:

gedeelde app-logica
       |
   -----------
   |         |
 iPhone    Android

Voor gebruikers maakt dat weinig verschil.

Voor het onderzoeken van een app is het wel interessant. Het betekent namelijk dat informatie die je in de Android-versie tegenkomt ook meer inzicht kan geven in de algemene werking van de applicatie.

En dan komt Hermes erbij

De app gebruikte ook Hermes.

Hermes is een technologie van Meta die veel wordt gebruikt bij React Native-apps.

Het helpt de telefoon om de code van de app efficiënt uit te voeren.

Normaal zou je misschien verwachten dat de werking van een app ergens als gewone leesbare tekst staat.

Bij Hermes is dat niet zo eenvoudig.

De code wordt eerst omgezet naar een formaat dat beter geschikt is voor de telefoon.

Vergelijk het met een Word-document dat eerst naar een ander bestandsformaat wordt omgezet.

De informatie zit er nog wel in, maar je kunt hem niet meer op dezelfde simpele manier lezen.

Voor het begrijpen van de QR-code betekende dat vooral dat er wat meer analyse nodig was om te zien hoe de verschillende gegevens met elkaar werden gecombineerd.

De controlewaarde

Een van de interessantste onderdelen van de QR-code is de controlewaarde aan het einde.

Waarom is die nodig?

Stel dat een toegangscode alleen uit dit zou bestaan:

LID:123456

Dan is dat niet bijzonder sterk.

Iedereen die begrijpt hoe het formaat werkt, zou in theorie zelf een andere tekst in dezelfde vorm kunnen maken.

Daarom kan een systeem een extra controlewaarde toevoegen.

Die wordt berekend op basis van meerdere gegevens.

Bijvoorbeeld:

abonnementsinformatie
+
tijdelijke waarde
+
tijd
+
apparaatinformatie
        |
        v
controleberekening
        |
        v
controlewaarde

De precieze tekens zijn minder belangrijk dan het idee erachter.

De controlewaarde hoort bij de informatie ervoor.

Verander je een belangrijk onderdeel, dan verandert ook de uitkomst.

Wat is een hash?

Bij zo'n controle kan een zogenaamde hashfunctie worden gebruikt.

Een bekende hashfunctie is SHA-256.

Dat klinkt ingewikkeld, maar je kunt het zien als een machine waar je gegevens in stopt.

Bijvoorbeeld:

ABONNEMENT123-TELEFOON7-10:30

Daar maakt de computer een lange reeks tekens van.

Bijvoorbeeld:

82A7B1...

Dit is alleen een vereenvoudigd voorbeeld.

Verander je de invoer een beetje:

ABONNEMENT123-TELEFOON7-10:31

dan krijg je een andere uitkomst.

Het systeem heeft daarmee een handige manier om gegevens aan elkaar te koppelen.

Je hoeft als gebruiker helemaal niets met zo'n hash te doen. Het gebeurt volledig op de achtergrond.

Van app naar toegangspoortje

Als we alles bij elkaar zetten, wordt het proces een stuk duidelijker.

Je opent eerst de sportschoolapp.

De app heeft informatie over jouw account en abonnement.

Daarnaast weet het systeem welk apparaat geregistreerd staat.

Wanneer de toegangscode wordt opgebouwd, worden verschillende gegevens gebruikt om een tijdelijke QR-code te maken.

Vervolgens laat je die code bij de ingang zien.

Het toegangssysteem leest de code en controleert de relevante informatie.

Conceptueel ziet dat ongeveer zo uit:

App openen
     |
     v
Account herkennen
     |
     v
Abonnement controleren
     |
     v
Geregistreerd apparaat herkennen
     |
     v
Tijdelijke gegevens gebruiken
     |
     v
Controlewaarde berekenen
     |
     v
QR-code tonen
     |
     v
QR-code scannen
     |
     v
Toegangssysteem controleert de code

Voor de gebruiker duurt dat allemaal maar een paar seconden.

De telefoon is eigenlijk je ledenpas

Vroeger kreeg je bij veel sportscholen een plastic pasje.

Dat pasje bevatte bijvoorbeeld een chip of barcode waarmee je jezelf bij de ingang kon identificeren.

Bij een mobiele toegangscode verschuift die functie naar je telefoon.

Je telefoon wordt daarmee eigenlijk een digitale ledenpas.

Alleen kan zo'n mobiele ledenpas meer doen dan een simpel plastic kaartje.

De app kan bijvoorbeeld rekening houden met:

  • je account;
  • je abonnement;
  • je geregistreerde apparaat;
  • het huidige tijdstip;
  • informatie van de server;
  • en een tijdelijke toegangscode.

Daardoor kan de toegangscode iedere keer opnieuw worden opgebouwd.

Waarom een screenshot niet hetzelfde is als het systeem zelf

Dit laat ook zien waarom je de QR-code niet moet zien als een los plaatje.

Het plaatje is alleen de laatste stap.

Daarvoor is al veel gebeurd.

Een eenvoudig overzicht:

Account
   +
Abonnement
   +
Telefoon
   +
Tijd
   +
Controle
   =
QR-code

Wanneer een QR-code regelmatig verandert, is een oude afbeelding dus niet hetzelfde als de actuele code die door de app wordt opgebouwd.

De echte waarde zit niet alleen in het uiterlijk van de QR-code, maar in de informatie die erin verwerkt is.

Wat gebeurt er tijdens het inloggen?

Tijdens het bekijken van het netwerkverkeer kwam ook naar voren dat de app gebruikmaakt van technieken zoals OAuth 2.0 en PKCE.

Ook hiervoor hoef je geen programmeur te zijn om het basisidee te begrijpen.

Een app wil niet iedere keer opnieuw je gebruikersnaam en wachtwoord hoeven te versturen.

Daarom werkt een moderne login meestal met tijdelijke digitale toestemmingen.

Heel simpel gezegd:

Je logt in
    |
    v
Server controleert je gegevens
    |
    v
App krijgt een digitale toestemming
    |
    v
App gebruikt die voor volgende verzoeken

OAuth helpt bij het regelen van dat proces.

PKCE voegt extra controle toe voor mobiele applicaties.

Het resultaat is vooral dat de app na het inloggen veilig met de server kan blijven communiceren zonder dat je bij iedere handeling opnieuw je wachtwoord hoeft in te voeren.

Van buiten simpel, van binnen behoorlijk uitgebreid

Dat vond ik uiteindelijk het interessantste aan dit onderzoek.

Aan de buitenkant bestaat het hele proces uit drie stappen:

App openen
QR-code tonen
Scannen

Maar onder de motorkap ziet het er eerder zo uit:

Gebruiker
   |
   v
Account
   |
   v
Abonnement
   |
   v
Geregistreerd apparaat
   |
   v
Servercommunicatie
   |
   v
Tijdelijke gegevens
   |
   v
Controleberekening
   |
   v
QR-code
   |
   v
Toegangssysteem

Al deze onderdelen werken samen terwijl de gebruiker daar vrijwel niets van merkt.

En dat is eigenlijk precies hoe goede techniek hoort te werken: de ingewikkelde zaken gebeuren op de achtergrond, terwijl de handeling voor de gebruiker eenvoudig blijft.

Wat ik tijdens het onderzoek heb gebruikt

Stream op iOS

Stream gebruikte ik op mijn iPhone om beter inzicht te krijgen in de communicatie tussen de mobiele app en de server.

Hierdoor werd duidelijk welke soorten gegevens de app ontvangt en gebruikt, waaronder identifiers die betrekking hebben op het abonnement en het geregistreerde apparaat.

Jadx

Met Jadx heb ik de Android-versie van de applicatie bekeken.

Daarmee kun je de structuur van een Android-app onderzoeken en beter begrijpen welke technologieën en onderdelen erin zitten.

Een QR-scanner

Een gewone QR-scanner was eigenlijk het begin van het hele onderzoek.

Daarmee kon ik zien dat er achter de zichtbare QR-code daadwerkelijk een gestructureerde reeks gegevens zat.

Technologieën achter het systeem

Tijdens het onderzoek kwamen verschillende bekende technologieën voorbij.

React Native wordt gebruikt om mobiele applicaties te maken waarvan een groot deel van de code kan worden gedeeld tussen iOS en Android.

Hermes helpt bij het uitvoeren van React Native-code op mobiele apparaten.

OAuth 2.0 wordt gebruikt om gebruikers veilig te laten aanmelden en apps toegang te geven tot een ingelogde sessie.

PKCE voegt extra beveiliging toe aan dat inlogproces voor mobiele apps.

SHA-256 is een hashfunctie waarmee gegevens kunnen worden omgezet naar een controlewaarde.

Los van elkaar zijn het allemaal technische termen.

Samen zorgen ze ervoor dat een gebruiker uiteindelijk gewoon zijn telefoon kan pakken, een QR-code kan laten zien en naar binnen kan lopen.

Conclusie

Een QR-code bij de ingang van een sportschool lijkt misschien niet bijzonder ingewikkeld.

Maar het zichtbare blokje op je scherm is alleen het eindresultaat.

Daarachter zitten informatie over het account, het abonnement, het geregistreerde apparaat, tijdelijke waarden, communicatie met servers en controles waarmee het systeem de gegevens aan elkaar kan koppelen.

Door met Stream op iOS naar de communicatie van de app te kijken en met Jadx de opbouw van de Android-app te onderzoeken, werd steeds duidelijker hoe die verschillende onderdelen samenkomen.

De belangrijkste conclusie is eigenlijk heel eenvoudig:

de QR-code is niet het toegangssysteem — hij is alleen het zichtbare eindproduct ervan.

Voor de gebruiker blijft het:

Telefoon pakken → QR-code openen → scannen → naar binnen

En juist achter die paar simpele handelingen zit de interessante techniek.

Get in touch