crypt.py :  » Content-Management-Systems » PyLucid » PyLucid_standalone » pylucid_project » utils » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Content Management Systems » PyLucid 
PyLucid » PyLucid_standalone » pylucid_project » utils » crypt.py
# -*- coding: utf-8 -*-

"""
    PyLucid.tools.crypt
    ~~~~~~~~~~~~~~~~~~~

    -Two usefull salt hash functions. (Used in the _install section for login.)
    -A one-time-pad XOR crypter. (Used for the SHA-JS-Login)

    unittest: ./dev_scripts/unittests/unittest_crypt.py

    Last commit info:
    ~~~~~~~~~~~~~~~~~
    $LastChangedDate$
    $Rev$
    $Author$

    :copyleft: 2007-2009 by the PyLucid team, see AUTHORS for more details.
    :license: GNU GPL v3 or above, see LICENSE for more details.
"""


import os, sys, time, random, base64, re
try:
    import hashlib
    sha_constructor = hashlib.sha1
except ImportError:
    import sha
    sha_constructor = sha.new

if __name__ == "__main__":
    print "Local DocTest..."
    settings = type('Mock', (object,), {})()
    settings.SECRET_KEY = "DocTest"
    smart_str = str
else:
    from django.conf import settings
    from django.utils.encoding import smart_str


# Warning: Debug must always be False in productiv environment!
#DEBUG = True
DEBUG = False
if DEBUG:
    import warnings
    warnings.warn("Debugmode is on", UserWarning)

HASH_TYP = "sha1"

SALT_LEN = 5 # length of the random salt value
HASH_LEN = 40 # length of a SHA-1 hexdigest

# SHA-1 hexdigest + "sha1" + (2x "$") + salt length
SALT_HASH_LEN = HASH_LEN + 4 + 2 + SALT_LEN


class SaltHashError(Exception):
    pass

#______________________________________________________________________________

SHA1_RE = re.compile(r'[a-f0-9]{40}$')

def validate_sha_value(sha_value):
    """
    Check if the given >sha_value< is a possible SHA1 hexdigest ;)
    returned true or false

    Should we better use a RE method?
    http://www.python-forum.de/post-74657.html

    >>> validate_sha_value("wrong length")
    False
    >>> validate_sha_value(1234)
    False
    >>> validate_sha_value("right length but not a SHA1 hexdigest!!!")
    False
    >>> validate_sha_value("790f2ebcb902c966fb0e232515ec1319dc9118af")
    True
    """
    if not isinstance(sha_value, basestring):
        return False

    if SHA1_RE.match(sha_value):
        return True

    return False
#
#    if not (isinstance(sha_value, basestring) and len(sha_value) == HASH_LEN):
#        return False
#
#    try:
#        int(sha_value, 16)
#    except (ValueError, OverflowError), e:
#        return False
#    else:
#        return True

#______________________________________________________________________________


def get_new_seed(can_debug=True):
    """
    Generate a new, random seed value.

    >>> get_new_seed() # DEBUG is True in DocTest!
    'DEBUG_1234567890'
    >>> seed = get_new_seed(can_debug=False)
    >>> assert seed != 'DEBUG', "seed is: %s" % seed
    >>> assert len(seed) == HASH_LEN, "Wrong length: %s" % len(seed)
    """
    if can_debug and DEBUG:
        seed = "DEBUG_1234567890"
    else:
        raw_seed = "%s%s%s%s" % (
            random.randint(0, sys.maxint - 1), os.getpid(), time.time(),
            settings.SECRET_KEY
        )
        seed = sha_constructor(raw_seed).hexdigest()

    return seed


def get_new_salt(can_debug=True):
    """
    Generate a new, random salt value.

    >>> get_new_salt() # DEBUG is True in DocTest!
    'DEBUG'
    >>> salt = get_new_salt(can_debug=False)
    >>> assert salt != 'DEBUG_1234567890', "salt is: %s" % salt
    >>> assert len(salt) == SALT_LEN, "Wrong length: %s" % len(salt)
    """
    seed = get_new_seed(can_debug)
    return seed[:SALT_LEN]

def get_pseudo_salt(*args):
    """
    generate a pseudo salt (used, if user is wrong)
    """
    temp = "".join([repr(arg) for arg in args])
    return sha_constructor(temp).hexdigest()[:SALT_LEN]


def make_hash(txt, salt):
    """
    make a SHA1 hexdigest from the given >txt< and >salt<.
    IMPORTANT:
        This routine must work like
        django.contrib.auth.models.User.set_password()!

    >>> make_hash(txt="test", salt='DEBUG')
    '790f2ebcb902c966fb0e232515ec1319dc9118af'
    """
    hash = sha_constructor(salt + smart_str(txt)).hexdigest()
    return hash


