# 2.20 The sha Module

### 2.20 The sha Module

The sha module provides an alternative way to calculate message signatures, as shown in Example 2-39. It's similar to the md5 module, but generates 160-bit signatures instead.

##### Example 2-39. Using the sha Module
File: sha-example-1.py

import sha

hash = sha.new()
hash.update("spam, spam, and eggs")

print repr(hash.digest())
print hash.hexdigest()

'13
File: sha-example-1.py import sha hash = sha.new() hash.update("spam, spam, and eggs") print repr(hash.digest()) print hash.hexdigest()

'\321\333\003\026I\331\272-j\303\247\240\345\343Tvq\364\346\311' d1db031649d9ba2d6ac3a7a0e5e3547671f4e6c9

36I12-j37053Tvq461' d1db031649d9ba2d6ac3a7a0e5e3547671f4e6c9

See the md5 examples for more ways to use sha signatures.

### 2.21 The crypt Module

(Optional) The crypt module implements one-way DES encryption. Unix systems use this encryption algorithm to store passwords, and this module is really only useful to generate or check such passwords.

Example 2-40 shows how to encrypt a password by calling crypt.crypt with the password string, plus a salt, which should consist of two random characters . You can now throw away the actual password, and just store the encrypted string.

##### Example 2-40. Using the crypt Module
File: crypt-example-1.py

import crypt

import random, string

def getsalt(chars = string.letters + string.digits):
# generate a random 2-character 'salt'
return random.choice(chars) + random.choice(chars)

print crypt.crypt("bananas", getsalt())

'py8UGrijma1j6'

To verify a given password, encrypt the new password using the two first characters from the encrypted string as the salt. If the result matches the encrypted string, the password is valid. Example 2-41 uses the pwd module to fetch the encrypted password for a given user .

##### Example 2-41. Using the crypt Module for Authentication
File: crypt-example-2.py

import pwd, crypt

def login(user, password):
"Check if user would be able to log in using password"
try:
pw1 = pwd.getpwnam(user)[1]
pw2 = crypt.crypt(password, pw1[:2])
return pw1 == pw2
except KeyError:
return 0 # no such user

user = raw_input("username:")
password = raw_input("password:")

if login(user, password):
print "welcome", user
else:
print "login failed"

For other ways to implement authentication, see the description of the md5 module.

### 2.22 The rotor Module

(Optional) The rotor module implements a simple encryption algorithm, shown in Example 2-42, which is based on the WWII Enigma engine.

##### Example 2-42. Using the rotor Module
File: rotor-example-1.py

import rotor

SECRET_KEY = "spam"
MESSAGE = "the holy grail"

r = rotor.newrotor(SECRET_KEY)

encoded_message = r.encrypt(MESSAGE)
decoded_message = r.decrypt(encoded_message)

print "original:", repr(MESSAGE)
print "encoded message:", repr(encoded_message)
print "decoded message:", repr(decoded_message)

original: 'the holy grail'
encoded message: '71455sw407270U'
decoded message: 'the holy grail'

### 2.23 The zlib Module

(Optional) The zlib module provides support for "zlib" compression. (This compression method is also known as "deflate.")

Example 2-43 shows how the compress and decompress functions take string arguments.

##### Example 2-43. Using the zlib Module to Compress a String
File: zlib-example-1.py

import zlib

MESSAGE = "life of brian"

compressed_message = zlib.compress(MESSAGE)
decompressed_message = zlib.decompress(compressed_message)

print "original:", repr(MESSAGE)
print "compressed message:", repr(compressed_message)
print "decompressed message:", repr(decompressed_message)

original: 'life of brian'
compressed message: 'x431LKU0OSH*2L4
File: zlib-example-1.py import zlib MESSAGE = "life of brian" compressed_message = zlib.compress(MESSAGE) decompressed_message = zlib.decompress(compressed_message) print "original:", repr(MESSAGE) print "compressed message:", repr(compressed_message) print "decompressed message:", repr(decompressed_message)

original: 'life of brian' compressed message: 'x\234\313\311LKU\310OSH*\312L\314\003\000!\010\004\302' decompressed message: 'life of brian'

3
File: zlib-example-1.py import zlib MESSAGE = "life of brian" compressed_message = zlib.compress(MESSAGE) decompressed_message = zlib.decompress(compressed_message) print "original:", repr(MESSAGE) print "compressed message:", repr(compressed_message) print "decompressed message:", repr(decompressed_message)

original: 'life of brian' compressed message: 'x\234\313\311LKU\310OSH*\312L\314\003\000!\010\004\302' decompressed message: 'life of brian'

0!0
File: zlib-example-1.py import zlib MESSAGE = "life of brian" compressed_message = zlib.compress(MESSAGE) decompressed_message = zlib.decompress(compressed_message) print "original:", repr(MESSAGE) print "compressed message:", repr(compressed_message) print "decompressed message:", repr(decompressed_message)

