Jekyll2023-12-23T09:32:46-08:00/feed.xmlZachary RatliffPhD Student at Harvard | Staff Scientist at Raytheon BBN TechnologiesZachary RatliffSecurely Deleting Data2023-12-21T00:00:00-08:002023-12-21T00:00:00-08:00/Secure-File-Deletion<p>Imagine the following scenario:
You’re editing a private document on your computer.
When you’re done, you delete the file for security reasons.
However, in an unforeseen turn of events, your computer gets stolen.
In an ideal situation, the thief should be completely blocked from accessing any of your files.
With full disk encryption and robust authentication
measures (like a high-entropy user password),
your data would generally be considered secure.
But, what if the thief manages to crack your
authentication system
(perhaps your password wasn’t as robust as you believed)?
In such a case, you would still hope that the <em>deleted</em>
document remains unrecoverable.
<strong>This is the essence of secure deletion</strong>:
<em>ensuring that, even if someone gains unrestricted access to your computer,
your erased files remain confidential.</em></p>
<p>Secure deletion might seem like a basic aspect of data security,
yet it’s not provided by most commodity file systems.
For example, on popular Linux file systems like EXT4,
deleting a file marks the file’s on-device data blocks as unallocated. However,
the original data persists and is only replaced if the file system eventually chooses to allocate those
blocks to a new file.
Additionally, attempts to manually overwrite old file data,
like using the Linux <a href="https://linux.die.net/man/1/shred">shred</a> command for zero-filling,
are often ineffective. This is particularly true with
solid-state drives (SSDs) found in most modern laptops and desktops.
SSDs employ wear-leveling techniques that inadvertently
preserve file data by duplicating it across different parts of the drive,
even after a file is supposedly deleted. For this reason, the implementation
of software-based approaches for secure deletion can be tricky. One must either
(a) make assumptions about how the underlying storage controller manages and writes
data, or (b) treat the storage controller itself as adversarial.</p>
<p>Secure deletion has been extensively
explored in previous research.
While I won’t delve into a detailed analysis here,
I recommend the following surveys by <a href="https://arxiv.org/pdf/2109.11007.pdf">Zinkus, Jois, and Green</a>
and <a href="https://oaklandsok.github.io/papers/reardon2013.pdf">Reardon, Basin, and Capkun</a>.
The most common method for secure deletion is <em>cryptographic erasure</em>.
This technique involves encrypting all data written to the disk
and decrypting it upon reading. Securely deleting a file becomes a matter of
making its corresponding encryption key unrecoverable. This is usually accomplished by
only storing the encryption key in a secure, wipeable storage (often with limited capacity), and
then erasing/rotating the key during a secure deletion operation.</p>
<p>Take Apple devices as an example.
Apple devices encrypt the entire file system with a key,
which is itself encrypted by an “<em>effaceable key</em>” stored in a reserved area of flash memory.
When this effaceable key is wiped from memory,
it renders the file system, and therefore the data, inaccessible.</p>
<figure>
<img src="../images/effaceable-ios.jpg" width="300px" height="200px" align="center" />
<figcaption>Figure 1. Effaceable storage on Apple devices. The effaceable key encrypts the file system key which in turn encrypts the per-file keys. Note that this is a slightly simplified figure, and in reality, the file keys are additionally protected by one or more "class keys." The class keys are encrypted under a secure enclave UID key and the user's passcode.</figcaption>
</figure>
<p>However, Apple’s method offers a coarse-grained approach to secure deletion.
There’s often a need for more refined control, such as per-file secure deletion.
Consider a scenario where a user’s device is
compromised without their knowledge, so that the user never
issues a wipe command. Even if the user is aware that their device has been compromised, they
may be unable to securely wipe it without facing legal consequences, e.g., in the case of device seizure by warrant.
In such cases, the user would still
expect any previously deleted files (prior to the intruder gaining access)
to remain unrecoverable.</p>
<p>This is where our research enters the picture!
My co-authors (Wittmann Goh, Abe Wieland, James Mickens, and Ryan Williams) and I
wrote a <a href="https://eprint.iacr.org/2023/1927.pdf">paper</a>
that was recently accepted to Oakland’24! In this work, we designed Holepunch, a new block device driver
that provides full disk encryption and secure deletion of file system data.
Holepunch improves over the current state-of-the-art by:</p>
<ul>
<li>making no assumptions about how storage hardware internally manages and writes data</li>
<li>reducing both the memory space and storage IOs needed to manage file keys</li>
<li>providing crash consistency, ensuring that the file system is usable after a crash or power outage occurs</li>
</ul>
<p>Given these properties, we believe that Holepunch is the first practical system for securely deleting file system data.</p>
<h3 id="holepunch-overview">Holepunch Overview</h3>
<p>Holepunch leverages a cryptographic primitive known as a puncturable pseudorandom function (puncturable PRF).
A psuedorandom function $F$ accepts a key $k$ and an input $x$ and outputs a bit string $y$ that is
indisitnguishable from random.<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup> Given such an object, one can output many random strings that are suitable for
cryptographic use, e.g., we can quickly generate the $n$ keys:</p>
<p>$k_1 = F(k, 1), k_2 = F(k, 2), \dots, k_n = F(k, n)$</p>
<p>and then use these keys to encrypt data. Notably, a <em>puncturable</em> PRF includes the additional property that the PRF key
can be punctured at an evaluation point. For example, given a key $k$ and a point $x$, one can puncture $k$ to produce
a new key $k’$ such that $F(k’, x) = \bot$. We can use this puncturing feature to “forget” how to generate certain
cryptographic keys.</p>
<p>In Holepunch puncturable PRF generates the wrapping keys that encrypt the individual per-file keys,
which in turn are used for encrypting file data.
The puncturable PRF key is then encrypted by a master key that is only ever stored in tamper-resistant hardware
such as a Trusted Platform Module (TPM).</p>
<figure>
<img src="../images/holepunch-overview.jpg" width="300px" height="200px" align="center" />
<figcaption>Figure 3. A high-level overview of Holepunch.</figcaption>
</figure>
<p>To delete a file, the puncturable PRF key is <em>punctured</em> at a single point such
that the new key can no longer generate the necessary wrapping key.
Holepunch then rotates the master key stored within the TPM, and the newly
punctured PRF key is then re-encrypted under the updated master key.</p>
<p>Unsurprisingly, there are some technical hurdles with making our approach efficient and usable.
First, our implementation of the puncturable PRF is based on the <a href="https://dl.acm.org/doi/pdf/10.1145/6490.6503">Goldreich, Goldwasser, and Micali construction</a>.
As a result, the puncturable PRF key grows linearly with every puncture operation.
Re-encrypting this ever-growing punctured PRF key under a newly rotated master key
can become increasingly resource-intensive over time, potentially impacting system performance.
Second, we require precise management and synchronization between the file system’s on-disk state and the TPM’s state.
This coordination is essential to prevent data loss during unforeseen events like system crashes or power outages.
If you’re interested in the technical details of how we solved these problems,
check out our <a href="https://eprint.iacr.org/2023/1927.pdf">paper</a>!</p>
<p>I’m happy to say that after solving some of these problems, our Holepunch implementation was able to match the performance
of dm-crypt (standard full disk encryption without secure deletion) and <a href="https://onarlioglu.com/eraser/">Eraser</a>. Improving
upon the Eraser tool was one of the original motivations of this work and we were able to significantly reduce the memory
footprint of key management using our puncturable PRF based scheme.</p>
<figure>
<img src="../images/memory.jpg" width="300px" height="200px" align="center" />
<figcaption>Figure 4. Baseline memory consumption of Eraser (Onarlioglu et al.) compared to various Holepunch configurations.</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>Secure deletion frameworks are increasingly relevant for several reasons.
Governments worldwide have begun enforcing laws that regulate
the collection and retention of user data.
Notable examples include the European Union’s General Data
Protection Regulation (GDPR) and the California Consumer Privacy Act (CCPA).
Both of these grant users a “right to be forgotten,”
enabling them to request the complete removal of their
information from a business’s records.
While this right is a boon for privacy advocates,
its effective implementation heavily relies on robust technical solutions
that can enforce GDPR-style regulations in real-world scenarios.</p>
<p>Beyond legal requirements, secure deletion is also critical
for mitigating inherent security risks associated
with improper data sanitization practices.
There have been notable incidents, such as the
Pennsylvania Department of Labor and Industry
<a href="https://www.nytimes.com/2002/05/02/technology/basics-hard-drive-magic-making-data-disappear-forever.html?unlocked_article_code=1.H00.KkAX.6Gfm_Lg4wlYw&smid=nytcore-android-share">inadvertently reselling computers with
unsanitized drives containing state employee records</a>,
or the United States Veterans Administration <a href="https://www.nextgov.com/digital-government/2002/08/va-toughens-security-after-pc-disposal-blunders/201254/">distributing hard disks with sensitive patient data</a>
like credit card numbers. These examples highlight a prevalent issue:
if devices don’t facilitate secure deletion,
many individuals and organizations might neglect to
properly sanitize storage media, leading to significant security breaches.
Therefore, the implementation of secure deletion technology
is not just a regulatory compliance measure but
also a crucial step in safeguarding sensitive
information for both individuals and organizations.</p>
<h4 id="a-puncturable-prf-implementation-in-python">A Puncturable PRF Implementation in Python</h4>
<p>As a bonus, if you’re interested in playing around with puncturable PRFs,
here’s a reference implementation we programmed in Python that’s based on the GGM’86 construction.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import bisect, pyaes, secrets
KEY_PREFIX = 0
KEY_DEPTH = 1
KEY_VALUE = 2
class PPRF:
def __init__(self, key, domain_bits=128):
assert domain_bits <= 128, "PPRF only supports domain sizes up to 2^128"
assert len(key) == 16, "PPRF key must be 16 bytes"
self.key = [(0, 0, key)]
self.domain_bits = domain_bits
# Used for length-doubling PRG
self.inputs = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
self.inputs += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
# Length Doubling PRG
# Expands 128-bit seed to 256-bit pseudorandom output
# Uses AES-ECB as a PRF
def __prg(self, seed):
aes = pyaes.AESModeOfOperationECB(seed)
ciphertext = aes.encrypt(self.inputs[:16])
ciphertext += aes.encrypt(self.inputs[16:])
return ciphertext
# Punctures the PRF at a point x and returns a new punctured key
def puncture(self, x):
key, key_idx = self.__get_longest_matching_prefix(x)
seed = key[KEY_VALUE]
check_val = key[KEY_PREFIX]
prefix = key[KEY_PREFIX]
self.key.pop(key_idx)
new_keys = []
for i in range(key[KEY_DEPTH], self.domain_bits):
prg_output = self.__prg(seed)
bit = x >> (self.domain_bits - 1 - i) & 1
prefix_add = (1 - bit) * (2 ** (self.domain_bits - 1 - i))
if bit:
seed = prg_output[16:]
bisect.insort(new_keys, (prefix + prefix_add, i + 1, prg_output[:16]))
check_val += (2 ** (self.domain_bits - 1 - i))
else:
seed = prg_output[:16]
bisect.insort(new_keys, (prefix + prefix_add, i + 1, prg_output[16:]))
prefix += bit * (2 ** (self.domain_bits - 1 - i))
# Key was already punctured at this point.
if check_val != x:
self.key.insert(key_idx, key)
return self.key
self.key[key_idx:key_idx] = new_keys
return self.key
# Get key that can evaluate the point x
def __get_longest_matching_prefix(self, x):
i = bisect.bisect_left(self.key, (x, 2 ** self.domain_bits, 2 ** self.domain_bits))
if i == len(self.key):
return self.key[i - 1], i - 1
elif self.key[i] == x:
return self.key[i], i
return self.key[i - 1], i - 1
def get_key_idx(self, x):
_, idx = self.__get_longest_matching_prefix(x)
return idx
# Evaluate the PPRF at a point x
def eval(self, x):
key,_ = self.__get_longest_matching_prefix(x)
seed = key[KEY_VALUE]
check_val = key[KEY_PREFIX]
for i in range(key[KEY_DEPTH], self.domain_bits):
prg_output = self.__prg(seed)
if x >> (self.domain_bits - 1 - i) & 1:
seed = prg_output[16:]
check_val += (2 ** (self.domain_bits - 1 - i))
else:
seed = prg_output[:16]
# value already punctured
if check_val != x:
seed = None
return seed
</code></pre></div></div>
<p>uniformly random string.</p>
<script type="text/javascript" async="" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<div class="footnotes">
<ol>
<li id="fn:1">
<p>Technically speaking, the output of the pseudorandom function is <em>computationally</em> indistinguishable from random. This means that any computationally-bounded (polynomial-time) adversary cannot distinguish the output of the PRF from a <a href="#fnref:1" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Zachary RatliffImagine the following scenario: You’re editing a private document on your computer. When you’re done, you delete the file for security reasons. However, in an unforeseen turn of events, your computer gets stolen. In an ideal situation, the thief should be completely blocked from accessing any of your files. With full disk encryption and robust authentication measures (like a high-entropy user password), your data would generally be considered secure. But, what if the thief manages to crack your authentication system (perhaps your password wasn’t as robust as you believed)? In such a case, you would still hope that the deleted document remains unrecoverable. This is the essence of secure deletion: ensuring that, even if someone gains unrestricted access to your computer, your erased files remain confidential.Implementing the Lamport one-time signature scheme in Python2019-10-01T00:00:00-07:002019-10-01T00:00:00-07:00/Lamport-Signatures<p>Armed with a cryptographically secure one-way hash function and a secure
source of randomness, we can build a digital signature scheme that is believed to
be secure even with the advent of quantum computers. Created by Leslie Lamport at
SRI International, the Lamport signature scheme is useful for many things.</p>
<p>In this post, I use SHA-256 as my cryptographically
secure hash function. My implementation in Python uses the <em>secrets</em> library for generating random
numbers (which defaults to using the most secure source of randomness provided by your operating
system).</p>
<h2 id="digital-signatures">Digital signatures</h2>
<p>Suppose <strong>Alice</strong> wishes to broadcast messages to her many friends. She creates a message
$m_{A} = \text{“Lamport signatures are cool! (from Alice)”}$ and broadcasts it. Her friend <strong>Bob</strong>
receives this message and reads it. But how can Bob be sure that this message came
from Alice, and is un-altered? For example, what if an evil person <strong>Eve</strong> writes a message $m_{E} =
\text{“Lamport signatures are NOT cool! (from Alice)”}$ and broadcasts it? Bob will receive this message
and think that Alice does not like Lamport signatures! Luckily for Alice, this is one of the many
problems solved by digital signatures!</p>
<h2 id="lamport-signatures">Lamport Signatures</h2>
<p>Lamport signatures are a one-time signature scheme that allows Alice to sign
her messages. This signature allows Bob and others
to verify that messages which say “(from Alice)” were indeed constructed by Alice.</p>
<h3 id="key-generation">Key generation</h3>
<p>Suppose Alice wishes to broadcast messages to her many friends.
For every message, a new public key / secret key pair $(pk, sk)$ must be generated.
The secret key $sk$ takes the form of a $256 \times 2$ matrix of random numbers $r_x$.</p>
<p><img src="../images/lamport-sk.jpg" width="600px" height="400px" align="center" /></p>
<p>From the secret key, another $256 \times 2$ matrix is created by hashing
the secret key values: $y_{x}^{b} = SHA256(r_{x}^{b})$. This matrix is Alice’s
public key $pk$.</p>
<p><img src="../images/lamport-pk.jpg" width="600px" height="400px" align="center" /></p>
<p>This public key $pk$ must then be distributed to message recipients who wish to
verify the authenticity of Alice’s messages. For this post, we assume this is easy enough
(<em>spoiler alert</em>: this is not easy).</p>
<p>All of Alice’s friends (and also Eve) have access to this public key $pk$
made available by Alice. However, only Alice has access to her secret key $sk$.</p>
<h3 id="message-signing">Message signing</h3>
<p>To sign the message $m_A = \text{“Lamport signatures are cool! (from Alice)”}$,
Alice first hashes the message using a cryptographically secure hash function:
$h_{m_A} = SHA256(m_A)$ and observes
the binary representation of $h_{m_A}$. For each bit $b = \{0, 1\}$,
<strong>Alice</strong> appends $sk_i^b$
to the message signature. For example, if $h_{m_A} = 1011…01$:</p>
<p><img src="../images/lamport-sigp1.jpg" width="600px" height="400px" align="center" /></p>
<p>The resulting message signature $s_{m_A}$ is therefore:</p>
<p><img src="../images/lamport-sigp2.jpg" width="600px" height="400px" align="center" /></p>
<p>Alice can then broadcast the message/signature pair $(m_A, s_{m_A})$ to all of her friends.</p>
<h3 id="message-verification">Message verification</h3>
<p>When Bob receives $(m_A, s_{m_A})$ over the air, he wants first to verify that it did indeed
come from Alice. To do this, he uses his access to Alice’s public key $pk$.
First, the message $m_A$ is hashed using the same cryptographically secure hash
function that Alice used: $h_{m_A} = SHA256(m_A)$.
For each bit $b$ in the binary representation of $h_{m_A}$, Bob appends the corresponding
public key value $pk_i^b$ to a string of bytes.</p>
<p><img src="../images/lamport-checkp1.jpg" width="600px" height="400px" align="center" /></p>
<p>Finally, Bob can verify the signature $s_{m_A}$ by hashing each $256$-bit chunk of the signature,
and verifying that it equals the corresponding string of bytes constructed from Alice’s
public key:</p>
<p><img src="../images/lamport-checkp2.jpg" width="600px" height="400px" align="center" /></p>
<p>When Bob sees that these two values are equal, he can be sure that this message came from Alice.
Furthermore, it is <em>hard</em> for some adversary (Eve) to forge signatures that Bob accepts.</p>
<h2 id="the-hardness-of-forging-a-lamport-signature">The hardness of forging a Lamport signature</h2>
<p>Lamport signatures are secure due to the hardness of inverting a cryptographic hash function.
Imagine Eve has the message $m_E = \text{“Lamport signatures are NOT cool! (from Alice)”}$, which
she wishes to forge Alice’s signature for. For each bit $b$ in $h_{m_E} = SHA256(m_E)$,
Eve must find a value $x_i$ such that $y_i^b = SHA256(x_i)$. This is difficult to do for
many cryptographic hash functions (such as SHA-256), and is what is known as pre-image resistance.
Furthermore, to forge signatures for arbitrary messages, Eve must do this for every $y^b_i$ i.e., find
pre-images for $512$ different hashes provided by Alice’s public key. On the average case, a SHA-256
hash takes $2^{256}$ tries to find a pre-image. This is infeasbile and is what makes this
signature scheme secure (as long as you only use each key once).</p>
<p>What happens if Alice uses a key to sign two different messages $m_1$ and $m_2$?
Observe that $h_{m_1} = SHA256(m_1)$ and $h_{m_2} = SHA256(m_2)$,
will differ in approximately half of their bits on average. For each bit $b_i$ where
$h_{m_1}$ and $h_{m_2}$ differ, the adversary learns the secret key
values $r_{i}^0$ and $r_{i}^1$ that are
used to produce the corresponding values in the public key. For these bits, Eve is
able to assign values arbitrarily.</p>
<p>As a toy example, consider the case where:</p>
<p>$h_{m_1} = 0100110…011$</p>
<p>$h_{m_2} = 1101011…010$</p>
<p>Eve will be able to construct messages of the form:</p>
<p>$h_{m_3} = SHA256(m_3) = b10bb1b…01b$</p>
<p>where each $b$ value can be either a $0$ or $1$. This implies that Alice loses
about <em>half</em> of her security each time she re-uses her keypair to sign a message. Therefore,
after re-using the same key to sign just $5$ messages, Alice reduces her security from a
problem that would take over the age of the universe for Eve to solve in order to sign a single
message, to allowing Eve to sign almost any message without having to solve anything.</p>
<h2 id="key-and-signature-size">Key and signature size</h2>
<p>Note that these digital signatures are not very succinct. The signature itself
is $(256\cdot 256)$ bits $= 8$ kilobytes. Even worse, the public and private keys are each
$2\cdot(256\cdot 256)$ bits $= 16$ kilobytes. This is not to say
that signing, verification, and key generation are not <em>fast</em> or even that they are
inferior to other digital signature schemes. However, in many settings this overhead in signature size
can be an unacceptable cost e.g., power-constrained devices running on mobile ad hoc networks.</p>
<h2 id="implementation-in-python">Implementation in Python</h2>
<p>Below is an implementation of the Lamport one-time signature scheme in Python3. I usually
try to provide an interface for readers to try out the code from the browser (see my posts
on <a href="/Miller-Rabin">Miller-Rabin primality test</a>, <a href="/Baby-Step-Giant-Step">solving discrete log in Bash</a>, etc.);
however, I’m not providing one for this post. The primary reason being that signatures are 8KB in size,
and I don’t think that will display nicely on the page. Feel free to copy and paste the code
and try it out on your own!</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/python3</span>
<span class="c"># Author: Zachary Ratliff</span>
<span class="c"># Simple Python implementation of Lamport signature scheme</span>
<span class="kn">import</span> <span class="nn">hashlib</span>
<span class="kn">import</span> <span class="nn">math</span>
<span class="kn">import</span> <span class="nn">secrets</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">CHAR_ENC</span> <span class="o">=</span> <span class="s">'utf-8'</span>
<span class="n">BYTE_ORDER</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">byteorder</span>
<span class="n">SK</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">PK</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c"># Generate a keypair for a one-time signature</span>
<span class="k">def</span> <span class="nf">keygen</span><span class="p">():</span>
<span class="n">sk</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">)]</span> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">)]</span>
<span class="n">pk</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">)]</span> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">256</span><span class="p">):</span>
<span class="c">#secret key</span>
<span class="n">sk</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">secrets</span><span class="o">.</span><span class="n">token_bytes</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span>
<span class="n">sk</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">secrets</span><span class="o">.</span><span class="n">token_bytes</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span>
<span class="c">#public key</span>
<span class="n">pk</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">sk</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">i</span><span class="p">])</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span>
<span class="n">pk</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">sk</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="n">i</span><span class="p">])</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span>
<span class="n">keypair</span> <span class="o">=</span> <span class="p">[</span><span class="n">sk</span><span class="p">,</span><span class="n">pk</span><span class="p">]</span>
<span class="k">return</span> <span class="n">keypair</span>
<span class="c"># Sign messages using Lamport one-time signatures</span>
<span class="k">def</span> <span class="nf">sign</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">sk</span><span class="p">):</span>
<span class="n">sig</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">)]</span>
<span class="n">h</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">CHAR_ENC</span><span class="p">))</span><span class="o">.</span><span class="n">digest</span><span class="p">(),</span> <span class="n">BYTE_ORDER</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">256</span><span class="p">):</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">h</span> <span class="o">>></span> <span class="n">i</span> <span class="o">&</span> <span class="mi">1</span>
<span class="n">sig</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">sk</span><span class="p">[</span><span class="n">b</span><span class="p">][</span><span class="n">i</span><span class="p">]</span>
<span class="k">return</span> <span class="n">sig</span>
<span class="c"># Verify Lamport message signatures</span>
<span class="k">def</span> <span class="nf">verify</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">sig</span><span class="p">,</span> <span class="n">pk</span><span class="p">):</span>
<span class="n">h</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">CHAR_ENC</span><span class="p">))</span><span class="o">.</span><span class="n">digest</span><span class="p">(),</span> <span class="n">BYTE_ORDER</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">256</span><span class="p">):</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">h</span> <span class="o">>></span> <span class="n">i</span> <span class="o">&</span> <span class="mi">1</span>
<span class="n">check</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="n">sig</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">.</span><span class="n">digest</span><span class="p">()</span>
<span class="k">if</span> <span class="n">pk</span><span class="p">[</span><span class="n">b</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">!=</span> <span class="n">check</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
<span class="n">keypair</span> <span class="o">=</span> <span class="n">keygen</span><span class="p">()</span>
<span class="n">message</span> <span class="o">=</span> <span class="s">"Lamport signatures are cool!"</span>
<span class="n">sig</span> <span class="o">=</span> <span class="n">sign</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">keypair</span><span class="p">[</span><span class="n">SK</span><span class="p">])</span>
<span class="n">verify</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">sig</span><span class="p">,</span> <span class="n">keypair</span><span class="p">[</span><span class="n">PK</span><span class="p">])</span>
</code></pre></div></div>
<script type="text/javascript" async="" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML">
</script>Zachary RatliffArmed with a cryptographically secure one-way hash function and a secure source of randomness, we can build a digital signature scheme that is believed to be secure even with the advent of quantum computers. Created by Leslie Lamport at SRI International, the Lamport signature scheme is useful for many things.