#!/usr/bin/python
#
# utility for in-place partition encryption with LUKS container;
# see also:
#
# http://help.ubuntu.com/community/EncryptedFilesystemHowto7
#
# milki placed this into the PUBLIC DOMAIN.
import os
import sys
import re
from random import randint as rand
from getopt import gnu_getopt as getopt
class luksConvert:
def run(self):
print "\033[31m"+"luksConvert v1.0 - NO WARRANTY. USE AT YOUR OWN RISK."+"\033[0;0m\n"
self.get_args()
self.testrun_cryptsetup()
self.check_sdev()
self.start()
def get_args(self):
opts,files = getopt(sys.argv[1:], "yvc:s:h:", ["cipher=","hash=","key-size="])
opts = dict(opts)
if len(files) != 1:
print "syntax: luksConvert /dev/unencrypted [...]"
print " further options similar to cryptsetup:"
print " [-y] -[v] [-c aes-cbc-essiv:sha256] [-h sha1] [-s 128]\n"
sys.exit()
else:
self.sdev = files[0]
self.hash = opts.get("--hash", opts.get("-h", "sha1"))
self.keysize = opts.get("--key-size", opts.get("-s", "128"))
self.cipher = opts.get("--cipher", opts.get("-c", "aes-cbc-essiv:sha256"))
def luksCheck(self, devicename):
output = self.pipe("LC_ALL=C cryptsetup luksDump " + devicename)
return output.find("offset:\t1032") >= 0
def testrun_cryptsetup(self):
print "* performing cryptsetup test run... ",
fn = "/tmp/lC-test-"+str(rand(1000,9999))
os.system("dd if=/dev/zero of="+fn+" bs=1000 count=10000 2>/dev/null")
os.system("mkfs.vfat "+fn+" >/dev/null")
loop = self.pipe("losetup -f").strip()
os.system("losetup "+loop+" "+fn+" >/dev/null")
ok = 0==os.system("echo passw0rd | cryptsetup luksFormat "+loop+" -q -c "+self.cipher+" -h "+self.hash+" -s "+self.keysize+" 2>/dev/null")
ok = ok and self.luksCheck(loop)
os.system("losetup -d "+loop+" ; rm "+fn);
if ok:
print "ok\n"
else:
print "fail\n WARNING: cryptsetup test run didn't work as expected\n"
sys.exit()
return ok
def check_sdev(self):
# read
f = open(self.sdev, "rb")
startsector = f.read(2048)
f.close()
# check magic numbers
if startsector[0x438:0x43A] != "\x53\xEF":
print "* "+self.sdev+" doesn't seem to contain ext2/ext3 system\n"
if startsector[0:4] == "LUKS":
print "* Source device already has a LUKS header. Don't run this tool twice!"
print " (Or put back the backup on, if the last run of luksConvert failed.)\n"
sys.exit()
# check filesystem size < below partition size (-1032)
print "* Hopefully you have shrunken your filesystem on " + self.sdev + "\n already - by 1032 sectors / 516KB at least! (Use resize2fs.)\n"
return 0
def cmd(self, cmd):
print cmd
return not os.system(cmd)
def pipe(self, cmd):
pc = os.popen(cmd, "r")
pc_s = ""
try:
pc_s = pc.read(4000)
except:
0
return pc_s
# walks through the cryptsetup and sector copying process
def start(self):
# read first 5MB
source = blockdevice(self.sdev, "rb")
data1 = source.readBlock()
# back it up
fn = "luksconvert.backup."+str(rand(1000,9999))
f = open(fn, "wb")
f.write(data1)
f.close()
print "* First 5MB of source device " + self.sdev + " backed up into\n "+fn+"\n"
# create encrypted devmapper node
cryptnode = "lconvert" + str(rand(1000,9999))
print "* DO NOT ABORT AFTER THIS POINT"
print " first sectors will be overwritten, AFTER you entered a password\n"
if not self.cmd("cryptsetup luksFormat "+self.sdev+" -v -y -c "+self.cipher+" -h "+self.hash+" -s "+self.keysize):
print "\nERROR. ABORTING.\n"
sys.exit()
# check cryptsetup
if (not self.luksCheck(self.sdev)):
print "\n* WARNING: the written LUKS header seems to be larger than 1032 sectors. Unexpected. Read buffer (5MB) might have been too small and data already lost. But proceeding anyway. (If you abort here, be sure to put the backup block onto the source device.)\n"
# init devmapper node, open encrypted target
print "\n* yes, you have to enter your password once more:\n"
while not self.cmd("cryptsetup luksOpen "+self.sdev+" "+cryptnode):
print "* ERROR. If you already forgot the password, you need the sectorbackup."
dest = blockdevice("/dev/mapper/"+cryptnode, "wb")
# copying process
print "* now copying everything over... please wait\n"
#
# first block is already read,
# get a second 5MB, before writing the first,
# because the last 516K data always overlap
#
while (1):
# info
print " " + str(source.pos / 1024) + "K\r",
# pre-read next block
data2 = source.readBlock()
if not len(data2):
break;
# write
dest.writeBlock(data1)
data1 = data2
# write remaining
dest.writeBlock(data1)
print " \n"
# close up
dest.handle.close()
source.handle.close()
self.cmd("cryptsetup luksClose "+cryptnode)
print
self.cmd("sync")
# thanks for buying
print "\n* done \n"
class blockdevice:
def __init__(self, fn, mode, bsize=5*1024*1024):
self.fn = fn
self.handle = open(fn, mode)
self.bsize = bsize
self.pos = 0
def readBlock(self):
try:
block = self.handle.read(self.bsize)
except:
print "eof / read error"
self.pos += len(block)
return block
def writeBlock(self, block):
try:
self.handle.write(block)
except Exception, e:
print
print e
print "\n* writing error or device "+self.fn+" full"
print " (should in fact occour for the last 1032 sectors from source device)"
self.pos += len(block)
luksConvert().run()