def get_salt_and_hash(txt):
    """
    Generate a hast value with a random salt
    returned salt and hash as a tuple

    >>> get_salt_and_hash("test")
    ('sha1', 'DEBUG', '790f2ebcb902c966fb0e232515ec1319dc9118af')
    """
    if not isinstance(txt, str):
        raise SaltHashError("Only string allowed!")

    salt = get_new_salt()
    hash = make_hash(txt, salt)

    return (HASH_TYP, salt, hash)


def make_salt_hash(txt):
    """
    make from the given string a hash with a salt value
    returned one string back

    >>> make_salt_hash("test")
    'sha1$DEBUG$790f2ebcb902c966fb0e232515ec1319dc9118af'
    """
    salt_hash = "$".join(get_salt_and_hash(txt))
    return salt_hash


def check_salt_hash(txt, salt_hash):
    """
    compare txt with the salt-hash.

    TODO: Should we used the django function for this?
        Look at: django.contrib.auth.models.check_password

    >>> salt_hash = make_salt_hash("test")
    >>> salt_hash
    'sha1$DEBUG$790f2ebcb902c966fb0e232515ec1319dc9118af'
    >>> check_salt_hash("test", salt_hash)
    True
    """
#    if not (isinstance(txt, str) and isinstance(salt_hash, str)):
#        raise SaltHashError("Only string allowed!")

    if len(salt_hash) != SALT_HASH_LEN:
        raise SaltHashError("Wrong salt-hash length.")

    try:
        type, salt, hash = salt_hash.split("$")
    except ValueError:
        raise SaltHashError("Wrong salt-hash format.")

    if type != "sha1":
        raise SaltHashError("Unsupported hash method.")

    test_hash = make_hash(txt, salt)
#    raise
    if hash != test_hash:
        msg = "salt-hash compare failed."
        if DEBUG:
            msg += " (txt: '%s', salt: '%s', hash: '%s', test_hash: '%s')" % (
                txt, salt, hash, test_hash
            )
        raise SaltHashError(msg)

    return True


def salt_hash_to_dict(salt_hash):
    """
    >>> salt_hash_to_dict("sha$salt_value$the_SHA_value")
    {'salt': 'salt_value', 'type': 'sha', 'hash': 'the_SHA_value'}
    """
    type, salt, hash = salt_hash.split("$")
    return {
        "type": type,
        "salt": salt,
        "hash": hash
    }


#______________________________________________________________________________


def crypt(txt, key):
    """
    XOR ciphering
    >txt< and >key< should be unicode.

    >>> crypt("1234", "ABCD")
    u'pppp'
    """
    assert len(txt) == len(key), "Error: txt and key must have the same length!"

    crypted = [unichr(ord(t) ^ ord(k)) for t, k in zip(txt, key)]
    return u"".join(crypted)


def encrypt(txt, key, use_base64=True, can_debug=True):
    """
    XOR ciphering with a SHA salt-hash checksum

    >>> encrypt(u"1234", u"ABCD") # DEBUG is True in DocTest!
    u'crypt 1234 with ABCD'

    >>> encrypt(u"1234", u"ABCD", can_debug=False)
    u'sha1$DEBUG$b323f546665b1f034742630133d1b489480a24e2cHBwcA=='

    >>> encrypt(u"1234", u"ABCD", use_base64=False, can_debug=False)
    u'sha1$DEBUG$b323f546665b1f034742630133d1b489480a24e2pppp'
    """
    if not (isinstance(txt, unicode) and isinstance(key, unicode)):
        raise UnicodeError("Only unicode allowed!")

    if can_debug and DEBUG:
        return "crypt %s with %s" % (txt, key)

    salt_hash = make_salt_hash(repr(txt))
    salt_hash = unicode(salt_hash)

    crypted = crypt(txt, key)
    if use_base64 == True:
        crypted = base64.b64encode(crypted)
    return salt_hash + crypted


