Kirjoittaja: qalle
Kirjoitettu: 04.09.2015 – 24.08.2016
Tagit: algoritmit, koodi näytille, vinkki
Alla esitetään MD5-tiivisteen laskenta-algoritmi Pythonilla ilman valmiita kirjastoja. Olen käyttänyt apuna Wikipediassa olevaa pseudokoodia. Tavoittelin helppolukuisuutta nopeuden kustannuksella. Ohjelma on testattu Python 3:lla ja Windows 10:llä.
"""manual-md5.py - laskee MD5-tiivisteen merkkijonosta ilman valmiita kirjastoja; testattu Python 3:lla Windows 10:llä """ import sys import math import struct # algoritmin alkutila MD5_INITIAL_STATE = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476] # vakioita algoritmia varten MD5_SINE_TABLE = tuple( math.floor(abs(math.sin(i + 1)) * (2 ** 32)) for i in range(64) ) # merkkijonon oletuskoodaus DEFAULT_ENCODING = "utf-8" # ohjeteksti HELP_TEXT = """\ manual-md5.py v. 1.2 - laskee MD5-tiivisteen merkkijonosta ilman valmiita kirjastoja Komentoriviparametrit: KOODAUS MERKKIJONO KOODAUS Merkkijonon merkistökoodaus. Vapaaehtoinen. Esimerkkejä: utf-8 UTF-8 (oletus) utf-16-le UTF-16 little-endian utf-16-be UTF-16 big-endian cp1252 Windows-1252 Katso myös: http://docs.python.org/3/library/codecs.html#standard-encodings MERKKIJONO Merkkijono, josta MD5-tiiviste lasketaan. Ei saa sisältää merkkejä, joita valittu merkistökoodaus ei tue. Jos merkkijono sisältää välilyöntejä tai on tyhjä, laita sen ympärille lainausmerkit ("").\ """ def MD5_pad_message(message): """Käsittelee viestin MD5-tiivisteen laskentaa varten. Parametrit: message: viesti tavujonona Palautusarvo: viesti tavujonona. """ # ota muistiin viestin alkuperäinen pituus tavuina originalLength = len(message) # lisää viestiin ykkösbitti (ja 7 nollabittiä) message += b"\x80" # lisää viestiin 0-63 nollatavua niin, että # (viestin pituus bitteinä) % 512 = 448 # eli # (viestin pituus tavuina) % 64 = 56 message += (56 - len(message) % 64) % 64 * b"\x00" # lisää viestiin (alkuperäisen viestin pituus bitteinä) % (2 ** 64); # 64 bittiä eli 8 tavua, little-endian-tavujärjestys originalLengthInBitsMod64 = (originalLength * 8) & 0xffffffffffffffff message += struct.pack("<Q", originalLengthInBitsMod64) return message def MD5_hash_chunk(state, chunk): """Laskee MD5-tiivisteen yhdestä 64-tavuisesta lohkosta. Parametrit: state: algoritmin nykyinen tila (lista, jossa neljä 32-bittistä kokonaislukua) chunk: lohkon sisältö (lista, jossa kuusitoista 32-bittistä kokonaislukua) Palautusarvo: arvot, joilla algoritmin tilaa muutetaan (lista, jossa neljä 32-bittistä kokonaislukua). """ # alusta lohkon tiiviste (CH = chunk hash) samaan tilaan kuin viestin # tähänastinen tiiviste (CH0, CH1, CH2, CH3) = state # muuta algoritmin tilaa 64 kertaa for i in range(64): if i < 16: boolean = (CH1 & CH2) | (~CH1 & CH3) whichChunk = i shift = (7, 12, 17, 22)[i % 4] elif i < 32: boolean = (CH3 & CH1) | (~CH3 & CH2) whichChunk = (5 * i + 1) % 16 shift = (5, 9, 14, 20)[i % 4] elif i < 48: boolean = CH1 ^ CH2 ^ CH3 whichChunk = (3 * i + 5) % 16 shift = (4, 11, 16, 23)[i % 4] else: boolean = CH2 ^ (CH1 | ~CH3) whichChunk = 7 * i % 16 shift = (6, 10, 15, 21)[i % 4] # laske CH1:n muutos; summa pidetään 32-bittisenä etumerkittömänä # kokonaislukuna CH1Change = ( MD5_SINE_TABLE[i] + CH0 + boolean + chunk[whichChunk] ) & 0xffffffff # vieritä bittejä vasemmalle; ylivuotavat bitit palaavat vähiten # merkitseviksi; muuttuja pidetään edelleen 32-bittisenä CH1Change = ( ((CH1Change << shift) & 0xffffffff) | (CH1Change >> (32 - shift)) ) # muuta algoritmin tilaa; CH1 pidetään 32-bittisenä (CH0, CH1, CH2, CH3) = \ (CH3, (CH1 + CH1Change) & 0xffffffff, CH1, CH2) return [CH0, CH1, CH2, CH3] def MD5_hash(message): """Laskee MD5-tiivisteen viestistä. Parametrit: message: viesti (tavujono) Palautusarvo: MD5-tiiviste heksadesimaalisena (merkkijono). """ # alusta algoritmin tila (neljä 32-bittistä kokonaislukua) state = MD5_INITIAL_STATE.copy() # esikäsittele viesti message = MD5_pad_message(message) # lue tavut 512 bittiä (64 tavua) kerrallaan ja tulkitse jokainen 16:na # 32-bittisenä kokonaislukuna for chunk in struct.iter_unpack("<16I", message): # laske lohkon tiiviste ja lisää se algoritmin tilaan chunkHash = MD5_hash_chunk(state, chunk) for i in range(4): state[i] = (state[i] + chunkHash[i]) & 0xffffffff # muunna algoritmin lopullinen tila eli tiiviste tavujonoksi (kukin 32- # bittinen luku käyttää little-endian-tavujärjestystä) binaryState = b"".join(struct.pack("<I", number) for number in state) # palauta tiiviste heksadesimaalisena return "".join(format(byte, "02x") for byte in binaryState) def string_to_7bit(string): """Korvaa merkkijonossa esiintyvät 7-bittisen ASCII:n ulkopuoliset merkit kenoviiva-alkuisilla koodeilla. Parametrit: string: merkkijono Palautusarvo: merkkijono. """ return string.encode("ascii", errors = "backslashreplace").decode("ascii") def main(): """Pääohjelma.""" # lue komentoriviparametrit if len(sys.argv) == 1: # ei komentoriviparametreja; näytä ohjeteksti ja poistu exit(HELP_TEXT) elif len(sys.argv) == 2: # vain viesti encoding = DEFAULT_ENCODING message = sys.argv[1] elif len(sys.argv) == 3: # merkistökoodaus ja viesti (encoding, message) = sys.argv[1:] else: # liikaa parametreja; poistu exit( "Parametrien määrä ei kelpaa. Näet ohjeen ajamalla ohjelman ilman " "parametreja." ) # koodaa viesti merkkijonosta tavuiksi try: messageBytes = message.encode(encoding) except LookupError: printableEncoding = string_to_7bit(encoding) exit('Tuntematon merkistökoodaus "{:s}".'.format(printableEncoding)) except UnicodeError: exit("Merkkijono sisältää merkkejä, joita valittu koodaus ei tue.") # muotoile viesti tulostuskelpoiseksi printableMessage = string_to_7bit(message) # laske tiiviste tavuista hash = MD5_hash(messageBytes) print('MD5("{:s}") ='.format(printableMessage)) print(hash) # suorita pääohjelma, jos tätä moduulia ei ajeta toisen moduulin alla if __name__ == "__main__": main()
manual-md5.py v. 1.2 - laskee MD5-tiivisteen merkkijonosta ilman valmiita kirjastoja Komentoriviparametrit: KOODAUS MERKKIJONO KOODAUS Merkkijonon merkistökoodaus. Vapaaehtoinen. Esimerkkejä: utf-8 UTF-8 (oletus) utf-16-le UTF-16 little-endian utf-16-be UTF-16 big-endian cp1252 Windows-1252 Katso myös: http://docs.python.org/3/library/codecs.html#standard-encodings MERKKIJONO Merkkijono, josta MD5-tiiviste lasketaan. Ei saa sisältää merkkejä, joita valittu merkistökoodaus ei tue. Jos merkkijono sisältää välilyöntejä tai on tyhjä, laita sen ympärille lainausmerkit ("").
C:\>python manual-md5-1.2.py password MD5("password") = 5f4dcc3b5aa765d61d8327deb882cf99
Kun ajan ohjelman parametreillä -u ŋŋ
, tulee virhe:
C:\x\Desktop>md5.py -u ŋŋ Traceback (most recent call last): File "C:\x\md5.py", line 189, in <module> NormalMode(message, useUTF8) File "C:\x\md5.py", line 159, in NormalMode print("viesti merkkijonona: \"%s\"" % message) File "C:\Python34\lib\encodings\cp850.py", line 19, in encode return codecs.charmap_encode(input,self.errors,encoding_map)[0] UnicodeEncodeError: 'charmap' codec can't encode characters in position 22-23: character maps to <undefined>
Kiitos, guuglasin ratkaisun StackOverflow'sta. Nykyinen versio 1.1 korvaa tulostuksessa merkit, joita konsolin koodaus ei tue, "\u"-alkuisilla koodeilla (rivit 158–165).