Skip to content

Commit 814cd73

Browse files
author
Martin Fischer
committed
crypto-bdev: Add initial version.
1 parent e4cf095 commit 814cd73

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

micropython/crypto-bdev/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# crypto-bdev
2+
3+
This library provides a crypto wrapper for any block device
4+
that fullfills the vfs.abstractBlockDev API, e.g. SPI Flash FS
5+
or internal FS.
6+
7+
The design is compatible with Linux' LUKE for the following ciphers
8+
- AES CTR with plain IV
9+
- AES ESSIV
10+
11+
## Background
12+
13+
External SPI flash are common and often required for many MCU types.
14+
15+
It's often desired that sensitive data is not available in clear text
16+
on those chips, since it's straightforward to read them out.
17+
18+
A typical example may be Wifi credentials and/or code fragments.
19+
20+
The presented `CryptoBdev` class can be used toghether with a block device
21+
to add encryption.
22+
23+
## Usage
24+
25+
To use this class, you have to wrap an existing blockdevice.
26+
27+
For a simple test you can use the `RAMBlockDevice` (see https://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices)
28+
29+
and wrap it like that
30+
31+
```py
32+
# see https://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices
33+
import vfs
34+
from cryptbdev import CryptBdev
35+
36+
bdev = CryptoBlockDev(RAMBlockDev(512, 50))
37+
vfs.VfsLfs2.mkfs(bdev)
38+
39+
vfs.mount(bdev, '/ramdisk')
40+
41+
# use it as usual, it'll end up encrypted in /ramdisk
42+
with open('/ramdisk/hello.txt', 'w') as f:
43+
f.write('Hello world')
44+
print(open('/ramdisk/hello.txt').read())
45+
46+
47+
```
48+
49+
## Limitations
50+
51+
The following features are unsupported:
52+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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)

micropython/crypto-bdev/manifest.py

Whitespace-only changes.

0 commit comments

Comments
 (0)