Reads, blog drafts

This commit is contained in:
Pascal Engélibert 2025-12-21 18:54:05 +01:00
commit e92401bb76
12 changed files with 932 additions and 13 deletions

View file

@ -0,0 +1,95 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="372" height="172" viewBox="0 0 372 172">
<!-- Generated by https://txmn.tk/blog/flash-filesystem-encryption/graph.py -->
<!-- Image released under license CC0 (public domain) -->
<title>CBC</title>
<style>
text.t {
color: #000;
fill: #000;
text-anchor: middle;
dominant-baseline: central;
font-size: 16px;
font-family: "Libertinus Sans";
}
circle, line, rect, path.s {
stroke: #000;
stroke-width: 2px;
fill: none;
}
path.f {
stroke: none;
fill: #000;
}
@media (prefers-color-scheme: dark) {
text.t {
color: #ddd;
fill: #ddd;
}
circle, line, rect, path.s {
stroke: #ddd;
}
path.f {
fill: #ddd;
}
}
</style>
<text class="t" x="64" y="16">P</text>
<circle r="8" cx="64" cy="56"/>
<line x1="56" y1="56" x2="72" y2="56"/>
<line x1="64" y1="48" x2="64" y2="64"/>
<text class="t" x="16" y="96">K</text>
<rect x="48" y="80" width="32" height="32"/>
<text class="t" x="64" y="96">E</text>
<text class="t" x="64" y="144">C</text>
<line x1="64" y1="28" x2="64.0" y2="40.0"/>
<path class="f" transform="translate(64,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="64" y1="64" x2="64.0" y2="72.0"/>
<path class="f" transform="translate(64,80) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="28" y1="96" x2="40.0" y2="96.0"/>
<path class="f" transform="translate(48,96) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="64" y1="112" x2="64.0" y2="124.0"/>
<path class="f" transform="translate(64,132) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="16" y="56">IV</text>
<line x1="28" y1="56" x2="48.0" y2="56.0"/>
<path class="f" transform="translate(56,56) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<path class="s" d="M72 144L96 144L96 56L144.0 56.0"/>
<path class="f" transform="translate(152,56) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="160" y="16">P</text>
<circle r="8" cx="160" cy="56"/>
<line x1="152" y1="56" x2="168" y2="56"/>
<line x1="160" y1="48" x2="160" y2="64"/>
<text class="t" x="112" y="96">K</text>
<rect x="144" y="80" width="32" height="32"/>
<text class="t" x="160" y="96">E</text>
<text class="t" x="160" y="144">C</text>
<line x1="160" y1="28" x2="160.0" y2="40.0"/>
<path class="f" transform="translate(160,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="160" y1="64" x2="160.0" y2="72.0"/>
<path class="f" transform="translate(160,80) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="124" y1="96" x2="136.0" y2="96.0"/>
<path class="f" transform="translate(144,96) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="160" y1="112" x2="160.0" y2="124.0"/>
<path class="f" transform="translate(160,132) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<path class="s" d="M168 144L192 144L192 56L240.0 56.0"/>
<path class="f" transform="translate(248,56) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="256" y="16">P</text>
<circle r="8" cx="256" cy="56"/>
<line x1="248" y1="56" x2="264" y2="56"/>
<line x1="256" y1="48" x2="256" y2="64"/>
<text class="t" x="208" y="96">K</text>
<rect x="240" y="80" width="32" height="32"/>
<text class="t" x="256" y="96">E</text>
<text class="t" x="256" y="144">C</text>
<line x1="256" y1="28" x2="256.0" y2="40.0"/>
<path class="f" transform="translate(256,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="256" y1="64" x2="256.0" y2="72.0"/>
<path class="f" transform="translate(256,80) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="220" y1="96" x2="232.0" y2="96.0"/>
<path class="f" transform="translate(240,96) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="256" y1="112" x2="256.0" y2="124.0"/>
<path class="f" transform="translate(256,132) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<path class="s" d="M264 144L288 144L288 56L336.0 56.0"/>
<path class="f" transform="translate(344,56) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="356" y="56">···</text>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,94 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="288" height="172" viewBox="0 0 288 172">
<!-- Generated by https://txmn.tk/blog/flash-filesystem-encryption/graph.py -->
<!-- Image released under license CC0 (public domain) -->
<title>CTR</title>
<style>
text.t {
color: #000;
fill: #000;
text-anchor: middle;
dominant-baseline: central;
font-size: 16px;
font-family: "Libertinus Sans";
}
circle, line, rect, path.s {
stroke: #000;
stroke-width: 2px;
fill: none;
}
path.f {
stroke: none;
fill: #000;
}
@media (prefers-color-scheme: dark) {
text.t {
color: #ddd;
fill: #ddd;
}
circle, line, rect, path.s {
stroke: #ddd;
}
path.f {
fill: #ddd;
}
}
</style>
<text class="t" x="64" y="16">N||0</text>
<text class="t" x="16" y="64">K</text>
<rect x="48" y="48" width="32" height="32"/>
<text class="t" x="64" y="64">E</text>
<text class="t" x="16" y="112">P</text>
<circle r="8" cx="64" cy="112"/>
<line x1="56" y1="112" x2="72" y2="112"/>
<line x1="64" y1="104" x2="64" y2="120"/>
<text class="t" x="64" y="152">C</text>
<line x1="64" y1="28" x2="64.0" y2="40.0"/>
<path class="f" transform="translate(64,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="28" y1="64" x2="40.0" y2="64.0"/>
<path class="f" transform="translate(48,64) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="64" y1="80" x2="64.0" y2="96.0"/>
<path class="f" transform="translate(64,104) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="28" y1="112" x2="48.0" y2="112.0"/>
<path class="f" transform="translate(56,112) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="64" y1="120" x2="64.0" y2="132.0"/>
<path class="f" transform="translate(64,140) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="160" y="16">N||1</text>
<text class="t" x="112" y="64">K</text>
<rect x="144" y="48" width="32" height="32"/>
<text class="t" x="160" y="64">E</text>
<text class="t" x="112" y="112">P</text>
<circle r="8" cx="160" cy="112"/>
<line x1="152" y1="112" x2="168" y2="112"/>
<line x1="160" y1="104" x2="160" y2="120"/>
<text class="t" x="160" y="152">C</text>
<line x1="160" y1="28" x2="160.0" y2="40.0"/>
<path class="f" transform="translate(160,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="124" y1="64" x2="136.0" y2="64.0"/>
<path class="f" transform="translate(144,64) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="160" y1="80" x2="160.0" y2="96.0"/>
<path class="f" transform="translate(160,104) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="124" y1="112" x2="144.0" y2="112.0"/>
<path class="f" transform="translate(152,112) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="160" y1="120" x2="160.0" y2="132.0"/>
<path class="f" transform="translate(160,140) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="256" y="16">N||2</text>
<text class="t" x="208" y="64">K</text>
<rect x="240" y="48" width="32" height="32"/>
<text class="t" x="256" y="64">E</text>
<text class="t" x="208" y="112">P</text>
<circle r="8" cx="256" cy="112"/>
<line x1="248" y1="112" x2="264" y2="112"/>
<line x1="256" y1="104" x2="256" y2="120"/>
<text class="t" x="256" y="152">C</text>
<line x1="256" y1="28" x2="256.0" y2="40.0"/>
<path class="f" transform="translate(256,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="220" y1="64" x2="232.0" y2="64.0"/>
<path class="f" transform="translate(240,64) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="256" y1="80" x2="256.0" y2="96.0"/>
<path class="f" transform="translate(256,104) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="220" y1="112" x2="240.0" y2="112.0"/>
<path class="f" transform="translate(248,112) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="256" y1="120" x2="256.0" y2="132.0"/>
<path class="f" transform="translate(256,140) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -0,0 +1,70 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="288" height="128" viewBox="0 0 288 128">
<!-- Generated by https://txmn.tk/blog/flash-filesystem-encryption/graph.py -->
<!-- Image released under license CC0 (public domain) -->
<title>ECB</title>
<style>
text.t {
color: #000;
fill: #000;
text-anchor: middle;
dominant-baseline: central;
font-size: 16px;
font-family: "Libertinus Sans";
}
circle, line, rect, path.s {
stroke: #000;
stroke-width: 2px;
fill: none;
}
path.f {
stroke: none;
fill: #000;
}
@media (prefers-color-scheme: dark) {
text.t {
color: #ddd;
fill: #ddd;
}
circle, line, rect, path.s {
stroke: #ddd;
}
path.f {
fill: #ddd;
}
}
</style>
<text class="t" x="64" y="16">P</text>
<text class="t" x="16" y="64">K</text>
<rect x="48" y="48" width="32" height="32"/>
<text class="t" x="64" y="64">E</text>
<text class="t" x="64" y="112">C</text>
<line x1="64" y1="28" x2="64.0" y2="40.0"/>
<path class="f" transform="translate(64,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="28" y1="64" x2="40.0" y2="64.0"/>
<path class="f" transform="translate(48,64) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="64" y1="80" x2="64.0" y2="92.0"/>
<path class="f" transform="translate(64,100) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="160" y="16">P</text>
<text class="t" x="112" y="64">K</text>
<rect x="144" y="48" width="32" height="32"/>
<text class="t" x="160" y="64">E</text>
<text class="t" x="160" y="112">C</text>
<line x1="160" y1="28" x2="160.0" y2="40.0"/>
<path class="f" transform="translate(160,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="124" y1="64" x2="136.0" y2="64.0"/>
<path class="f" transform="translate(144,64) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="160" y1="80" x2="160.0" y2="92.0"/>
<path class="f" transform="translate(160,100) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<text class="t" x="256" y="16">P</text>
<text class="t" x="208" y="64">K</text>
<rect x="240" y="48" width="32" height="32"/>
<text class="t" x="256" y="64">E</text>
<text class="t" x="256" y="112">C</text>
<line x1="256" y1="28" x2="256.0" y2="40.0"/>
<path class="f" transform="translate(256,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="220" y1="64" x2="232.0" y2="64.0"/>
<path class="f" transform="translate(240,64) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="256" y1="80" x2="256.0" y2="92.0"/>
<path class="f" transform="translate(256,100) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,256 @@
#!/usr/bin/python3
"""
GNU AGPL v3, CopyLeft 2025 Pascal Engélibert [(why copyleft?)](https://txmn.tk/blog/why-copyleft/)
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.
"""
import math
ARGS = {
"BLACK": "#000",
"WHITE": "#ddd",
}
SVG = """\
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}">
<!-- Generated by https://txmn.tk/blog/flash-filesystem-encryption/graph.py -->
<!-- Image released under license CC0 (public domain) -->
<title>{title}</title>
<style>
text.t {{
color: {BLACK};
fill: {BLACK};
text-anchor: middle;
dominant-baseline: central;
font-size: 16px;
font-family: "Libertinus Sans";
}}
circle, line, rect, path.s {{
stroke: {BLACK};
stroke-width: 2px;
fill: none;
}}
path.f {{
stroke: none;
fill: {BLACK};
}}
@media (prefers-color-scheme: dark) {{
text.t {{
color: {WHITE};
fill: {WHITE};
}}
circle, line, rect, path.s {{
stroke: {WHITE};
}}
path.f {{
fill: {WHITE};
}}
}}
</style>
{body}
</svg>
"""
class Block:
def __init__(self, svg, x, y, r):
self.svg = svg
self.x = x
self.y = y
self.r = r
def to(self, block):
if self.x == block.x:
if self.y > block.y:
self.svg.t += arrow(self.x, self.y-self.r, block.x, block.y+block.r)
else:
self.svg.t += arrow(self.x, self.y+self.r, block.x, block.y-block.r)
elif self.y == block.y:
if self.x > block.x:
self.svg.t += arrow(self.x-self.r, self.y, block.x+block.r, block.y)
else:
self.svg.t += arrow(self.x+self.r, self.y, block.x-block.r, block.y)
XOR_R = 8
ENCRYPT_SIZE = 32
class Svg:
def __init__(self):
self.t = ""
def xor(self, x, y):
xl = x-XOR_R
xr = x+XOR_R
yt = y-XOR_R
yb = y+XOR_R
self.t += f"""\
<circle r="{XOR_R}" cx="{x}" cy="{y}"/>
<line x1="{xl}" y1="{y}" x2="{xr}" y2="{y}"/>
<line x1="{x}" y1="{yt}" x2="{x}" y2="{yb}"/>
"""
return Block(self, x, y, XOR_R)
def encrypt(self, x, y):
xl = x-ENCRYPT_SIZE//2
yt = y-ENCRYPT_SIZE//2
self.t += f"""\
<rect x="{xl}" y="{yt}" width="{ENCRYPT_SIZE}" height="{ENCRYPT_SIZE}"/>
<text class="t" x="{x}" y="{y}">E</text>
"""
return Block(self, x, y, ENCRYPT_SIZE//2)
def text(self, x, y, t):
self.t += f"""\
<text class="t" x="{x}" y="{y}">{t}</text>
"""
return Block(self, x, y, 12)
def square(self, x, y, t):
xl = x-ENCRYPT_SIZE//2
yt = y-ENCRYPT_SIZE//2
self.t += f"""\
<rect x="{xl}" y="{yt}" width="{ENCRYPT_SIZE}" height="{ENCRYPT_SIZE}"/>
<text class="t" x="{x}" y="{y}">{t}</text>
"""
return Block(self, x, y, ENCRYPT_SIZE//2)
ARROW_TIP_START = 8
ARROW_TIP_BACK = 12
ARROW_TIP_R = 6
ARROW_TIP = [
(-ARROW_TIP_START, 0),
(-ARROW_TIP_BACK, ARROW_TIP_R),
(0, 0),
(-ARROW_TIP_BACK, -ARROW_TIP_R),
(-ARROW_TIP_START, 0),
]
def arrow(x1, y1, x2, y2):
angle = math.degrees(math.atan2(y2-y1, x2-x1))
# Stop the line before the end to prevent it from passing through the tip
length = math.sqrt((x2-x1)**2+(y2-y1)**2)
xend = x2 - (x2-x1)/length*ARROW_TIP_START
yend = y2 - (y2-y1)/length*ARROW_TIP_START
path = ""
px = 0
py = 0
for p in ARROW_TIP:
path += "l{} {}".format(p[0]-px, p[1]-py)
px = p[0]
py = p[1]
return f"""\
<line x1="{x1}" y1="{y1}" x2="{xend}" y2="{yend}"/>
<path class="f" transform="translate({x2},{y2}) rotate({angle})" d="M0 0{path}z"/>
"""
def arrow_path(points):
x1 = points[-2][0]
y1 = points[-2][1]
x2 = points[-1][0]
y2 = points[-1][1]
angle = math.degrees(math.atan2(y2-y1, x2-x1))
# Stop the line before the end to prevent it from passing through the tip
length = math.sqrt((x2-x1)**2+(y2-y1)**2)
xend = x2 - (x2-x1)/length*ARROW_TIP_START
yend = y2 - (y2-y1)/length*ARROW_TIP_START
points[-1] = (xend, yend)
path_arrow = ""
px = 0
py = 0
for p in ARROW_TIP:
path_arrow += "l{} {}".format(p[0]-px, p[1]-py)
px = p[0]
py = p[1]
path_line = "M" + "L".join(["{} {}".format(p[0], p[1]) for p in points])
return f"""\
<path class="s" d="{path_line}"/>
<path class="f" transform="translate({x2},{y2}) rotate({angle})" d="M0 0{path_arrow}z"/>
"""
def ecb():
s = Svg()
for i in range(3):
P = s.text(96*i+64, 16, "P")
K = s.text(96*i+16, 64, "K")
E = s.encrypt(96*i+64, 64)
C = s.text(96*i+64, 112, "C")
P.to(E)
K.to(E)
E.to(C)
return SVG.format(body=s.t, title="ECB", w=288, h=128, **ARGS)
def ctr():
s = Svg()
for i in range(3):
S = s.text(96*i+64, 16, f"N||{i}")
K = s.text(96*i+16, 64, "K")
E = s.encrypt(96*i+64, 64)
P = s.text(96*i+16, 112, "P")
X = s.xor(96*i+64, 112)
C = s.text(96*i+64, 152, "C")
S.to(E)
K.to(E)
E.to(X)
P.to(X)
X.to(C)
return SVG.format(body=s.t, title="CTR", w=288, h=172, **ARGS)
def cbc():
s = Svg()
for i in range(3):
P = s.text(96*i+64, 16, "P")
X = s.xor(96*i+64, 56)
K = s.text(96*i+16, 96, "K")
E = s.encrypt(96*i+64, 96)
C = s.text(96*i+64, 144, "C")
P.to(X)
X.to(E)
K.to(E)
E.to(C)
if i == 0:
IV = s.text(96*i+16, 56, "IV")
IV.to(X)
s.t += arrow_path([(96*i+72,144), (96*i+96,144), (96*i+96,56), (96*(1+i)+56,56)])
s.text(356, 56, "···")
return SVG.format(body=s.t, title="CBC", w=372, h=172, **ARGS)
def xts():
s = Svg()
P = s.text(224, 16, "P")
X1 = s.xor(224, 56)
E1 = s.encrypt(224, 96)
X2 = s.xor(224, 144)
C = s.text(224, 180, "C")
K1 = s.text(176, 96, 'K<tspan dy="6">1</tspan>')
I = s.text(64, 16, "i")
E2 = s.encrypt(64, 96)
K2 = s.text(16, 96, 'K<tspan dy="6">2</tspan>')
J = s.text(128, 16, "j")
A = s.square(128, 96, '×α <tspan dy="-8">j</tspan>')
P.to(X1)
X1.to(E1)
K1.to(E1)
I.to(E2)
K2.to(E2)
E1.to(X2)
X2.to(C)
E2.to(A)
J.to(A)
s.t += arrow_path([(144,96), (160,96), (160,56), (216,56)])
s.t += arrow_path([(144,96), (160,96), (160,144), (216,144)])
return SVG.format(body=s.t, title="XTS", w=372, h=192, **ARGS)
def save(name, data):
f = open(f"{name}.svg", "w")
f.write(data)
f.close()
if __name__ == "__main__":
save("ecb", ecb())
save("ctr", ctr())
save("cbc", cbc())
save("xts", xts())

View file

@ -0,0 +1,192 @@
+++
title = "Embedded filesystem encryption on flash memory"
date = 2025-12-13
description = "My journey in the world of filesystem encryption and flash memories."
insert_anchor_links = "left"
draft = true
[taxonomies]
tags = ["cryptography", "embedded"]
[extra]
katex = true
+++
One of my long-term projects is an ESP32-based phone, using an SD card for storage. Then, why not encrypting the SD card?
_In this post, we first explain the basics of filesystem encryption, then explore ways to apply it to the case of an embedded device and flash memory. This last part is quite rarely analyzed in the litterature._
## Choosing a cipher
Cryptography is relatively expensive in computation and memory, this is why some chips include hardware implementations that are way more efficient than classical ones made of basic instructions. Fortunately, ESP32 has AES, SHA1 and SHA2 instructions, and [they are faster than software implementations](https://www.oryx-embedded.com/benchmark/espressif/crypto-esp32-s3.html). AES128 will be a good choice then.
If we didn't have a hardware AES, it would be interesting to look at lightweight ciphers like [ASCON](https://csrc.nist.gov/news/2023/lightweight-cryptography-nist-selects-ascon) that may be slower than AES but more memory-efficient, or more common ones such as CHACHA20 that can be faster on software.
## How filesystem encryption works in general
### Can't we just encrypt files?
The simpler solution would be to use a FAT filesystem encrypt files directly. But how are files encrypted, again?
#### ECB
Block ciphers like AES process data by blocks of fixed size, for instance 16 bytes. (note: AES128 has a 128-bit key and AES256 has a 256-bit key, but both process 128-bit blocks) The simplest way to encrypt data would be to divide it into blocks and encrypt each block separately.
<div style="text-align:center"><img alt="ECB" src="ecb.svg"/></div>
$$C = E(K, P)$$
(I love those diagrams so I made [a simple Python script](graph.py) to generate them in SVG.)
This mode of operation is called ECB for Electronic Code Book. It has, however, fatal flaws:
* If two blocks are identical, an adversary can spot them and learn information.
* A adversary who has access to an encryption oracle (i.e. they can obtain the ciphertext from a plaintext, that could happen if you store incoming messages in the encrypted files) can try different values and decrypt other blocks by bruteforcing well-known formats.
ECB alone is almost never a good idea.
#### CTR
From ECB we learn that each block must be encrypted differently. Another mode that solves this problem is CTR, that uses a counter to generate a secret pseudorandom for each block. It is like generating a long one-time pad from a short key.
<div style="text-align:center"><img alt="CTR" src="ctr.svg"/></div>
$$C = P \oplus E(K, N||i)$$
Here, $\oplus$ is bitwise XOR and $||$ is concatenation. N is a nonce.
> **Could we simply vary the key using the counter, and avoid xoring?** I tried that. Every time we change AES's key, we must recompute the key expansion, which can be roughly as long as encrypting a block. Hence we save a lot when reusing the same key for many blocks.
CTR provides privacy, as long as the same $(K, N, i)$ is used at most once. Else, the attacker can compute $C \oplus C' = (P \oplus E(K, N||i)) \oplus (P' \oplus E(K, N||i)) = P \oplus P'$. If $P$ is only zeros (which is common in binary files), then $P'$ is fully known. Thus we have to update the nonce whenever we update the file. Storing one nonce per block would almost double the storage size! The nonce could be stored per file, but then we have to reencrypt the whole file. And what if the attacker reverted the storage into an older state, so we reuse an old nonce?
This mode also has a 1-bit malleability: if the attacker flips a bit, this exact bit will be flipped at decryption.
These two properties are fatal in our context: let's look elsewhere.
### Flash memories are delicate
#### CBC
CBC (Cipher Block Chaining) introduces chaining.
<div style="text-align:center"><img alt="CBC" src="cbc.svg"/></div>
$$C_0 = E(K, P_0 \oplus IV)$$
$$C_i = E(K, P_i \oplus C_{i-1})$$
CBC is still somewhat malleable: if the adversary can afford scrambling one block, they can bit-flip the next one trivially.
CBC has another fatal property for us: modifying one block implies to re-encrypt all the following blocks.
The problem is that flash memories are slow and become damaged after a limited number of writes (like 10k or 100k). Rewriting entire files would take too much time and wear the device quickly.
#### A dedicated filesystem
Now that we've highlighted an important property of flash memories, it appears FAT32 may not be the best choice as a filesystem. Indeed, a modified block would stay at the same physical address, causing different regions of the storage to wear more rapidly than others. It would be better to spread the write operations across the entire space, in order to maximize the time before a failure happens.
[LittleFS](https://github.com/littlefs-project/littlefs) is made exactly for this purpose. Moreover, it provides atomic operations, meaning it never leaves the filesystem in an incoherent state if there is a power loss or a storage failure during a write operation.
If we're going down at the filesystem lever, why not going further? Instead of encrypting files, we can directly encrypt the filesystem's blocks, by placing the cryptographic module between LittleFS and the IO. LittleFS's write length can be customized so we can set it to our block length and avoid dealing with partial blocks, as we would have to do when encrypting files. Another benefit is that we're hiding the file tree as well: directories, names and metadata are encrypted as well, with no additional complexity.
#### XTS
We need something looking more like ECB or CTR in that it allows small random writes. XTS is a popular for filesystem encryption and satisfies this criterion.
<div style="text-align:center"><img alt="XTS" src="xts.svg"/></div>
$$C = E(K_1, P \oplus \Delta) \oplus \Delta$$
$$\Delta = E(K_2, i) \times \alpha^j$$
Here, the storage is divided into sectors and sectors into blocks. In the diagram, i is the sector number and j is the block number.
Why so complicated? First, $E(K_2, i)$ looks like CTR. To make it faster, it remains constant through the entire sector (which is useful because LittleFS prefers to read or write contiguous blocks when possible). Multiplication by $\alpha$ (as defined later) is faster than a block encryption and can be computed incrementally with $x \times \alpha^j = (x \times \alpha^{j-1}) \times \alpha$. The double XOR prevents attacks on chosen ciphertext or known plaintext as described before.
XTS has a way to deal with final partial blocks (when data length is not a multiple of block size), but as we're encrypting full blocks of 16 bytes only, we don't need that mechanism.
[Rogaway 2011](https://www.cs.ucdavis.edu/~rogaway/papers/modes.pdf) criticized XTS on multiple points.
* XTS is based on a modified version of Rogaway's XEX mode (XOR-Encrypt-Xor) which has well understood security properties.
* Ciphertext stealing, the way to deal with final partial blocks, is poorly designed or at least not proven secure under well-defined security goals. Again, we are not concerned.
* The use of two different keys is unjustified, except it makes proofs easier. If the sector number i is xored with a secret random salt, there is no risk of collision between the inputs of the two cipher blocks, as long as we do not store ciphertexts of the secret key or the salt (they should be user inputs stored in volatile memory only).
* It is a FIPS (NIST standard) but only specified in an IEEE spec that is seemingly not available publicly (unless using Sci-Hub of course).
* $\Delta$ is byte-swapped to make implementation easier on little-endian machines, but this has no security implications.
## Benchmarking ciphers
I implemented the simplified XEX in Rust and ran a benchmark on the ESP32. As the multiplication by powers of alpha can be implemented in many ways, I also tried different versions.
First version, delta is an unaligned array of bytes, cast to u128 to do the maths:
```rust
fn mul_delta_u128(delta: &mut [u8; 16]) {
let mut delta1 = u128::from_be_bytes(*delta);
delta1 = (delta1 << 1) ^ (135 * (delta1 >> 127));
*delta = delta1.to_be_bytes();
}
```
However ESP32's registers are only 32 bits so we have to trust the compiler to implement u128 efficiently. Using [cargo-show-asm](https://crates.io/crates/cargo-show-asm), I see the above function's assembly is 151 lines long and only operates bytewise. We can do better.
Switching to little endian (`from_le_bytes`, `to_le_bytes`) improves quite a bit to 82 lines, but still produces byte operations only. We can still do better!
To ensure the compiler can work efficiently with u128, we can use u128 from the start and avoid casting, so it should be aligned:
```rust
fn mul_delta_u128_aligned(delta: &mut u128) {
*delta = (*delta << 1) ^ (135 * (*delta >> 127));
}
```
Finally we can try implementing the details ourselves:
```rust
fn mul_delta_u32(delta: &mut [u32; 4]) {
let term = (delta[3] >> 31) * 135;
delta[3] <<= 1;
delta[3] ^= delta[2] >> 31;
delta[2] <<= 1;
delta[2] ^= delta[1] >> 31;
delta[1] <<= 1;
delta[1] ^= delta[0] >> 31;
delta[0] <<= 1;
delta[0] ^= term;
}
```
The two last implementations produce 22 assembly lines, using 32 bits operations.
Here are the benchmark results (encrypting 100 times 128kB):
| Mode | Implementation | Sector size (blocks) | Time (ms) (1 key) | Time (ms) (2 keys) |
| ---- | ----------------- | -------------------- | ----------------- | ------------------ |
| ECB | - | - | 744 | - |
| XTS | unaligned u128 BE | 8 | 2770 | 2774 |
| XTS | unaligned u128 BE | 16 | 2664 | 2775 |
| XTS | unaligned u128 BE | 32 | 2637 | 2749 |
| XTS | unaligned u128 BE | 64 | 2624 | 2736 |
| XTS | unaligned u128 LE | 8 | 2499 | 2448 |
| XTS | unaligned u128 LE | 16 | 2399 | 2445 |
| XTS | unaligned u128 LE | 32 | 2373 | 2420 |
| XTS | unaligned u128 LE | 64 | 2361 | 2408 |
| XTS | aligned u128 | 8 | 2549 | 2447 |
| XTS | aligned u128 | 16 | 2449 | 2445 |
| XTS | aligned u128 | 32 | 2424 | 2420 |
| XTS | aligned u128 | 64 | 2412 | 2408 |
| XTS | [u32; 4] | 8 | 2495 | 2493 |
| XTS | [u32; 4] | 16 | 2395 | 2490 |
| XTS | [u32; 4] | 32 | 2370 | 2465 |
| XTS | [u32; 4] | 64 | 2357 | 2453 |
The fastest is XTS with one key (and salted sector number) and long sectors.
Sectors must not be too long, however, as random access needs computing all
## Storing the key
AES128 needs 128 bits of key, however the user will only remember ASCII words, not fully random bytes. We need something to derive a key from a variable-length password. We could just compute a hash of the password, as the ESP32 provides a hardware implementation of SHA2, but for storing passwords it is better to use a dedicated function that is fast enough to run once but hard to bruteforce efficiently on optimized systems.
[PBKDF2](https://fr.wikipedia.org/wiki/PBKDF2) chains thousands of calls to a hash function, each one depending on the previous one, so it is impossible to parallelize. However an attacker can run thousands of instances in parallel on a GPU or cryptocurrency-mining chip.
A popular choice as of today is [Argon2](https://en.wikipedia.org/wiki/Argon2), which is memory-hard: one instance requires efficient access to a big amoung of memory, potentially megabytes or even gigabytes, so it is difficult to optimize even on dedicated hardware. Problems are that its implementation is quite complicated (it will take too much ROM) and its specs are not even complete.
[Catena](https://www.researchgate.net/publication/261548591_The_Catena_Password_Scrambler) is a scheme with similar properties but with a very simple description. It takes less than 50 lines of Rust. To run on the ESP32, I used SHA256 and set its memory usage to 128kB and 1024 iterations. In comparison, recommended parameters are between 67MB and 1GB with 3 or 4 iterations. It runs in 911ms. We can expect a speedup of more than 10 on a good CPU, and it still can be parallelized easily on an old GPU: if your GPU has 1GB of RAM, it can hold at most 8192 parallel instances.
The benefit of password hashing functions on the ESP32 is a bit disappointing, we only slow down attacks by a small factor. It seems easier to enforce strong passwords. Picking 10 random words from a [BIP39](https://github.com/bitcoin/bips/blob/04b448b599cb16beae40ba9a98df9f262da522f7/bip-0039/english.txt) wordlist gives $\log_2(2048^{10})=110$ bits of entropy. To make it faster to type, each word can be shortened to its 4 first letters without loosing entropy.

View file

@ -0,0 +1,77 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="372" height="192" viewBox="0 0 372 192">
<!-- Generated by https://txmn.tk/blog/flash-filesystem-encryption/graph.py -->
<!-- Image released under license CC0 (public domain) -->
<title>XTS</title>
<style>
text.t {
color: #000;
fill: #000;
text-anchor: middle;
dominant-baseline: central;
font-size: 16px;
font-family: "Libertinus Sans";
}
circle, line, rect, path.s {
stroke: #000;
stroke-width: 2px;
fill: none;
}
path.f {
stroke: none;
fill: #000;
}
@media (prefers-color-scheme: dark) {
text.t {
color: #ddd;
fill: #ddd;
}
circle, line, rect, path.s {
stroke: #ddd;
}
path.f {
fill: #ddd;
}
}
</style>
<text class="t" x="224" y="16">P</text>
<circle r="8" cx="224" cy="56"/>
<line x1="216" y1="56" x2="232" y2="56"/>
<line x1="224" y1="48" x2="224" y2="64"/>
<rect x="208" y="80" width="32" height="32"/>
<text class="t" x="224" y="96">E</text>
<circle r="8" cx="224" cy="144"/>
<line x1="216" y1="144" x2="232" y2="144"/>
<line x1="224" y1="136" x2="224" y2="152"/>
<text class="t" x="224" y="180">C</text>
<text class="t" x="176" y="96">K<tspan dy="6">1</tspan></text>
<text class="t" x="64" y="16">i</text>
<rect x="48" y="80" width="32" height="32"/>
<text class="t" x="64" y="96">E</text>
<text class="t" x="16" y="96">K<tspan dy="6">2</tspan></text>
<text class="t" x="128" y="16">j</text>
<rect x="112" y="80" width="32" height="32"/>
<text class="t" x="128" y="96">×α <tspan dy="-8">j</tspan></text>
<line x1="224" y1="28" x2="224.0" y2="40.0"/>
<path class="f" transform="translate(224,48) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="224" y1="64" x2="224.0" y2="72.0"/>
<path class="f" transform="translate(224,80) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="188" y1="96" x2="200.0" y2="96.0"/>
<path class="f" transform="translate(208,96) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="64" y1="28" x2="64.0" y2="72.0"/>
<path class="f" transform="translate(64,80) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="28" y1="96" x2="40.0" y2="96.0"/>
<path class="f" transform="translate(48,96) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="224" y1="112" x2="224.0" y2="128.0"/>
<path class="f" transform="translate(224,136) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="224" y1="152" x2="224.0" y2="160.0"/>
<path class="f" transform="translate(224,168) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="80" y1="96" x2="104.0" y2="96.0"/>
<path class="f" transform="translate(112,96) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<line x1="128" y1="28" x2="128.0" y2="72.0"/>
<path class="f" transform="translate(128,80) rotate(90.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<path class="s" d="M144 96L160 96L160 56L208.0 56.0"/>
<path class="f" transform="translate(216,56) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
<path class="s" d="M144 96L160 96L160 144L208.0 144.0"/>
<path class="f" transform="translate(216,144) rotate(0.0)" d="M0 0l-8 0l-4 6l12 -6l-12 -6l4 6z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB