Hoppa yfir í efnið

Strengir og hrá bæti (Strings vs. Bytes)

Velkomin í einn mikilvægasta kaflann í allri bókini! Í flestum forritunarnámskeiðum er þetta atriði útskýrt mjög hratt, en í netöryggi og tölvuhögun er munurinn á texta (strengjum) og hráum gögnum (bætum) undirstaða alls sem við gerum.

Við ætlum að skoða af hverju texti er í raun blekking, hvernig við meðhöndlum hrá bæti í Python, hvernig við breytum þar á milli og hvernig við beitum svokölluðum „sneiðum“ (e. slicing) til að stýra gögnunum okkar á fljótlegan hátt.


Texti er blekking

Í kaflanum um kóðun rittákna lærðum við að örgjörvi tölvunnar skilur ekkert nema tölur. Þegar þú sérð bókstafinn 'A' á skjánum, þá er tölvan í raun með töluna 65 í minni sínu.

Í Python höfum við tvö gerólík gagnatög til að meðhöndla þetta:

  1. Strengir (str): Þetta er texti sem við mennirnir lesum. Í Python getur strengur innihaldið hvaða rittákn sem er í heiminum, þökk sé Unicode staðlinum (t.d. íslenska stafi eins og æ, ö eða jafnvel tjákn eins og ð eða 🚀).
  2. Bæti (bytes): Þetta eru hrá, óbreytt tvíundagögn — röð af tölum þar sem hvert gildi er nákvæmlega eitt bæti (tala frá 0 upp í 255). Tölvan vinnur einungis með þetta form undir húfnum.

Í Python búum við til bytes breytu með því að setja stafinn b fyrir framan gæsalappirnar:

>>> texti = "halló"
>>> type(texti)
<class 'str'>

>>> gogn = b"halló"  # Þetta mun valda villu!
SyntaxError: bytes can only contain ASCII literal characters

ATH: Skrifstofustjóri Python grípur inn í

Af hverju máttirðu ekki búa til b"halló"? Vegna þess að íslenski stafurinn ó er ekki í ASCII staðlinum! Hrá bæti sem eru skrifuð með b"" mega aðeins innihalda hefðbundna ensku stafina (ASCII). Ef við viljum setja flóknari stafi inn í bæti, verðum við að nota sérstök stýritákn eða breyta strengnum á réttan hátt.

Sláum inn gilt dæmi:

>>> gogn = b"hallo"
>>> type(gogn)
<class 'bytes'>

Umbreytingabláin: Encode og Decode

Hvernig breytum við strengnum okkar (Unicode texta) yfir í hrá bæti sem við getum sent yfir netið, eða dulkóðað? Og hvernig breytum við bætunum sem við fáum aftur í lesanlegan texta?

Python býður upp á tvær mjög mikilvægar aðferðir til þess: .encode() og .decode().

       Texti (str)  "hæ"
            │
            │  .encode("utf-8")  (Kóðun)
            ▼
       Bæti (bytes) b"h\xc3\xa6"
            │
            │  .decode("utf-8")  (Afkóðun)
            ▼
       Texti (str)  "hæ"

Prófum þetta í gagnvirku skelinni:

# Úr streng yfir í bæti (Kóðun)
>>> texti = "hæ"
>>> baeti = texti.encode("utf-8")
>>> baeti
b'h\xc3\xa6'

Taktu eftir því að stafurinn h er sýndur sem h (því hann er í ASCII), en íslenski stafurinn æ er sýndur sem \xc3\xa6. Þetta eru tvö bæti skrifuð í sextándakerfinu (0xc3 og 0xa6) sem saman tákna stafinn æ í UTF-8 kóðuninni!

Breytum bætunum aftur í texta:

# Úr bætum yfir í streng (Afkóðun)
>>> baeti.decode("utf-8")
'hæ'

Þegar hlutirnir fara úrskeiðis

Ef þú reynir að afkóða bæti sem innihalda handahófskenndar tölur sem passa ekki við neina UTF-8 stafi, mun Python gefa þér villu: UnicodeDecodeError. Þetta er mjög algengt þegar við erum að vinna með dulkóðuð gögn eða keyranleg forrit — þau eru einfaldlega ekki ætluð til að vera lesin sem texti!


Hex-stríðið: hex og binascii

Byrjendur ruglast mjög oft á þessu tvennu: * Hráa bætinu sem hefur gildið 65 (sem í ASCII er stafurinn 'A'). * Textastrengnum "41" (sem er hvernig við skrifum töluna 65 í sextándakerfinu).

Í netöryggi fáum við oft sextándakerfisstrengi og þurfum að breyta þeim í hrá bæti til að geta sent þá áfram. Python býður upp á tvær helstu leiðir til að leysa þetta.

1. Innbyggðu föllin í Python (Mælt með)

Auðveldasta leiðin í nýrri útgáfum af Python er að nota .hex() og bytes.fromhex():

# Breyting úr hráum bætum yfir í hex-streng
>>> b"ABC".hex()
'414243'

