Hashfuncties zijn alomtegenwoordig in de hedendaagse cryptografie. De Amerikaanse crytografiedeskundige Bruce Schneier1 noemt ze niet voor niets de "werkpaarden van de moderne cryptografie". Ze worden onder andere gebruikt in digitale handtekeningen, bij het versleutelen van berichten, als vingerafdrukken om dubbels te detecteren en als controlemechanisme om foutieve gegevens te detecteren.

Peter Pearson ontwikkelde een snelle en eenvoudige hashfunctie die werkt met een permutatietabel $$T$$ van de natuurlijke getallen tussen 0 (inbegrepen) en 256 (niet inbegrepen). We noteren het $$i$$-de getal in de tabel als $$T_i$$ ($$i = 0, 1, \ldots, 255$$). Bij Pearson hashing wordt de hashwaarde van een string $$s$$ als volgt berekend:

  1. initiële hashwaarde: $$h \leftarrow 0$$

  2. voor elk karakter $$s_i$$ van de string $$s$$ ($$i = 0, 1, \ldots, |s| - 1$$)

    1. $$v \leftarrow \text{ord}(s_i)$$

    2. $$h \leftarrow (h + v)\ \text{mod}\ 256$$

    3. $$h \leftarrow T_h$$

In stap (2a) wordt de ingebouwde functie ord gebruikt om de integerwaarde van een karakter te bepalen. In sommige implementaties — waaronder de originele implementatie van Pearson zelf — worden de twee waarden in stap (2b) gecombineerd met XOR ($$h \leftarrow (h\ \text{XOR}\ v)\ \text{mod}\ 256$$) in plaats ze op te tellen. Het resultaat blijft altijd een hashwaarde tussen 0 (inbegrepen) en 256 (niet inbegrepen).

Pearson hashing is niet geschikt voor het versleutelen van berichten, maar wordt wel gebruikt voor niet-cryptografische doeleinden zoals het opbouwen van een blockchain — het algoritme achter Bitcoin2 en andere cryptovaluta3.

Een blockchain is een voor het publiek leesbare databank waarin gegevens in blokken opgeslagen worden in een onveranderlijke en controleerbare keten. Er kunnen slechts twee bewerkingen uitgevoerd worden op de databank:

Er is geen bewerking om gegevens te wissen. Van zodra een datum aan de databank wordt toegevoegd, kan deze nooit meer verwijderd worden. Er is geen bewerking om gegevens te wijzigen. Van zodra een datum aan de databank wordt toegevoegd, kan deze nooit meer gewijzigd worden. Er is geen bewerking om gegevens te sorteren. Van zodra een datum aan de databank wordt toegevoegd, kan deze nooit binnen de keten verplaatst worden.

blockchain
Voorbeeld van blokken in een blockchain.

Elk blok in de keten heeft vier eigenschappen:

Het eerste blok van een blockchain wordt het genesisblok genoemd. Per definitie heeft dit blok index 0, datum Genesis Block en vorige hashwaarde 0. Het genesisblok bepaalt ook welke hashfunctie gebruikt wordt voor het opbouwen van de blockchain (bijvoorbeeld Pearson hashing). De hashwaarde van een blok wordt berekend als de hashwaarde van de string die bestaat uit de opeenvolging van de index, de datum en de hashwaarde van het vorige blok in de keten. Voor het genesisblok is dit dus de string 0Genesis Block0.

De blockchain zelf is een gelinkte lijst van blokken die kan uitgebreid worden door een nieuw blok met een gegeven datum toe te voegen (adjoin) aan een bestaand blok. Het valideren (validate) van een blok bestaat erin om de hashwaarde van het blok opnieuw te berekenen en te vergelijken met de ingestelde hashwaarde. Als dat het geval is, dan wordt de bewerking herhaald voor de voorgaande blokken in de keten, totdat het genesisblok bereikt wordt. De blockchain is corrupt van zodra een berekende hashwaarde verschilt van een ingestelde hashwaarde. Als het genesisblok bereikt wordt zonder een fout te vinden, dan is de blockchain geldig.

Opgave

Definieer een klasse Pearson waarmee de Pearson hashwaarde van strings kan berekend worden. Zorg ervoor dat de klasse minstens de volgende methoden ondersteunt:

Definieer ook nog een klasse Blok waarmee blokken uit een blockchain kunnen voorgesteld worden. Aan de initialisatiemethode kan een hashfunctie doorgegeven worden. Deze hashfunctie wordt voorgesteld door een object met een methode hash waaraan een string $$s$$ kan doorgegeven worden en die de hashwaarde van de string $$s$$ teruggeeft. Als niet expliciet een hashfunctie wordt doorgegegeven aan de initialisatiemethode, dan moet de blockchain gebruikmaken van Pearson hashing met als permutatietabel $$T$$ de reeks 0, 1, …, 255.

Elk object van de klasse Blok moet vier eigenschappen hebben die bepaald worden zoals beschreven in de inleiding: index, datum, hash en vorig_hash. De eigenschappen index en vorige_hash zijn onveranderlijk. Als een poging wordt ondernomen om deze eigenschappen te wijzigen, dan moet een AttributeError opgeworpen worden met de boodschap can't set attribute. De eigenschappen datum en hash moeten wel gewijzigd kunnen worden, in een poging om de blockchain aan te passen.

Zorg ervoor dat de klasse Blok minstens de volgende methoden ondersteunt:

Voorbeeld

>>> pearson = Pearson()
>>> pearson.hash('Hello')
244
>>> pearson.hash('Hello,')
32
>>> pearson.hash('Hell,o')
32
>>> pearson.hash('Hel,lo')
32
>>> pearson.hash('Hello, World!')
105

>>> pearson = Pearson(tabel=list(range(255, -1, -1)))
>>> pearson.hash('Hello')
173
>>> pearson.hash('Hello,')
38
>>> pearson.hash('Hell,o')
160
>>> pearson.hash('Hel,lo')
32
>>> pearson.hash('Hello, World!')
234

>>> pearson = Pearson(tabel=list(range(255, -1, -1)), combineer=lambda hash, char: (hash ^ char) % 256)
>>> pearson.hash('Hello')
189
>>> pearson.hash('Hello,')
110
>>> pearson.hash('Hell,o')
110
>>> pearson.hash('Hel,lo')
110
>>> pearson.hash('Hello, World!')
210

>>> Pearson([1, 2, 3, 4])
Traceback (most recent call last):
AssertionError: ongeldige tabel

>>> genesis = Blok()
>>> genesis.index, genesis.datum, genesis.hash, genesis.vorige_hash
(0, 'Genesis Block', 57, 0)
>>> genesis.is_geldig()
True

>>> blokA = genesis.toevoegen('A')
>>> blokA.index, blokA.datum, blokA.hash, blokA.vorige_hash
(1, 'A', 222, 57)
>>> blokA.is_geldig()
True

>>> blokB = blokA.toevoegen('B')
>>> blokB.index, blokB.datum, blokB.hash, blokB.vorige_hash
(2, 'B', 10, 222)
>>> blokB.is_geldig()
True

>>> blokC = blokB.toevoegen('C')
>>> blokC.index, blokC.datum, blokC.hash, blokC.vorige_hash
(3, 'C', 215, 10)
>>> blokC.is_geldig()
True

>>> blokD = blokB.toevoegen('D')
>>> blokD.index, blokD.datum, blokD.hash, blokD.vorige_hash
(3, 'D', 216, 10)
>>> blokD.is_geldig()
True

>>> blokD.index = 100
Traceback (most recent call last):
AttributeError: can't set attribute
>>> blokD.vorige_hash = 200
Traceback (most recent call last):
AttributeError: can't set attribute

>>> blokD.hash = 666
>>> blokA.is_geldig()
True
>>> blokB.is_geldig()
True
>>> blokC.is_geldig()
True
>>> blokD.is_geldig()
False
>>> blokD.hash = 216
>>> blokA.is_geldig()
True
>>> blokB.is_geldig()
True
>>> blokC.is_geldig()
True
>>> blokD.is_geldig()
True

>>> blokB.datum = 'XXX'
>>> blokA.is_geldig()
True
>>> blokB.is_geldig()
False
>>> blokC.is_geldig()
False
>>> blokD.is_geldig()
False
>>> blokB.datum = 'B'
>>> blokA.is_geldig()
True
>>> blokB.is_geldig()
True
>>> blokC.is_geldig()
True
>>> blokD.is_geldig()
True

>>> genesis = Blok(Pearson(tabel=list(range(255, -1, -1))))
>>> genesis.index, genesis.datum, genesis.hash, genesis.vorige_hash
(0, 'Genesis Block', 52, 0)
>>> genesis.is_geldig()
True

>>> blokA = genesis.toevoegen('A')
>>> blokA.index, blokA.datum, blokA.hash, blokA.vorige_hash
(1, 'A', 243, 52)
>>> blokA.is_geldig()
True

Bronnen