0,0 → 1,349 |
#!/usr/bin/env python |
# -*- coding: iso-8859-1 |
|
# Note that PyPy contains also a built-in module 'sha' which will hide |
# this one if compiled in. |
|
"""A sample implementation of SHA-1 in pure Python. |
|
Framework adapted from Dinu Gherman's MD5 implementation by |
J. Hallén and L. Creighton. SHA-1 implementation based directly on |
the text of the NIST standard FIPS PUB 180-1. |
""" |
|
|
__date__ = '2004-11-17' |
__version__ = 0.91 # Modernised by J. Hallén and L. Creighton for Pypy |
|
|
import struct, copy |
|
|
# ====================================================================== |
# Bit-Manipulation helpers |
# |
# _long2bytes() was contributed by Barry Warsaw |
# and is reused here with tiny modifications. |
# ====================================================================== |
|
def _long2bytesBigEndian(n, blocksize=0): |
"""Convert a long integer to a byte string. |
|
If optional blocksize is given and greater than zero, pad the front |
of the byte string with binary zeros so that the length is a multiple |
of blocksize. |
""" |
|
# After much testing, this algorithm was deemed to be the fastest. |
s = '' |
pack = struct.pack |
while n > 0: |
s = pack('>I', n & 0xffffffffL) + s |
n = n >> 32 |
|
# Strip off leading zeros. |
for i in range(len(s)): |
if s[i] <> '\000': |
break |
else: |
# Only happens when n == 0. |
s = '\000' |
i = 0 |
|
s = s[i:] |
|
# Add back some pad bytes. This could be done more efficiently |
# w.r.t. the de-padding being done above, but sigh... |
if blocksize > 0 and len(s) % blocksize: |
s = (blocksize - len(s) % blocksize) * '\000' + s |
|
return s |
|
|
def _bytelist2longBigEndian(list): |
"Transform a list of characters into a list of longs." |
|
imax = len(list)/4 |
hl = [0L] * imax |
|
j = 0 |
i = 0 |
while i < imax: |
b0 = long(ord(list[j])) << 24 |
b1 = long(ord(list[j+1])) << 16 |
b2 = long(ord(list[j+2])) << 8 |
b3 = long(ord(list[j+3])) |
hl[i] = b0 | b1 | b2 | b3 |
i = i+1 |
j = j+4 |
|
return hl |
|
|
def _rotateLeft(x, n): |
"Rotate x (32 bit) left n bits circularly." |
|
return (x << n) | (x >> (32-n)) |
|
|
# ====================================================================== |
# The SHA transformation functions |
# |
# ====================================================================== |
|
def f0_19(B, C, D): |
return (B & C) | ((~ B) & D) |
|
def f20_39(B, C, D): |
return B ^ C ^ D |
|
def f40_59(B, C, D): |
return (B & C) | (B & D) | (C & D) |
|
def f60_79(B, C, D): |
return B ^ C ^ D |
|
|
f = [f0_19, f20_39, f40_59, f60_79] |
|
# Constants to be used |
K = [ |
0x5A827999L, # ( 0 <= t <= 19) |
0x6ED9EBA1L, # (20 <= t <= 39) |
0x8F1BBCDCL, # (40 <= t <= 59) |
0xCA62C1D6L # (60 <= t <= 79) |
] |
|
class sha: |
"An implementation of the MD5 hash function in pure Python." |
|
def __init__(self): |
"Initialisation." |
|
# Initial message length in bits(!). |
self.length = 0L |
self.count = [0, 0] |
|
# Initial empty message as a sequence of bytes (8 bit characters). |
self.input = [] |
|
# Call a separate init function, that can be used repeatedly |
# to start from scratch on the same object. |
self.init() |
|
|
def init(self): |
"Initialize the message-digest and set all fields to zero." |
|
self.length = 0L |
self.input = [] |
|
# Initial 160 bit message digest (5 times 32 bit). |
self.H0 = 0x67452301L |
self.H1 = 0xEFCDAB89L |
self.H2 = 0x98BADCFEL |
self.H3 = 0x10325476L |
self.H4 = 0xC3D2E1F0L |
|
def _transform(self, W): |
|
for t in range(16, 80): |
W.append(_rotateLeft( |
W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1) & 0xffffffffL) |
|
A = self.H0 |
B = self.H1 |
C = self.H2 |
D = self.H3 |
E = self.H4 |
|
""" |
This loop was unrolled to gain about 10% in speed |
for t in range(0, 80): |
TEMP = _rotateLeft(A, 5) + f[t/20] + E + W[t] + K[t/20] |
E = D |
D = C |
C = _rotateLeft(B, 30) & 0xffffffffL |
B = A |
A = TEMP & 0xffffffffL |
""" |
|
for t in range(0, 20): |
TEMP = _rotateLeft(A, 5) + ((B & C) | ((~ B) & D)) + E + W[t] + K[0] |
E = D |
D = C |
C = _rotateLeft(B, 30) & 0xffffffffL |
B = A |
A = TEMP & 0xffffffffL |
|
for t in range(20, 40): |
TEMP = _rotateLeft(A, 5) + (B ^ C ^ D) + E + W[t] + K[1] |
E = D |
D = C |
C = _rotateLeft(B, 30) & 0xffffffffL |
B = A |
A = TEMP & 0xffffffffL |
|
for t in range(40, 60): |
TEMP = _rotateLeft(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2] |
E = D |
D = C |
C = _rotateLeft(B, 30) & 0xffffffffL |
B = A |
A = TEMP & 0xffffffffL |
|
for t in range(60, 80): |
TEMP = _rotateLeft(A, 5) + (B ^ C ^ D) + E + W[t] + K[3] |
E = D |
D = C |
C = _rotateLeft(B, 30) & 0xffffffffL |
B = A |
A = TEMP & 0xffffffffL |
|
|
self.H0 = (self.H0 + A) & 0xffffffffL |
self.H1 = (self.H1 + B) & 0xffffffffL |
self.H2 = (self.H2 + C) & 0xffffffffL |
self.H3 = (self.H3 + D) & 0xffffffffL |
self.H4 = (self.H4 + E) & 0xffffffffL |
|
|
# Down from here all methods follow the Python Standard Library |
# API of the sha module. |
|
def update(self, inBuf): |
"""Add to the current message. |
|
Update the md5 object with the string arg. Repeated calls |
are equivalent to a single call with the concatenation of all |
the arguments, i.e. m.update(a); m.update(b) is equivalent |
to m.update(a+b). |
|
The hash is immediately calculated for all full blocks. The final |
calculation is made in digest(). It will calculate 1-2 blocks, |
depending on how much padding we have to add. This allows us to |
keep an intermediate value for the hash, so that we only need to |
make minimal recalculation if we call update() to add more data |
to the hashed string. |
""" |
|
leninBuf = long(len(inBuf)) |
|
# Compute number of bytes mod 64. |
index = (self.count[1] >> 3) & 0x3FL |
|
# Update number of bits. |
self.count[1] = self.count[1] + (leninBuf << 3) |
if self.count[1] < (leninBuf << 3): |
self.count[0] = self.count[0] + 1 |
self.count[0] = self.count[0] + (leninBuf >> 29) |
|
partLen = 64 - index |
|
if leninBuf >= partLen: |
self.input[index:] = list(inBuf[:partLen]) |
self._transform(_bytelist2longBigEndian(self.input)) |
i = partLen |
while i + 63 < leninBuf: |
self._transform(_bytelist2longBigEndian(list(inBuf[i:i+64]))) |
i = i + 64 |
else: |
self.input = list(inBuf[i:leninBuf]) |
else: |
i = 0 |
self.input = self.input + list(inBuf) |
|
|
def digest(self): |
"""Terminate the message-digest computation and return digest. |
|
Return the digest of the strings passed to the update() |
method so far. This is a 16-byte string which may contain |
non-ASCII characters, including null bytes. |
""" |
|
H0 = self.H0 |
H1 = self.H1 |
H2 = self.H2 |
H3 = self.H3 |
H4 = self.H4 |
input = [] + self.input |
count = [] + self.count |
|
index = (self.count[1] >> 3) & 0x3fL |
|
if index < 56: |
padLen = 56 - index |
else: |
padLen = 120 - index |
|
padding = ['\200'] + ['\000'] * 63 |
self.update(padding[:padLen]) |
|
# Append length (before padding). |
bits = _bytelist2longBigEndian(self.input[:56]) + count |
|
self._transform(bits) |
|
# Store state in digest. |
digest = _long2bytesBigEndian(self.H0, 4) + \ |
_long2bytesBigEndian(self.H1, 4) + \ |
_long2bytesBigEndian(self.H2, 4) + \ |
_long2bytesBigEndian(self.H3, 4) + \ |
_long2bytesBigEndian(self.H4, 4) |
|
self.H0 = H0 |
self.H1 = H1 |
self.H2 = H2 |
self.H3 = H3 |
self.H4 = H4 |
self.input = input |
self.count = count |
|
return digest |
|
|
def hexdigest(self): |
"""Terminate and return digest in HEX form. |
|
Like digest() except the digest is returned as a string of |
length 32, containing only hexadecimal digits. This may be |
used to exchange the value safely in email or other non- |
binary environments. |
""" |
return ''.join(['%02x' % ord(c) for c in self.digest()]) |
|
def copy(self): |
"""Return a clone object. |
|
Return a copy ('clone') of the md5 object. This can be used |
to efficiently compute the digests of strings that share |
a common initial substring. |
""" |
|
return copy.deepcopy(self) |
|
|
# ====================================================================== |
# Mimic Python top-level functions from standard library API |
# for consistency with the md5 module of the standard library. |
# ====================================================================== |
|
# These are mandatory variables in the module. They have constant values |
# in the SHA standard. |
|
digest_size = digestsize = 20 |
blocksize = 1 |
|
def new(arg=None): |
"""Return a new sha crypto object. |
|
If arg is present, the method call update(arg) is made. |
""" |
|
crypto = sha() |
if arg: |
crypto.update(arg) |
|
return crypto |
|