# Breyting úr hex-streng yfir í hrá bæti
>>> bytes.fromhex("414243")
b'ABC'

2. Standard bókasafnið binascii

Í mörgum forritum og dæmum á netinu muntu sjá bókasafnið binascii notað. Það gerir nákvæmlega það sama en notar föllin hexlify og unhexlify:

>>> import binascii

# binascii.hexlify breytir hráum bætum í hex-bætaskrifaðan streng
>>> binascii.hexlify(b"ABC")
b'414243'

# binascii.unhexlify breytir hex-streng í hrá bæti
>>> binascii.unhexlify("414243")
b'ABC'

Athugaðu að hexlify skilar bytes hlut (b'414243'), en ekki venjulegum streng ('414243'). Til að breyta því í venjulegan streng þarftu að keyra .decode() aftan á það.


Tvíundakóðun í Python: Base64, Base32 og Base85

Í kaflanum um gagnakóðun skoðuðum við hvernig við getum pakkað hráum tvíundagögnum (bætum) inn í textastrengi sem nota einungis ákveðin ASCII tákn, eins og með Base64, Base32 og Base85 kóðunum.

Í netöryggisþrautum og CTF-keppnum muntu stöðugt rekast á fána eða gögn sem hafa verið kóðuð á þennan hátt. Þótt við getum notað CyberChef til að gera þessar breytingar fljótt í vafra, þá er Python okkar öflugasta tól til að sjálfvirknivæða þetta í skriftum.

Python býður upp á innbyggða bókasafnið base64 sem gerir okkur kleift að umbreyta þessum gögnum á mjög einfaldan hátt.

Base64 í Python

Til að nota þessar kóðanir þurfum við að flytja inn base64 safnið. ATH: Þessi föll vinna einungis á bætum (bytes), svo við verðum að gæta þess að nota b"" eða keyra .encode() fyrst.

import base64

# Kóðun: Úr hráum bætum yfir í Base64-bæti
>>> upprunaleg_baeti = b"GGF"
>>> kodi_baeti = base64.b64encode(upprunaleg_baeti)
>>> kodi_baeti
b'R0dG'

# Ef við viljum fá venjulegan textastreng getum við afkóðað bætastrenginn:
>>> kodi_baeti.decode("utf-8")
'R0dG'

# Afkóðun: Úr Base64-streng yfir í hrá bæti
>>> base64.b64decode("R0dG")
b'GGF'

Base32 og Base85

Bókasafnið styður aðra grunna sem við kynntumst áður á nákvæmlega sama hátt með föllunum b32encode/b32decode og b85encode/b85decode:

# Base32 kóðun og afkóðun
>>> base64.b32encode(b"Ha")
b'MJSQ===='
>>> base64.b32decode(b"MJSQ====")
b'Ha'

# Base85 (Ascii85) kóðun og afkóðun
>>> base64.b85encode(b"Ha")
b'cYe|'
>>> base64.b85decode(b"cYe|")
b'Ha'

Sneiðar og skurðir (Slicing)

Þegar við erum að vinna með löng bætafylki (t.d. heilar skrár) þurfum við oft að geta bútað þau niður. Python býður upp á magnaðan rithátt til þess sem kallast sneiðar (e. slices).

Rithátturinn notar hornklofa aftan við breytuna: gogn[start:end:step] * start: Vísitalan (e. index) þar sem á að byrja (innifalin, byrjar á 0). * end: Vísitalan þar sem á að hætta (ekki innifalin!). * step: Hversu mörg skref á að taka í einu (sjálfgefið 1).

Prófum þetta með dæmi:

>>> málsháttur = b"HAKKARASKOLINN"

Sækjum fyrstu 4 bætin:

>>> málsháttur[0:4]
b'HAKK'

Sækjum gildi frá vísitölu 4 til loka (við getum sleppt end ef við viljum fara alla leið á enda):

>>> málsháttur[4:]
b'ARASKOLINN'

Sækjum hvern annan staf í öllum strengnum:

>>> málsháttur[::2]
b'HKKRSOLN'

Öfugi galdurinn: [::-1]

Ef við setjum -1 sem skrefstærð (step), þá fer Python afturábak í gegnum gögnin! Þetta er fljótlegasta leiðin í Python til að snúa streng eða bætum við:

>>> málsháttur[::-1]
b'NNILOKARAKKAH'

Fróðleikur: Bætaröð (Endianness)

Í tölvuarkitektúr eru stærri heiltölur vistuð í minni á tvo ólíka vegu: háenda (e. big-endian, eins og við skrifum tölur á blað) og lágenda (e. little-endian, þar sem minnsta bætið er ritað fyrst). X86/X64 örgjörvar (sem eru í flestum heimilistölvum) nota lágendaframsetningu. Ef við viljum lesa vistfang úr minni tölvunnar í gegnum Python þurfum við mjög oft að snúa bætunum við svo við fáum réttu töluna. Rithátturinn [::-1] er okkar helsta tól til þess!