def decrypt(crypted, key, use_base64=True, can_debug=True):
    """
    1. Decrypt a XOR crypted String.
    2. Compare the inserted sSHA salt-hash checksum.

    >>> decrypt(u'crypt 1234 with ABCD', u"ABCD") # DEBUG is True in DocTest!
    u'1234'

    >>> crypted = encrypt(u"1234", u"ABCD", can_debug=False)
    >>> crypted
    u'sha1$DEBUG$b323f546665b1f034742630133d1b489480a24e2cHBwcA=='
    >>> decrypt(crypted, u"ABCD", can_debug=False)
    u'1234'

    >>> crypted = encrypt(u"1234", u"ABCD", use_base64=False, can_debug=False)
    >>> decrypt(crypted, u"ABCD", use_base64=False, can_debug=False)
    u'1234'
    """
    if not (isinstance(crypted, unicode) and isinstance(key, unicode)):
        raise UnicodeError("Only unicode allowed!")

    if can_debug and DEBUG:
        txt, _, key2 = crypted.split(" ", 3)[1:]
        assert key == key2, "key: %s != key2: %s" % (key, key2)
        return txt

    salt_hash = str(crypted[:SALT_HASH_LEN])
    crypted = crypted[SALT_HASH_LEN:]
    if use_base64 == True:
        crypted = base64.b64decode(crypted)
        crypted = unicode(crypted)

    decrypted = crypt(crypted, key)

    # raised a SaltHashError() if the checksum is wrong:
    check_salt_hash(repr(decrypted), salt_hash)

    return decrypted


#______________________________________________________________________________

def django_to_sha_checksum(django_salt_hash):
    """
    Create a JS-SHA-Checksum from the django user password.
    (For the unittest)

    The >django_salt_hash< is:
        user = User.objects.get(...)
        django_salt_hash = user.password

    >>> django_to_sha_checksum("sha1$DEBUG$50b412a7ef09f4035f2daca882a1f8bfbe263b62")
    ('DEBUG', u'crypt 50b412a7ef09f4035f2d with aca882a1f8bfbe263b62')
    """
    hash_typ, salt, hash = django_salt_hash.split("$")
    assert hash_typ == "sha1", "hash typ not supported!"
    assert len(hash) == HASH_LEN, "Wrong hash length! (Not a SHA1 hash?)"

    # Split the SHA1-Hash in two pieces
    sha_a = hash[:(HASH_LEN / 2)]
    sha_b = hash[(HASH_LEN / 2):]

    sha_a = unicode(sha_a)
    sha_b = unicode(sha_b)
    sha_checksum = encrypt(txt=sha_a, key=sha_b)

    return salt, sha_checksum

def make_sha_checksum2(raw_password):
    """
    Create a SHA1-JS-Login checksum from a plaintext password.

    >>> make_sha_checksum2("test")
    ('DEBUG', u'crypt 790f2ebcb902c966fb0e with 232515ec1319dc9118af')
    """
    _, salt, hash_value = get_salt_and_hash(raw_password)

    return salt, make_sha_checksum(hash_value)

def make_sha_checksum(hash_value):
    """
    Made the needed sha_checksum for the SHA1-JS-Login.

    >>> make_sha_checksum("50b412a7ef09f4035f2daca882a1f8bfbe263b62")
    u'crypt 50b412a7ef09f4035f2d with aca882a1f8bfbe263b62'
    """
    # Split the SHA1-Hash in two pieces
    sha_a = hash_value[:(HASH_LEN / 2)]
    sha_b = hash_value[(HASH_LEN / 2):]

    sha_a = unicode(sha_a)
    sha_b = unicode(sha_b)
    sha_checksum = encrypt(txt=sha_a, key=sha_b)
    return sha_checksum


def check_js_sha_checksum(challenge, sha_a2, sha_b, sha_checksum):
    """
    Check a PyLucid JS-SHA-Login

    >>> salt1 = "a salt value"
    >>> challenge = "debug"
    >>> password = "test"
    >>>
    >>> hash_value = make_hash(password, salt1)
    >>> hash_value
    'f893fc3ebdfd886836822161b6bc2ccac955e014'
    >>> sha_checksum = make_sha_checksum(hash_value)
    >>> sha_checksum
    u'crypt f893fc3ebdfd88683682 with 2161b6bc2ccac955e014'
    >>>
    >>> sha_a = hash_value[:(HASH_LEN/2)]
    >>> sha_a
    'f893fc3ebdfd88683682'
    >>> sha_b = hash_value[(HASH_LEN/2):]
    >>> sha_b
    '2161b6bc2ccac955e014'
    >>> sha_a2 = make_hash(sha_a, challenge)
    >>> sha_a2
    '0d96f2fdda9c6f633ba0f5c2619aa7706abc492d'
    >>>
    >>> check_js_sha_checksum(challenge, sha_a2, sha_b, sha_checksum)
    True
    """
    sha_checksum = unicode(sha_checksum)
    sha_b = unicode(sha_b)

    encrypted_checksum = decrypt(sha_checksum, sha_b)
    client_checksum = make_hash(encrypted_checksum, challenge)

    if client_checksum == sha_a2:
        return True

    return False




def _doc_test(verbose):
    global DEBUG
    DEBUG = True

    import doctest
    doctest.testmod(verbose=verbose)

if __name__ == "__main__":
    _doc_test(verbose=False)
#    _doc_test(verbose=True)
    print "DocTest end."
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.