Kirjautuminen

Haku

Tehtävät

Koodit: Python: MD5-tiivisteen laskeminen ilman kirjastoja

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ä.

MD5-algoritmin toimintaperiaate lyhyesti

Lähdekoodi

"""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()

Ohje suoraan ohjelmasta

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 ("").

Käyttöesimerkki

C:\>python manual-md5-1.2.py password
MD5("password") =
5f4dcc3b5aa765d61d8327deb882cf99

Versiohistoria

Kommentit

qwerty12302 [10.09.2015 20:12:45]

#

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>

qalle [13.09.2015 07:27:21]

#

Kiitos, guuglasin ratkaisun StackOverflow'sta. Nykyinen versio 1.1 korvaa tulostuksessa merkit, joita konsolin koodaus ei tue, "\u"-alkuisilla koodeilla (rivit 158–165).

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta