| 
 | 1 | +# This file is part of the MicroPython project, http://micropython.org/  | 
 | 2 | +#  | 
 | 3 | +# The MIT License (MIT)  | 
 | 4 | +#  | 
 | 5 | +# Copyright (c) 2025 Martin Fischer <[email protected]>  | 
 | 6 | +#  | 
 | 7 | +# Permission is hereby granted, free of charge, to any person obtaining a copy  | 
 | 8 | +# of this software and associated documentation files (the "Software"), to deal  | 
 | 9 | +# in the Software without restriction, including without limitation the rights  | 
 | 10 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  | 
 | 11 | +# copies of the Software, and to permit persons to whom the Software is  | 
 | 12 | +# furnished to do so, subject to the following conditions:  | 
 | 13 | +#  | 
 | 14 | +# The above copyright notice and this permission notice shall be included in  | 
 | 15 | +# all copies or substantial portions of the Software.  | 
 | 16 | +#  | 
 | 17 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  | 
 | 18 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  | 
 | 19 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  | 
 | 20 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  | 
 | 21 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  | 
 | 22 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN  | 
 | 23 | +# THE SOFTWARE.  | 
 | 24 | +#  | 
 | 25 | +# A simple Wrapper class to encrypt an existing block device  | 
 | 26 | +# using AES CBS or AES ESSIV. Implementation should be compatible with Linux  | 
 | 27 | +# dcrypt/LUKS  | 
 | 28 | + | 
 | 29 | +import os  | 
 | 30 | +from cryptolib import aes  | 
 | 31 | +from hashlib import sha256  | 
 | 32 | +import struct  | 
 | 33 | + | 
 | 34 | +MODE_ECB = 1  | 
 | 35 | +MODE_CTR = 6   # AES counter mode  | 
 | 36 | +MODE_CBC = 2   # AES CBC mode  | 
 | 37 | + | 
 | 38 | +# CBC can be used together with "plain". It uses the sector number as IV.  | 
 | 39 | +# see also https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/FAQ.md?ref_type=heads chapter 5.13  | 
 | 40 | +# for pro/cons  | 
 | 41 | +ENCRYPT_AES_CBC = 1  | 
 | 42 | +# ESSIV is more secure and uses the hashed master key to obtain  | 
 | 43 | +# a new AES instance that can be used to encrypt the IV.  | 
 | 44 | +ENCRYPT_AES_ESSIV = 2  | 
 | 45 | + | 
 | 46 | + | 
 | 47 | +class CryptBdev():  | 
 | 48 | +    def __init__(self, bdev, key=None, mode=ENCRYPT_AES_ESSIV):  | 
 | 49 | +        self.key = key if key else os.urandom(32)  | 
 | 50 | +        self.bdev = bdev  | 
 | 51 | +        self.mode = mode  | 
 | 52 | +        # get block size of block device or default to 512 if none is returned  | 
 | 53 | +        self.block_size = self.bdev.ioctl(5, None) or 512  | 
 | 54 | +        self.scratch_buffer = bytearray(self.block_size)  | 
 | 55 | +        # for ESSIV mode, preallocate the IV AES generation instance  | 
 | 56 | +        if mode == ENCRYPT_AES_ESSIV:  | 
 | 57 | +            self.aes1 = aes(sha256(self.key).digest(), MODE_ECB)  | 
 | 58 | +        print(f'blocksize = {self.block_size} bytes / key = {self.key}')  | 
 | 59 | + | 
 | 60 | +    def _get_aes_obj(self, block_num):  | 
 | 61 | +        iv = struct.pack("<QQ", block_num, 0)  | 
 | 62 | +        if self.mode == ENCRYPT_AES_ESSIV:  | 
 | 63 | +            iv = self.aes1.encrypt(iv)  | 
 | 64 | +        return aes(self.key, MODE_CBC, iv)  | 
 | 65 | + | 
 | 66 | +    def readblocks(self, block_num, buf, offset=0):  | 
 | 67 | +        # get encrypted block, decrypt it and return the required length/offset  | 
 | 68 | +        aes_obj = self._get_aes_obj(block_num)  | 
 | 69 | +        self.bdev.readblocks(block_num, self.scratch_buffer)  | 
 | 70 | +        buf[:] = aes_obj.decrypt(self.scratch_buffer)[offset: offset + len(buf)]  | 
 | 71 | + | 
 | 72 | +    def writeblocks(self, block_num, buf, offset=0):  | 
 | 73 | +        # decrypt block, copy over buf, then encrypt again  | 
 | 74 | +        aes_obj = self._get_aes_obj(block_num)  | 
 | 75 | +        self.bdev.readblocks(block_num, self.scratch_buffer)  | 
 | 76 | +        self.scratch_buffer[offset:offset+len(buf)] = buf  | 
 | 77 | +        self.bdev.writeblocks(block_num, aes_obj.encrypt(self.scratch_buffer))  | 
 | 78 | + | 
 | 79 | +    def ioctl(self, op, arg):  | 
 | 80 | +        if op == 6:   # block erase  | 
 | 81 | +            # handle page erase separately  | 
 | 82 | +            print(f"erasing block {arg}...")  | 
 | 83 | +            aes_obj = self._get_aes_obj(arg)  | 
 | 84 | +            self.bdev.writeblocks(arg, aes_obj.encrypt(bytearray(self.block_size)))  | 
 | 85 | +            return 0  | 
 | 86 | +        return self.bdev.ioctl(op, arg)  | 
0 commit comments