Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
sun_md5_crypt.py364 linesDownload Raw Back to handlers
1"""passlib.handlers.sun_md5_crypt - Sun's Md5 Crypt, used on Solaris2 3.. warning::4 5    This implementation may not reproduce6    the original Solaris behavior in some border cases.7    See documentation for details.8"""9 10#=============================================================================11# imports12#=============================================================================13# core14from hashlib import md515import re16import logging; log = logging.getLogger(__name__)17from warnings import warn18# site19# pkg20from passlib.utils import to_unicode21from passlib.utils.binary import h6422from passlib.utils.compat import byte_elem_value, irange, u, \23                                 uascii_to_str, unicode, str_to_bascii24import passlib.utils.handlers as uh25# local26__all__ = [27    "sun_md5_crypt",28]29 30#=============================================================================31# backend32#=============================================================================33# constant data used by alg - Hamlet act 3 scene 1 + null char34# exact bytes as in http://www.ibiblio.org/pub/docs/books/gutenberg/etext98/2ws2610.txt35# from Project Gutenberg.36 37MAGIC_HAMLET = (38    b"To be, or not to be,--that is the question:--\n"39    b"Whether 'tis nobler in the mind to suffer\n"40    b"The slings and arrows of outrageous fortune\n"41    b"Or to take arms against a sea of troubles,\n"42    b"And by opposing end them?--To die,--to sleep,--\n"43    b"No more; and by a sleep to say we end\n"44    b"The heartache, and the thousand natural shocks\n"45    b"That flesh is heir to,--'tis a consummation\n"46    b"Devoutly to be wish'd. To die,--to sleep;--\n"47    b"To sleep! perchance to dream:--ay, there's the rub;\n"48    b"For in that sleep of death what dreams may come,\n"49    b"When we have shuffled off this mortal coil,\n"50    b"Must give us pause: there's the respect\n"51    b"That makes calamity of so long life;\n"52    b"For who would bear the whips and scorns of time,\n"53    b"The oppressor's wrong, the proud man's contumely,\n"54    b"The pangs of despis'd love, the law's delay,\n"55    b"The insolence of office, and the spurns\n"56    b"That patient merit of the unworthy takes,\n"57    b"When he himself might his quietus make\n"58    b"With a bare bodkin? who would these fardels bear,\n"59    b"To grunt and sweat under a weary life,\n"60    b"But that the dread of something after death,--\n"61    b"The undiscover'd country, from whose bourn\n"62    b"No traveller returns,--puzzles the will,\n"63    b"And makes us rather bear those ills we have\n"64    b"Than fly to others that we know not of?\n"65    b"Thus conscience does make cowards of us all;\n"66    b"And thus the native hue of resolution\n"67    b"Is sicklied o'er with the pale cast of thought;\n"68    b"And enterprises of great pith and moment,\n"69    b"With this regard, their currents turn awry,\n"70    b"And lose the name of action.--Soft you now!\n"71    b"The fair Ophelia!--Nymph, in thy orisons\n"72    b"Be all my sins remember'd.\n\x00" #<- apparently null at end of C string is included (test vector won't pass otherwise)73)74 75# NOTE: these sequences are pre-calculated iteration ranges used by X & Y loops w/in rounds function below76xr = irange(7)77_XY_ROUNDS = [78    tuple((i,i,i+3) for i in xr), # xrounds 079    tuple((i,i+1,i+4) for i in xr), # xrounds 180    tuple((i,i+8,(i+11)&15) for i in xr), # yrounds 081    tuple((i,(i+9)&15, (i+12)&15) for i in xr), # yrounds 182]83del xr84 85def raw_sun_md5_crypt(secret, rounds, salt):86    """given secret & salt, return encoded sun-md5-crypt checksum"""87    global MAGIC_HAMLET88    assert isinstance(secret, bytes)89    assert isinstance(salt, bytes)90 91    # validate rounds92    if rounds <= 0:93        rounds = 094    real_rounds = 4096 + rounds95    # NOTE: spec seems to imply max 'rounds' is 2**32-196 97    # generate initial digest to start off round 0.98    # NOTE: algorithm 'salt' includes full config string w/ trailing "$"99    result = md5(secret + salt).digest()100    assert len(result) == 16101 102    # NOTE: many things in this function have been inlined (to speed up the loop103    #       as much as possible), to the point that this code barely resembles104    #       the algorithm as described in the docs. in particular:105    #106    #       * all accesses to a given bit have been inlined using the formula107    #         rbitval(bit) = (rval((bit>>3) & 15) >> (bit & 7)) & 1108    #109    #       * the calculation of coinflip value R has been inlined110    #111    #       * the conditional division of coinflip value V has been inlined as112    #         a shift right of 0 or 1.113    #114    #       * the i, i+3, etc iterations are precalculated in lists.115    #116    #       * the round-based conditional division of x & y is now performed117    #         by choosing an appropriate precalculated list, so that it only118    #         calculates the 7 bits which will actually be used.119    #120    X_ROUNDS_0, X_ROUNDS_1, Y_ROUNDS_0, Y_ROUNDS_1 = _XY_ROUNDS121 122    # NOTE: % appears to be *slightly* slower than &, so we prefer & if possible123 124    round = 0125    while round < real_rounds:126        # convert last result byte string to list of byte-ints for easy access127        rval = [ byte_elem_value(c) for c in result ].__getitem__128 129        # build up X bit by bit130        x = 0131        xrounds = X_ROUNDS_1 if (rval((round>>3) & 15)>>(round & 7)) & 1 else X_ROUNDS_0132        for i, ia, ib in xrounds:133            a = rval(ia)134            b = rval(ib)135            v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1)136            x |= ((rval((v>>3)&15)>>(v&7))&1) << i137 138        # build up Y bit by bit139        y = 0140        yrounds = Y_ROUNDS_1 if (rval(((round+64)>>3) & 15)>>(round & 7)) & 1 else Y_ROUNDS_0141        for i, ia, ib in yrounds:142            a = rval(ia)143            b = rval(ib)144            v = rval((a >> (b % 5)) & 15) >> ((b>>(a&7)) & 1)145            y |= ((rval((v>>3)&15)>>(v&7))&1) << i146 147        # extract x'th and y'th bit, xoring them together to yeild "coin flip"148        coin = ((rval(x>>3) >> (x&7)) ^ (rval(y>>3) >> (y&7))) & 1149 150        # construct hash for this round151        h = md5(result)152        if coin:153            h.update(MAGIC_HAMLET)154        h.update(unicode(round).encode("ascii"))155        result = h.digest()156 157        round += 1158 159    # encode output160    return h64.encode_transposed_bytes(result, _chk_offsets)161 162# NOTE: same offsets as md5_crypt163_chk_offsets = (164    12,6,0,165    13,7,1,166    14,8,2,167    15,9,3,168    5,10,4,169    11,170)171 172#=============================================================================173# handler174#=============================================================================175class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler):176    """This class implements the Sun-MD5-Crypt password hash, and follows the :ref:`password-hash-api`.177 178    It supports a variable-length salt, and a variable number of rounds.179 180    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:181 182    :type salt: str183    :param salt:184        Optional salt string.185        If not specified, a salt will be autogenerated (this is recommended).186        If specified, it must be drawn from the regexp range ``[./0-9A-Za-z]``.187 188    :type salt_size: int189    :param salt_size:190        If no salt is specified, this parameter can be used to specify191        the size (in characters) of the autogenerated salt.192        It currently defaults to 8.193 194    :type rounds: int195    :param rounds:196        Optional number of rounds to use.197        Defaults to 34000, must be between 0 and 4294963199, inclusive.198 199    :type bare_salt: bool200    :param bare_salt:201        Optional flag used to enable an alternate salt digest behavior202        used by some hash strings in this scheme.203        This flag can be ignored by most users.204        Defaults to ``False``.205        (see :ref:`smc-bare-salt` for details).206 207    :type relaxed: bool208    :param relaxed:209        By default, providing an invalid value for one of the other210        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,211        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`212        will be issued instead. Correctable errors include ``rounds``213        that are too small or too large, and ``salt`` strings that are too long.214 215        .. versionadded:: 1.6216    """217    #===================================================================218    # class attrs219    #===================================================================220    name = "sun_md5_crypt"221    setting_kwds = ("salt", "rounds", "bare_salt", "salt_size")222    checksum_chars = uh.HASH64_CHARS223    checksum_size = 22224 225    # NOTE: docs say max password length is 255.226    # release 9u2227 228    # NOTE: not sure if original crypt has a salt size limit,229    # all instances that have been seen use 8 chars.230    default_salt_size = 8231    max_salt_size = None232    salt_chars = uh.HASH64_CHARS233 234    default_rounds = 34000 # current passlib default235    min_rounds = 0236    max_rounds = 4294963199 ##2**32-1-4096237        # XXX: ^ not sure what it does if past this bound... does 32 int roll over?238    rounds_cost = "linear"239 240    ident_values = (u("$md5$"), u("$md5,"))241 242    #===================================================================243    # instance attrs244    #===================================================================245    bare_salt = False # flag to indicate legacy hashes that lack "$$" suffix246 247    #===================================================================248    # constructor249    #===================================================================250    def __init__(self, bare_salt=False, **kwds):251        self.bare_salt = bare_salt252        super(sun_md5_crypt, self).__init__(**kwds)253 254    #===================================================================255    # internal helpers256    #===================================================================257    @classmethod258    def identify(cls, hash):259        hash = uh.to_unicode_for_identify(hash)260        return hash.startswith(cls.ident_values)261 262    @classmethod263    def from_string(cls, hash):264        hash = to_unicode(hash, "ascii", "hash")265 266        #267        # detect if hash specifies rounds value.268        # if so, parse and validate it.269        # by end, set 'rounds' to int value, and 'tail' containing salt+chk270        #271        if hash.startswith(u("$md5$")):272            rounds = 0273            salt_idx = 5274        elif hash.startswith(u("$md5,rounds=")):275            idx = hash.find(u("$"), 12)276            if idx == -1:277                raise uh.exc.MalformedHashError(cls, "unexpected end of rounds")278            rstr = hash[12:idx]279            try:280                rounds = int(rstr)281            except ValueError:282                raise uh.exc.MalformedHashError(cls, "bad rounds")283            if rstr != unicode(rounds):284                raise uh.exc.ZeroPaddedRoundsError(cls)285            if rounds == 0:286                # NOTE: not sure if this is forbidden by spec or not;287                #      but allowing it would complicate things,288                #      and it should never occur anyways.289                raise uh.exc.MalformedHashError(cls, "explicit zero rounds")290            salt_idx = idx+1291        else:292            raise uh.exc.InvalidHashError(cls)293 294        #295        # salt/checksum separation is kinda weird,296        # to deal cleanly with some backward-compatible workarounds297        # implemented by original implementation.298        #299        chk_idx = hash.rfind(u("$"), salt_idx)300        if chk_idx == -1:301            # ''-config for $-hash302            salt = hash[salt_idx:]303            chk = None304            bare_salt = True305        elif chk_idx == len(hash)-1:306            if chk_idx > salt_idx and hash[-2] == u("$"):307                raise uh.exc.MalformedHashError(cls, "too many '$' separators")308            # $-config for $$-hash309            salt = hash[salt_idx:-1]310            chk = None311            bare_salt = False312        elif chk_idx > 0 and hash[chk_idx-1] == u("$"):313            # $$-hash314            salt = hash[salt_idx:chk_idx-1]315            chk = hash[chk_idx+1:]316            bare_salt = False317        else:318            # $-hash319            salt = hash[salt_idx:chk_idx]320            chk = hash[chk_idx+1:]321            bare_salt = True322 323        return cls(324            rounds=rounds,325            salt=salt,326            checksum=chk,327            bare_salt=bare_salt,328        )329 330    def to_string(self, _withchk=True):331        ss = u('') if self.bare_salt else u('$')332        rounds = self.rounds333        if rounds > 0:334            hash = u("$md5,rounds=%d$%s%s") % (rounds, self.salt, ss)335        else:336            hash = u("$md5$%s%s") % (self.salt, ss)337        if _withchk:338            chk = self.checksum339            hash = u("%s$%s") % (hash, chk)340        return uascii_to_str(hash)341 342    #===================================================================343    # primary interface344    #===================================================================345    # TODO: if we're on solaris, check for native crypt() support.346    #       this will require extra testing, to make sure native crypt347    #       actually behaves correctly. of particular importance:348    #       when using ""-config, make sure to append "$x" to string.349 350    def _calc_checksum(self, secret):351        # NOTE: no reference for how sun_md5_crypt handles unicode352        if isinstance(secret, unicode):353            secret = secret.encode("utf-8")354        config = str_to_bascii(self.to_string(_withchk=False))355        return raw_sun_md5_crypt(secret, self.rounds, config).decode("ascii")356 357    #===================================================================358    # eoc359    #===================================================================360 361#=============================================================================362# eof363#=============================================================================364 
codekingpro/portable-devtools · Team Ai