Denna handledning kommer att visa hur du använder Pythons inbyggda modul `hashlib` för att generera säkra hashvärden.
Att förstå hashfunktioner och hur man skapar säkra hashvärden med kod kan vara värdefullt, även om du inte sysslar med säkerhetsprogrammering. Varför?
När du arbetar med Python-projekt kan det uppstå situationer där du vill undvika att lagra lösenord och annan känslig information direkt i databaser eller källkodsfiler. I sådana fall är det säkrare att använda en hashfunktion på den känsliga informationen och lagra hashvärdet istället.
I denna guide kommer vi att utforska vad hashing är och hur det skiljer sig från kryptering. Vi kommer även att gå igenom de viktigaste egenskaperna hos säkra hashfunktioner. Därefter kommer vi att använda vanliga hashalgoritmer för att beräkna hashvärden för text i Python, med hjälp av den inbyggda `hashlib`-modulen.
Låt oss börja!
Vad är hashing?
Hashing innebär att omvandla en textsträng (ett meddelande) till en utdata av fast längd, kallad hashvärde. Det betyder att oavsett längden på den ursprungliga textsträngen, kommer hashvärdet alltid att ha samma längd för en given hashalgoritm. Men hur skiljer det sig från kryptering?
Vid kryptering omvandlas meddelandet, eller klartext, med hjälp av en krypteringsalgoritm till en krypterad utdata. Sedan kan den krypterade utdatan dekrypteras med en dekrypteringsalgoritm för att återskapa den ursprungliga textsträngen.
Hashing fungerar däremot annorlunda. Kryptering är en reversibel process, där man kan gå från det krypterade meddelandet till det okrypterade meddelandet och vice versa.
Till skillnad från kryptering är hashing inte en reversibel process, vilket betyder att man inte kan återskapa den ursprungliga textsträngen från hashvärdet.
Egenskaper hos hashfunktioner
Låt oss snabbt gå igenom några egenskaper som en bra hashfunktion bör ha:
- Deterministisk: En hashfunktion ska vara deterministisk. Det betyder att om man använder samma textsträng som indata, kommer hashvärdet alltid att vara identiskt.
- Preimage-resistent: Vi har redan nämnt att hashing inte är reversibelt. Preimage-resistens innebär att det ska vara omöjligt att ta reda på den ursprungliga textsträngen utifrån hashvärdet.
- Kollisionsresistent: Det ska vara mycket svårt (eller i princip omöjligt) att hitta två olika textsträngar `m1` och `m2` som ger samma hashvärde. Detta kallas kollisionsresistens.
- Andra preimage-resistent: Detta betyder att om man har en textsträng `m1` och dess hashvärde, ska det vara omöjligt att hitta en annan textsträng `m2` som ger samma hashvärde som `m1`.
Pythons hashlib-modul
Pythons inbyggda modul `hashlib` tillhandahåller implementationer av flera hash- och meddelandesammanfattningsalgoritmer, inklusive SHA- och MD5-algoritmerna.
För att kunna använda konstruktorerna och de inbyggda funktionerna i `hashlib`-modulen, kan du importera den i din arbetsmiljö på följande sätt:
import hashlib
`hashlib`-modulen har konstanterna `algorithms_available` och `algorithms_guaranteed`, som visar vilka algoritmer som är tillgängliga respektive garanterade att finnas på en viss plattform.
`algorithms_guaranteed` är alltså en delmängd av `algorithms_available`.
Starta en Python REPL, importera `hashlib` och kolla in konstanterna `algorithms_available` och `algorithms_guaranteed`:
>>> hashlib.algorithms_available
# Output
{'md5', 'md5-sha1', 'sha3_256', 'shake_128', 'sha384', 'sha512_256', 'sha512', 'md4',
'shake_256', 'whirlpool', 'sha1', 'sha3_512', 'sha3_384', 'sha256', 'ripemd160', 'mdc2',
'sha512_224', 'blake2s', 'blake2b', 'sha3_224', 'sm3', 'sha224'}
>>> hashlib.algorithms_guaranteed
# Output
{'md5', 'shake_256', 'sha3_256', 'shake_128', 'blake2b', 'sha3_224', 'sha3_384',
'sha384', 'sha256', 'sha1', 'sha3_512', 'sha512', 'blake2s', 'sha224'}
Vi ser att `algorithms_guaranteed` verkligen är en delmängd av `algorithms_available`.
Hur man skapar hash-objekt i Python
Låt oss nu lära oss hur man skapar hash-objekt i Python. Vi kommer att beräkna SHA256-hashvärdet för en textsträng med hjälp av följande metoder:
- Den generiska `new()`-konstruktorn
- Algoritmspecifika konstruktorer
Använda `new()`-konstruktorn
Låt oss först definiera en textsträng:
>>> message = "adminvista.com is awesome!"
För att skapa hash-objektet kan vi använda konstruktorn `new()` och ange algoritmens namn, som visas här:
>>> sha256_hash = hashlib.new("SHA256")
Vi kan nu anropa metoden `update()` på hash-objektet med textsträngen som argument:
>>> sha256_hash.update(message)
Om du gör det kommer du att få ett felmeddelande eftersom hashalgoritmer bara kan hantera bytesträngar.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Unicode-objects must be encoded before hashing
För att få den kodade strängen kan du använda metoden `encode()` på textsträngen och använda resultatet i anropet till `update()`. Efter det kan du anropa metoden `hexdigest()` för att få SHA256-hashvärdet som motsvarar textsträngen.
sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Output:'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
Istället för att koda textsträngen med `encode()`-metoden kan du också definiera den som en bytesträng genom att prefixa strängen med `b` så här:
message = b"adminvista.com is awesome!"
sha256_hash.update(message)
sha256_hash.hexdigest()
# Output: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
Det erhållna hashvärdet är detsamma som tidigare, vilket bekräftar att hashfunktioner är deterministiska.
Dessutom bör en liten ändring i textsträngen resultera i ett drastiskt annorlunda hashvärde (även kallat ”lavineffekt”).
Låt oss testa detta genom att ändra ’a’ i ’awesome’ till ’A’ och beräkna hashvärdet:
message = "adminvista.com is Awesome!"
h1 = hashlib.new("SHA256")
h1.update(message.encode())
h1.hexdigest()
# Output: '3c67f334cc598912dc66464f77acb71d88cfd6c8cba8e64a7b749d093c1a53ab'
Vi ser att hashvärdet har förändrats helt.
Använda algoritmspecifika konstruktorer
I föregående exempel använde vi den generiska `new()`-konstruktorn och angav `”SHA256″` som algoritmens namn för att skapa hash-objektet.
Istället för det kan vi också använda konstruktorn `sha256()` som visas här:
sha256_hash = hashlib.sha256()
message= "adminvista.com is awesome!"
sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Output: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'
Det resulterande hashvärdet är identiskt med det vi fick tidigare för textsträngen ”adminvista.com is awesome!”.
Utforska hash-objektens attribut
Hash-objekt har några användbara attribut:
- Attributet `digest_size` anger storleken på sammanfattningen i byte. Till exempel ger SHA256-algoritmen ett 256-bitars hashvärde, vilket motsvarar 32 byte.
- Attributet `block_size` refererar till den blockstorlek som används i hashalgoritmen.
- Attributet `name` är namnet på algoritmen som vi kan använda i konstruktorn `new()`. Det kan vara användbart att se värdet för det här attributet när hash-objekten saknar beskrivande namn.
Vi kan kontrollera dessa attribut för `sha256_hash`-objektet som vi skapade tidigare:
>>> sha256_hash.digest_size
32
>>> sha256_hash.block_size
64
>>> sha256_hash.name
'sha256'
Låt oss nu ta en titt på några intressanta praktiska tillämpningar av hashing med Pythons `hashlib`-modul.
Praktiska exempel på hashing
Verifiering av programvaras och filers integritet
Som utvecklare laddar vi ner och installerar programvarupaket hela tiden, oavsett om vi använder Linux, Windows eller macOS.
Vissa speglar för programvarupaket kan vara opålitliga. Därför kan du ofta hitta hashvärdet (eller checksumman) vid nedladdningslänken. Du kan verifiera integriteten hos den nedladdade programvaran genom att beräkna hashvärdet och jämföra det med det officiella hashvärdet.
Detta kan också tillämpas på filer på din egen dator. Även den minsta förändring i en fil kommer att resultera i ett radikalt annorlunda hashvärde. Du kan kontrollera om en fil har modifierats genom att jämföra dess hashvärde.
Här är ett enkelt exempel. Skapa en textfil ’`my_file.txt`’ i din arbetskatalog och lägg till lite innehåll i den.
$ cat my_file.txt
This is a sample text file.
We are going to compute the SHA256 hash of this text file and also
check if the file has been modified by
recomputing the hash.
Öppna sedan filen i binärt läsläge (’rb’), läs in filens innehåll och beräkna SHA256-hashvärdet som visas här:
>>> import hashlib
>>> with open("my_file.txt","rb") as file:
... file_contents = file.read()
... sha256_hash = hashlib.sha256()
... sha256_hash.update(file_contents)
... original_hash = sha256_hash.hexdigest()
Variabeln `original_hash` innehåller nu hashvärdet för ’`my_file.txt`’ i sitt nuvarande tillstånd.
>>> original_hash
# Output: '53bfd0551dc06c4515069d1f0dc715d002d451c8799add29f3e5b7328fda9f8f'
Ändra nu filen ’`my_file.txt`’. Du kan till exempel ta bort det extra inledande mellanslaget före ordet ”going”. 🙂
Beräkna hashvärdet igen och lagra det i variabeln `computed_hash`.
>>> import hashlib
>>> with open("my_file.txt","rb") as file:
... file_contents = file.read()
... sha256_hash = hashlib.sha256()
... sha256_hash.update(file_contents)
... computed_hash = sha256_hash.hexdigest()
Du kan sedan lägga till en enkel `assert`-sats som kontrollerar om `computed_hash` är lika med `original_hash`.
>>> assert computed_hash == original_hash
Om filen har ändrats (vilket den har i det här fallet) kommer du att få ett `AssertionError`:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
Du kan använda hashing när du lagrar känslig information, som lösenord, i databaser. Du kan också använda hashing för lösenordsautentisering när du ansluter till databaser, genom att verifiera hashvärdet för det angivna lösenordet mot hashvärdet för det korrekta lösenordet.
Sammanfattning
Jag hoppas att denna handledning har lärt dig hur man genererar säkra hashvärden med Python. Här är de viktigaste punkterna:
- Pythons `hashlib`-modul tillhandahåller färdiga implementationer av flera hashalgoritmer. Du kan hämta en lista över garanterade algoritmer på din plattform med `hashlib.algorithms_guaranteed`.
- För att skapa ett hash-objekt kan du använda den generiska `new()`-konstruktorn med syntaxen: `hashlib.new(”algo-name”)`. Alternativt kan du använda konstruktorer som motsvarar specifika hashalgoritmer, som t.ex.: `hashlib.sha256()` för SHA256-hash.
- Efter att ha definierat den textsträng som ska hashas och skapat hash-objektet, kan du anropa metoden `update()` på hash-objektet, följt av metoden `hexdigest()` för att hämta hashvärdet.
- Hashing kan vara användbart för att kontrollera integriteten hos programvaruartefakter och filer, för att lagra känslig information i databaser och mycket mer.
Nästa steg är att lära sig hur man skapar en slumpmässig lösenordsgenerator i Python.