snap_crypt.py :  » Development » SnapLogic » snaplogic » common » 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 » Development » SnapLogic 
SnapLogic » snaplogic » common » snap_crypt.py
# $SnapHashLicense:
# 
# SnapLogic - Open source data services
# 
# Copyright (C) 2008-2009, SnapLogic, Inc.  All rights reserved.
# 
# See http://www.snaplogic.org for more information about
# the SnapLogic project. 
# 
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
# 
# "SnapLogic" is a trademark of SnapLogic, Inc.
# 
# 
# $

# $Id: snap_crypt.py 7646 2009-05-11 20:33:05Z darin $


"""
This module contains hashing and obfuscation functions.

For starters, we have some obfuscation-related functions.
Not really 'crypt', but this module seemed to be the best
place for them.

At the end then, we have the public domain implementation of
the md5crypt algorithm. We would like to fully acknowledge
Poul-Henning Kamp, the original author of the md5crypt algorithm,
as well as Michal Wallace, who has done this Python implementation
of the algorithm.

SnapLogic has done two small modification: According to the
Python documentation, the md5 module is outdated with Python2.5.
Instead, it is recommedned to use hashlib. We have done this
very small change.

Also, we have removed the test cases at the end of the module.

Other than that, the program text at the end was left entirely
unchanged.

"""

import os
import array
import random
import base64
import string

seed_array = range(ord('a'),ord('z'))+range(ord('A'),ord('Z'))
char_array = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

# -------------------------------------------------------------------
# We don't want to send passwords and credentials that are needed
# by our components to contact other servers across the wire in clear
# text. We don't want to store them in clear text either. However,
# without proper key management, we cannot securely encrypt them.
# Since insecure encryption is not any better than mere obfuscation,
# we are just sticking to obfuscation for now. If you want to
# secure the transfer of data between server processes, please use
# SSL. The README contains information on how to set up SnapLogic
# to run behind a proxy that uses SSL. If you want to secure the
# data on disk, please place the repository in directories with
# proper, secure access rights.
# -------------------------------------------------------------------

def obfuscate(text):
    """
    Obfuscate some unicode text.

    Multiple obfuscations of the same text should all look
    different and have different lengths. That 'looks' more
    secure, and makes trivial plain-text attacks a bit more
    difficult. But then, of course, it's not like this
    algorithm here is particularly 'secret' (not that this
    would be a good idea anyway).

    So, why bother? Well, the results are sufficiently random
    looking to allow a later drop in replacement with something
    that actually encrypts, should we ever choose to do so.

    @param  text:   Some text.
    @type   text:   string (unicode)

    @return:        An obfuscated version of the same string.
    @rtype:         string

    """
    try:
        # Complicated by the fact that text may be unicode
        u = text.encode("utf-8")
        a = array.array('b')
        a.fromstring(u)

        # The result should have a random length, so we are
        # adding some random padding bytes
        random_pad_len = random.randint(3,17)   # How many?
        for i in xrange(random_pad_len):
            a.append(random.choice(seed_array))
        l = len(a)-1                             # Position of last pad byte

        # We select a random seed. The number of padding bytes
        # is appended (obfuscated by the random seed) and also
        # the random seed itself.
        random_seed = random.choice(seed_array)
        a.append(random_seed ^ random_pad_len)
        a.append(random_seed)

        # Process all bytes backwards, starting from the last random pad
        prev = random_seed
        for i in xrange(l, -1, -1):
            prev = a[i] = a[i] ^ prev

        # Calculate a simple 8-bit checksum and append
        csum = 0
        for b in a:
            csum ^= b
        a.append(csum)

        # Don't like the '=' padding bytes that base64 encoding adds
        # in the end. We count and remove them and then add a character
        # in the very end that tells us how many of those we have removed.
        b64_result    = base64.b64encode(a.tostring().encode("utf-8"))
        num_b64_pads  = b64_result[4:].count("=")
        if num_b64_pads:
            return b64_result[:-num_b64_pads] + str(num_b64_pads)
        else:
            return b64_result + random.choice(char_array)
    except:
        return None