original: 'life of brian' compressed message: 'x\234\313\311LKU\310OSH*\312L\314\003\000!\010\004\302' decompressed message: 'life of brian'

42' decompressed message: 'life of brian'

The compression rate varies a lot, depending on the contents of the file, as you can see in Example 2-44.

##### Example 2-44. Using the zlib Module to Compress a Group of Files
File: zlib-example-2.py

import zlib
import glob

for file in glob.glob("samples/*"):

indata = open(file, "rb").read()
outdata = zlib.compress(indata, zlib.Z_BEST_COMPRESSION)

print file, len(indata), "=>", len(outdata),
print "%d%%" % (len(outdata) * 100 / len(indata))

samples\sample.au 1676 => 1109 66%
samples\sample.gz 42 => 51 121%
samples\sample.htm 186 => 135 72%
samples\sample.ini 246 => 190 77%
samples\sample.jpg 4762 => 4632 97%
samples\sample.msg 450 => 275 61%
samples\sample.sgm 430 => 321 74%
samples\sample.tar 10240 => 125 1%
samples\sample.tgz 155 => 159 102%
samples\sample.txt 302 => 220 72%
samples\sample.wav 13260 => 10992 82%

You can also compress or decompress data on the fly, which Example 2-45 demonstrates .

##### Example 2-45. Using the zlib Module to Decompress Streams
File: zlib-example-3.py

import zlib

encoder = zlib.compressobj()

data = encoder.compress("life")
data = data + encoder.compress(" of ")
data = data + encoder.compress("brian")
data = data + encoder.flush()

print repr(data)
print repr(zlib.decompress(data))

'x431LKU0OSH*2L4
File: zlib-example-3.py import zlib encoder = zlib.compressobj() data = encoder.compress("life") data = data + encoder.compress(" of ") data = data + encoder.compress("brian") data = data + encoder.flush() print repr(data) print repr(zlib.decompress(data))

'x\234\313\311LKU\310OSH*\312L\314\003\000!\010\004\302' 'life of brian'

3
File: zlib-example-3.py import zlib encoder = zlib.compressobj() data = encoder.compress("life") data = data + encoder.compress(" of ") data = data + encoder.compress("brian") data = data + encoder.flush() print repr(data) print repr(zlib.decompress(data))

'x\234\313\311LKU\310OSH*\312L\314\003\000!\010\004\302' 'life of brian'

0!0
File: zlib-example-3.py import zlib encoder = zlib.compressobj() data = encoder.compress("life") data = data + encoder.compress(" of ") data = data + encoder.compress("brian") data = data + encoder.flush() print repr(data) print repr(zlib.decompress(data))

'x\234\313\311LKU\310OSH*\312L\314\003\000!\010\004\302' 'life of brian'

42' 'life of brian'

Example 2-46 shows how to make it a bit more convenient to read a compressed file, by wrapping a decoder object in a file-like wrapper.

##### Example 2-46. Emulating a File Object for Compressed Streams
File: zlib-example-4.py

import zlib
import string, StringIO

class ZipInputStream:

def _ _init_ _(self, file):
self.file = file
self._ _rewind()

def _ _rewind(self):
self.zip = zlib.decompressobj()
self.pos = 0 # position in zipped stream
self.offset = 0 # position in unzipped stream
self.data = ""

def _ _fill(self, bytes):
if self.zip:
# read until we have enough bytes in the buffer
while not bytes or len(self.data) < bytes:
self.file.seek(self.pos)
data = self.file.read(16384)
if not data:
self.data = self.data + self.zip.flush()
self.zip = None # no more data
break
self.pos = self.pos + len(data)
self.data = self.data + self.zip.decompress(data)

def seek(self, offset, whence=0):
if whence == 0:
position = offset
elif whence == 1:
position = self.offset + offset
else:
raise IOError, "Illegal argument"
if position < self.offset:
raise IOError, "Cannot seek backwards"

# skip forward, in 16k blocks
while position > self.offset:
if not self.read(min(position - self.offset, 16384)):
break

def tell(self):
return self.offset

def read(self, bytes = 0):
self._ _fill(bytes)
if bytes:
data = self.data[:bytes]
self.data = self.data[bytes:]
else:
data = self.data
self.data = ""
self.offset = self.offset + len(data)
return data

def readline(self):
# make sure we have an entire line
while self.zip and "\n" not in self.data:
self._ _fill(len(self.data) + 512)
i = string.find(self.data, "\n") + 1
if i <= 0:
return self.read()
return self.read(i)

def readlines(self):
lines = []
while 1:
s = self.readline()
if not s:
break
lines.append(s)
return lines

#
# try it out

data = open("samples/sample.txt").read()
data = zlib.compress(data)

file = ZipInputStream(StringIO.StringIO(data))
for line in file.readlines():
print line[:-1]

We will perhaps eventually be writing only small
modules which are identified by name as they are
used to build larger ones, so that devices like
indentation, rather than delimiters, might become
feasible for expressing local structure in the
source language.
-- Donald E. Knuth, December 1974