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") =
5f4dcc3b5aa765d61d8327deb882cf99Kun 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).