def deobfuscate(text):
    """
    De-obfuscates a string and returns the original.

    This is the opposite to obfuscate().

    @param  text:   Some obfuscated text as produced by obfuscate().
    @type   text:   string (unicode)

    @return:        A clear-text version of the same string.
    @rtype:         string

    """
    try:
        # The last character implies the number of "=" pads that
        # were added via base64 encoding. We are restoring those
        # first.
        l = len(text)
        a = array.array('b')
        num_b64_pads_str = text[l-1]
        if num_b64_pads_str.isdigit():
            num_pads = int(num_b64_pads_str)
        else:
            num_pads = 0
        a.fromstring(base64.b64decode(text[:l-1] + "=" * num_pads))

        # Last character is a checksum
        p = len(a)-1
        csum = a[p]
        new_csum = 0
        for b in a[:-1]:
            new_csum ^= b
        if new_csum != csum:
            raise Exception("Wrong checksum")

        # Next up from the end is the random seed
        p -= 1
        random_seed = a[p]

        # The random pad length is next
        p -= 1
        random_pad_len = a[p] ^ random_seed
        # We can now 'decrypt' all the bytes of the array the same
        # way we encrypted them in 'obfuscate'
        l = p-1
        prev = random_seed
        for i in xrange(l, -1, -1):
            new_prev = a[i]
            a[i] = a[i] ^ prev
            prev = new_prev

        orig_len = p-random_pad_len
        res = a[:orig_len].tostring().decode("utf-8")
        return res
    except:
        return None


def output_obfuscate(text, n):
    """
    Produce string representation of obfuscated parameter.

    'n' determines if the entire output should be obfuscated
    (0), whther the first n characters should be shown in
    clear text (positive integer) or the last n characters
    in the clear (negative integer).

    If the entire output is to be obfuscated, it simply
    returns 8 "*". Otherwise, it returns n clear text
    characters, followed or preceded by exactly as many "*"
    as there would be remaining characters in the text.

    The function sees if it can deobfuscate the provided
    text. If that fails, it uses the text as is.

    @param text:                Possibly obfuscated text.
    @type  text:                string

    @param n:                   An integer, indicating how many
                                (if any) characters should be shown
                                in clear text and whether these
                                characters are at the start or end
                                of the text.
    @type  n:                   integer

    @return:                    String representation of the
                                obfuscated text, using "*" to show
                                the obfuscation.
    @rtype:                     string

    """
    if n == 0:
        return "********"

    # See if we can deobfuscate the text
    ctext = deobfuscate(text)
    if not ctext:
        ctext = text

    if n > 0:
        # The first 'n' characters in clear text, the rest as "*"
        return ctext[:n] + len(ctext[n:]) * "*"
    else:
        return len(ctext[:n]) * "*" + ctext[n:]


def make_salt():
    """
    Return a random 8 character string as a salt.

    @return:    An 8 character salt.
    @rtype:     string

    """
    c = list(string.letters+string.digits)
    buf = ""
    l = len(c)-1
    random.seed()
    # 8 bytes of salt
    for i in xrange(0,8):
        buf += c[random.randint(0,l)]
    return buf


# -------------------------------------------------------------
# Here now the mostly original md5crypt program text, including
# all acknowledgements.
# -------------------------------------------------------------

