mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-02-15 15:32:06 +00:00
335 lines
10 KiB
Python
335 lines
10 KiB
Python
#!/usr/local/bin/python -Ou
|
|
|
|
""" enc - encrypt/decrypt files using one of SSLeay's ciphers.
|
|
|
|
Copyright (c) 1998 by Marc-Andre Lemburg; All Rights Reserved;
|
|
mailto:mal@lemburg.com; See the documentation for further
|
|
copyright information or contact the author.
|
|
|
|
DISCLAIMER & WARNING: This tool comes with NO WARRANTY. Use at
|
|
YOUR OWN RISK. It may destroy data ! There is NO way to recover a
|
|
forgotten pass phrase !
|
|
"""
|
|
import exceptions,os,string,time,sys
|
|
from CryptoWorld import Ciphers,Hashes,Utils
|
|
from CommandLine import Application,SwitchOption,ArgumentOption
|
|
|
|
# Globals
|
|
verbose = 0
|
|
|
|
# Maximum block size used for en/decryption
|
|
MAX_BLOCKSIZE = 1024 * 1000
|
|
|
|
class OperationalError(exceptions.StandardError):
|
|
pass
|
|
|
|
def filesize(file):
|
|
|
|
oldpos = file.tell()
|
|
file.seek(0,2)
|
|
size = file.tell()
|
|
file.seek(oldpos)
|
|
return size
|
|
|
|
def invisible_input(prompt='>>> '):
|
|
|
|
""" Adapted from the Python 1.5.1 docs example getpass()
|
|
"""
|
|
import termios,TERMIOS
|
|
fd = sys.stdin.fileno()
|
|
old = termios.tcgetattr(fd)
|
|
new = termios.tcgetattr(fd)
|
|
new[3] = new[3] & ~TERMIOS.ECHO # fix lflags
|
|
try:
|
|
termios.tcsetattr(fd, TERMIOS.TCSADRAIN, new)
|
|
passwd = raw_input(prompt)
|
|
finally:
|
|
termios.tcsetattr(fd, TERMIOS.TCSADRAIN, old)
|
|
print
|
|
return passwd
|
|
|
|
def tempfile(filename='tmp',
|
|
|
|
maxint=sys.maxint,time=time.time,int=int,hex=hex,
|
|
exists=os.path.exists):
|
|
|
|
""" Return a new filename for a temporary file (based on filename).
|
|
"""
|
|
temp = filename + '.' + hex(maxint % int(time())) + '.tmp'
|
|
if not exists(temp):
|
|
return temp
|
|
# Ok, find an alternative name
|
|
i = 0
|
|
while 1:
|
|
temp = '%s.%s-%i.tmp' % (filename,hex(maxint % int(time())),i)
|
|
if not exists(temp):
|
|
return temp
|
|
i = i + 1
|
|
|
|
# Global key
|
|
_key = ''
|
|
|
|
def get_cipher(name,check=0):
|
|
|
|
global _key
|
|
|
|
cc = getattr(Ciphers,name)
|
|
keysize = cc.keysize
|
|
if not _key:
|
|
while 1:
|
|
key1 = invisible_input('Please enter the key phrase: ')
|
|
if check:
|
|
key2 = invisible_input('Please reenter the phrase: ')
|
|
if key1 != key2:
|
|
print "Phrases don't match. Please start again..."
|
|
continue
|
|
if len(key1) == 0:
|
|
print "Empty key phrase. Please start again..."
|
|
else:
|
|
break
|
|
_key = key1
|
|
key = _key
|
|
# Fit key
|
|
if keysize > 0:
|
|
if len(key) < keysize:
|
|
key = key + \
|
|
'Do not change this string, it is important !'\
|
|
[:keysize - len(key)]
|
|
elif len(key) > keysize:
|
|
key = key[:keysize]
|
|
cipher = cc(key,Ciphers.CBC)
|
|
return cipher
|
|
|
|
def reset_key():
|
|
|
|
global _key
|
|
|
|
_key = ''
|
|
|
|
###
|
|
|
|
def encrypt(filename,ciphername,overwrite=0):
|
|
|
|
if verbose:
|
|
print 'Encrypting:',filename
|
|
if filename[-4:] == '.enc':
|
|
raise OperationalError,'already encrypted'
|
|
if not os.path.isfile(filename):
|
|
raise OperationalError,'not a file or not found'
|
|
|
|
# Check overwrites
|
|
if os.path.exists(filename + '.enc'):
|
|
if not overwrite:
|
|
raise OperationalError,'would overwrite an existing file'
|
|
elif os.path.samefile(filename, filename + '.enc'):
|
|
raise OperationalError,'would overwrite the original file'
|
|
|
|
# Open plain file
|
|
f = open(filename,'rb')
|
|
size = filesize(f)
|
|
if verbose:
|
|
print ' total size: %i bytes' % size
|
|
|
|
# Open work file
|
|
workfilename = tempfile(filename)
|
|
out = open(workfilename,'wb')
|
|
|
|
try:
|
|
# Init cipher and write header
|
|
cipher = get_cipher(ciphername,check=1)
|
|
out.write('enc %s %s %i\n' % \
|
|
(repr(filename),ciphername,size))
|
|
|
|
# Init hash and blocksize
|
|
hash = Hashes.MD5()
|
|
blocksize = size
|
|
if blocksize > MAX_BLOCKSIZE:
|
|
blocksize = MAX_BLOCKSIZE
|
|
blocksize = ((blocksize + cipher.blocksize - 1) / cipher.blocksize) \
|
|
* cipher.blocksize
|
|
|
|
# Write the encrypted data in blocks
|
|
bytesread = 0
|
|
while bytesread < size:
|
|
if verbose:
|
|
print ' reading %i bytes...' % blocksize,
|
|
block = f.read(blocksize)
|
|
if verbose:
|
|
print 'read %i bytes' % len(block)
|
|
bytesread = bytesread + len(block)
|
|
hash.update(block)
|
|
if bytesread == size:
|
|
# Final block
|
|
offset = len(block) % cipher.blocksize
|
|
if offset:
|
|
padsize = cipher.blocksize - offset
|
|
block = block + '\0'*padsize
|
|
if verbose:
|
|
print ' padding with %i bytes' % (padsize)
|
|
encblock = cipher.encrypt(block)
|
|
out.write(encblock)
|
|
|
|
# Write hash value
|
|
hash_value = hash.digest()
|
|
if verbose:
|
|
print ' hash value:',repr(hash_value)
|
|
out.write(hash_value)
|
|
|
|
# Copy work file to .enc file
|
|
out.close()
|
|
f.close()
|
|
os.rename(workfilename,filename+'.enc')
|
|
workfilename = None
|
|
|
|
finally:
|
|
if workfilename:
|
|
if not out.closed:
|
|
out.close()
|
|
os.remove(workfilename)
|
|
|
|
###
|
|
|
|
def decrypt(filename,overwrite=0):
|
|
|
|
if verbose:
|
|
print 'Decrypting:',filename
|
|
if filename[-4:] != '.enc':
|
|
raise OperationalError,'decrypt a plain file'
|
|
if not os.path.isfile(filename):
|
|
raise OperationalError,'not a file or not found'
|
|
|
|
# Read header from cipher file
|
|
f = open(filename,'rb')
|
|
header = string.split(f.readline())
|
|
if len(header) != 4:
|
|
raise OperationalError,'wrong header format:'+ str(header)
|
|
origfilename = eval(header[1])
|
|
ciphername = header[2]
|
|
size = string.atoi(header[3])
|
|
if verbose:
|
|
print ' total size: %i bytes' % size
|
|
|
|
# Check overwrites
|
|
if os.path.exists(origfilename):
|
|
if not overwrite:
|
|
raise OperationalError,'would overwrite an existing file'
|
|
elif os.path.samefile(origfilename, filename):
|
|
raise OperationalError,'would overwrite the encrypted file'
|
|
|
|
# Open work file
|
|
workfilename = tempfile(filename)
|
|
out = open(workfilename,'wb')
|
|
|
|
try:
|
|
|
|
# Load cipher and init hash
|
|
cipher = get_cipher(ciphername)
|
|
hash = Hashes.MD5()
|
|
|
|
# Read the encrypted data in blocks
|
|
blocksize = size
|
|
if blocksize > MAX_BLOCKSIZE:
|
|
blocksize = MAX_BLOCKSIZE
|
|
blocksize = ((blocksize + cipher.blocksize - 1) / cipher.blocksize) \
|
|
* cipher.blocksize
|
|
bytesread = 0
|
|
while bytesread < size:
|
|
if size - bytesread < blocksize:
|
|
# Read remaining data only
|
|
blocksize = size - bytesread
|
|
blocksize = ((blocksize + cipher.blocksize - 1) / \
|
|
cipher.blocksize) * cipher.blocksize
|
|
if verbose:
|
|
print ' reading %i bytes...' % blocksize,
|
|
encblock = f.read(blocksize)
|
|
if verbose:
|
|
print 'read %i bytes' % len(encblock)
|
|
bytesread = bytesread + len(encblock)
|
|
block = cipher.decrypt(encblock)
|
|
if bytesread > size:
|
|
# Depad
|
|
padsize = bytesread - size
|
|
block = block[:-padsize]
|
|
if verbose:
|
|
print ' depadded last block by %i bytes' % (padsize)
|
|
hash.update(block)
|
|
out.write(block)
|
|
|
|
# Check hash value
|
|
hash_value = f.read(hash.digestsize)
|
|
if verbose:
|
|
print ' hash value:',repr(hash_value)
|
|
if hash_value != hash.digest():
|
|
raise OperationalError,'data corrupt'
|
|
|
|
# Copy workfile to origfile
|
|
out.close()
|
|
f.close()
|
|
os.rename(workfilename,origfilename)
|
|
workfilename = None
|
|
|
|
finally:
|
|
if workfilename:
|
|
if not out.closed:
|
|
out.close()
|
|
os.remove(workfilename)
|
|
|
|
###
|
|
|
|
class Encrypt(Application):
|
|
|
|
header = "File encryption utility using the SSLeay ciphers"
|
|
|
|
about = """\
|
|
Encrypts or decrypts the files given on the command line. If no
|
|
options are given the filenames extensions are taken as hint: '.enc'
|
|
means encrypted, everything else not encrypted. The utility then goes
|
|
and switches the state of the files. Overwriting of files only takes
|
|
place in case the '-O' switch is set.
|
|
|
|
The following ciphers are supported:
|
|
RC2, RC4, RC5, IDEA, Blowfish, DES, DES3, CAST
|
|
|
|
This tool comes with NO WARRANTY. Use at YOUR OWN RISK. It may destroy
|
|
data ! There is NO way to recover a forgotten pass phrase !
|
|
"""
|
|
|
|
options = [SwitchOption('-e', 'encrypt'),
|
|
SwitchOption('-d', 'decyrpt'),
|
|
SwitchOption('-a', 'use the same key for all files'),
|
|
SwitchOption('-O', 'allow overwrites (use with care)'),
|
|
ArgumentOption('-c', 'cipher to use', 'RC5'),
|
|
]
|
|
|
|
def main(self):
|
|
|
|
overwrite = self.values['-O']
|
|
ciphername = self.values['-c']
|
|
samekey = self.values['-a']
|
|
for file in self.files:
|
|
if not samekey:
|
|
reset_key()
|
|
print '-'*78
|
|
print 'Working on file:',file
|
|
try:
|
|
if self.values['-e']:
|
|
encrypt(file,ciphername,overwrite)
|
|
elif self.values['-d']:
|
|
decrypt(file,overwrite)
|
|
elif file[-4:] != '.enc':
|
|
encrypt(file,ciphername,overwrite)
|
|
else:
|
|
decrypt(file,overwrite)
|
|
except OperationalError,why:
|
|
print '%s skipped -- %s' % (file,why)
|
|
except IOError,(code,why):
|
|
print '%s skipped -- %s' % (file,why)
|
|
except os.error,why:
|
|
print '%s skipped -- %s' % (file,why)
|
|
except KeyboardInterrupt:
|
|
print '*user break*'
|
|
break
|
|
|
|
if __name__ == '__main__':
|
|
Encrypt()
|