#########################################################
# md5crypt.py
#
# 0423.2000 by michal wallace http://www.sabren.com/
# based on perl's Crypt::PasswdMD5 by Luis Munoz (lem@cantv.net)
# based on /usr/src/libcrypt/crypt.c from FreeBSD 2.2.5-RELEASE
#
# MANY THANKS TO
#
#  Carey Evans - http://home.clear.net.nz/pages/c.evans/
#  Dennis Marti - http://users.starpower.net/marti1/
#
#  For the patches that got this thing working!
#
#########################################################
"""md5crypt.py - Provides interoperable MD5-based crypt() function

SYNOPSIS

  import md5crypt.py

  cryptedpassword = md5crypt.md5crypt(password, salt);

DESCRIPTION

unix_md5_crypt() provides a crypt()-compatible interface to the
rather new MD5-based crypt() function found in modern operating systems.
It's based on the implementation found on FreeBSD 2.2.[56]-RELEASE and
contains the following license in it:

 "THE BEER-WARE LICENSE" (Revision 42):
 <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
 can do whatever you want with this stuff. If we meet some day, and you think
 this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp

apache_md5_crypt() provides a function compatible with Apache's
.htpasswd files. This was contributed by Bryan Hart <bryan@eai.com>.

"""

MAGIC = '$1$'      # Magic string
ITOA64 = "./0123456789" + char_array

# Modified to use hashlib, rather than the md5 module, since that supposedly
# is not deprecated.

import hashlib

def to64 (v, n):
    ret = ''
    while (n - 1 >= 0):
        n = n - 1
  ret = ret + ITOA64[v & 0x3f]
  v = v >> 6
    return ret


def apache_md5_crypt (pw, salt):
    # change the Magic string to match the one used by Apache
    return unix_md5_crypt(pw, salt, '$apr1$')


def unix_md5_crypt(pw, salt, magic=None):
    
    if magic==None:
        magic = MAGIC

    # Take care of the magic string if present
    if salt[:len(magic)] == magic:
        salt = salt[len(magic):]
        

    # salt can have up to 8 characters:
    import string
    salt = string.split(salt, '$', 1)[0]
    salt = salt[:8]

    ctx = pw + magic + salt

    final = hashlib.md5(pw + salt + pw).digest()

    for pl in range(len(pw),0,-16):
        if pl > 16:
            ctx = ctx + final[:16]
        else:
            ctx = ctx + final[:pl]


    # Now the 'weird' xform (??)

    i = len(pw)
    while i:
        if i & 1:
            ctx = ctx + chr(0)  #if ($i & 1) { $ctx->add(pack("C", 0)); }
        else:
            ctx = ctx + pw[0]
        i = i >> 1

    final = hashlib.md5(ctx).digest()
    
    # The following is supposed to make
    # things run slower. 

    # my question: WTF???

    for i in range(10):
        ctx1 = ''
        if i & 1:
            ctx1 = ctx1 + pw
        else:
            ctx1 = ctx1 + final[:16]

        if i % 3:
            ctx1 = ctx1 + salt

        if i % 7:
            ctx1 = ctx1 + pw

        if i & 1:
            ctx1 = ctx1 + final[:16]
        else:
            ctx1 = ctx1 + pw
            
            
        final = hashlib.md5(ctx1).digest()


    # Final xform
                                
    passwd = ''

    passwd = passwd + to64((int(ord(final[0])) << 16)
                           |(int(ord(final[6])) << 8)
                           |(int(ord(final[12]))),4)

    passwd = passwd + to64((int(ord(final[1])) << 16)
                           |(int(ord(final[7])) << 8)
                           |(int(ord(final[13]))), 4)

    passwd = passwd + to64((int(ord(final[2])) << 16)
                           |(int(ord(final[8])) << 8)
                           |(int(ord(final[14]))), 4)

    passwd = passwd + to64((int(ord(final[3])) << 16)
                           |(int(ord(final[9])) << 8)
                           |(int(ord(final[15]))), 4)

    passwd = passwd + to64((int(ord(final[4])) << 16)
                           |(int(ord(final[10])) << 8)
                           |(int(ord(final[5]))), 4)

    passwd = passwd + to64((int(ord(final[11]))), 2)


    return magic + salt + '$' + passwd


## assign a wrapper function:
md5crypt = unix_md5_crypt

def generate_random_string():
    """Generate a random value."""
    return base64.urlsafe_b64encode(os.urandom(30))

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.