diff --git a/src/ctap/storage.rs b/src/ctap/storage.rs
index 7569c1b0fc956474b933ba90cf10e2549af0914a..565b0b99bb4f3b39fbc532c02226e93ee5e1392e 100644
--- a/src/ctap/storage.rs
+++ b/src/ctap/storage.rs
@@ -198,6 +198,7 @@ impl PersistentStore {
                 .insert(StoreEntry {
                     tag: MASTER_KEYS,
                     data: &master_keys,
+                    sensitive: true,
                 })
                 .unwrap();
         }
@@ -206,6 +207,7 @@ impl PersistentStore {
                 .insert(StoreEntry {
                     tag: PIN_RETRIES,
                     data: &[MAX_PIN_RETRIES],
+                    sensitive: false,
                 })
                 .unwrap();
         }
@@ -245,6 +247,7 @@ impl PersistentStore {
         let new_entry = StoreEntry {
             tag: TAG_CREDENTIAL,
             data: &credential,
+            sensitive: true,
         };
         match old_entry {
             None => self.store.insert(new_entry)?,
@@ -299,6 +302,7 @@ impl PersistentStore {
                     .insert(StoreEntry {
                         tag: GLOBAL_SIGNATURE_COUNTER,
                         data: &buffer,
+                        sensitive: false,
                     })
                     .unwrap();
             }
@@ -312,6 +316,7 @@ impl PersistentStore {
                         StoreEntry {
                             tag: GLOBAL_SIGNATURE_COUNTER,
                             data: &buffer,
+                            sensitive: false,
                         },
                     )
                     .unwrap();
@@ -339,6 +344,7 @@ impl PersistentStore {
         let entry = StoreEntry {
             tag: PIN_HASH,
             data: pin_hash,
+            sensitive: true,
         };
         match self.store.find_one(&Key::PinHash) {
             None => self.store.insert(entry).unwrap(),
@@ -368,6 +374,7 @@ impl PersistentStore {
                 StoreEntry {
                     tag: PIN_RETRIES,
                     data: &[new_value],
+                    sensitive: false,
                 },
             )
             .unwrap();
@@ -381,6 +388,7 @@ impl PersistentStore {
                 StoreEntry {
                     tag: PIN_RETRIES,
                     data: &[MAX_PIN_RETRIES],
+                    sensitive: false,
                 },
             )
             .unwrap();
@@ -465,9 +473,9 @@ mod test {
         let storage = Storage::new(store, options);
         let store = embedded_flash::Store::new(storage, Config).unwrap();
         // We can replace 3 bytes with minimal overhead.
-        assert_eq!(store.replace_len(0), 2 * WORD_SIZE);
-        assert_eq!(store.replace_len(3), 2 * WORD_SIZE);
-        assert_eq!(store.replace_len(4), 3 * WORD_SIZE);
+        assert_eq!(store.replace_len(false, 0), 2 * WORD_SIZE);
+        assert_eq!(store.replace_len(false, 3), 3 * WORD_SIZE);
+        assert_eq!(store.replace_len(false, 4), 3 * WORD_SIZE);
     }
 
     #[test]
diff --git a/src/embedded_flash/store/bitfield.rs b/src/embedded_flash/store/bitfield.rs
index 60c6f869b583e4cf2fb6d4ff35040fe579dc0efb..797c78bd2b2cd12554c2248318e98d68e49f3c33 100644
--- a/src/embedded_flash/store/bitfield.rs
+++ b/src/embedded_flash/store/bitfield.rs
@@ -55,6 +55,11 @@ impl ByteGap {
             bit + 8 * self.length
         }
     }
+
+    /// Returns the slice of `data` corresponding to the gap.
+    pub fn slice(self, data: &[u8]) -> &[u8] {
+        &data[self.start..self.start + self.length]
+    }
 }
 
 /// Returns whether a bit is set in a sequence of bits.
diff --git a/src/embedded_flash/store/format.rs b/src/embedded_flash/store/format.rs
index cbef61f5e5981cae5b257bedc919ace462ed9449..8308c4a95f69494b35b522c4259dfa1e17e913b0 100644
--- a/src/embedded_flash/store/format.rs
+++ b/src/embedded_flash/store/format.rs
@@ -59,6 +59,14 @@ pub struct Format {
     /// - 1 for insert entries.
     replace_bit: usize,
 
+    /// Whether a user entry has sensitive data.
+    ///
+    /// When a user entry with sensitive data is deleted, the data is overwritten with zeroes.
+    ///
+    /// - 0 for sensitive data.
+    /// - 1 for non-sensitive data.
+    sensitive_bit: usize,
+
     /// The data length of a user entry.
     length_range: bitfield::BitRange,
 
@@ -138,8 +146,9 @@ impl Format {
         let deleted_bit = present_bit + 1;
         let internal_bit = deleted_bit + 1;
         let replace_bit = internal_bit + 1;
+        let sensitive_bit = replace_bit + 1;
         let length_range = bitfield::BitRange {
-            start: replace_bit + 1,
+            start: sensitive_bit + 1,
             length: byte_bits,
         };
         let tag_range = bitfield::BitRange {
@@ -182,6 +191,7 @@ impl Format {
             deleted_bit,
             internal_bit,
             replace_bit,
+            sensitive_bit,
             length_range,
             tag_range,
             replace_page_range,
@@ -196,10 +206,11 @@ impl Format {
         // Make sure all the following conditions hold:
         // - The page header is one word.
         // - The internal entry is one word.
-        // - The entry header fits in one word.
+        // - The entry header fits in one word (which is equivalent to the entry header size being
+        //   exactly one word for sensitive entries).
         if format.page_header_size() != word_size
             || format.internal_entry_size() != word_size
-            || format.header_size() > word_size
+            || format.header_size(true) != word_size
         {
             return None;
         }
@@ -220,28 +231,46 @@ impl Format {
     /// Returns the entry header length in bytes.
     ///
     /// This is the smallest number of bytes necessary to store all fields of the entry info up to
-    /// and including `length`.
-    pub fn header_size(&self) -> usize {
-        self.bits_to_bytes(self.length_range.end())
+    /// and including `length`. For sensitive entries, the result is word-aligned.
+    pub fn header_size(&self, sensitive: bool) -> usize {
+        let mut size = self.bits_to_bytes(self.length_range.end());
+        if sensitive {
+            // We need to align to the next word boundary so that wiping the user data will not
+            // count as a write to the header.
+            size = self.align_word(size);
+        }
+        size
+    }
+
+    /// Returns the entry header length in bytes.
+    ///
+    /// This is a convenience function for `header_size` above.
+    fn header_offset(&self, entry: &[u8]) -> usize {
+        self.header_size(self.is_sensitive(entry))
     }
 
     /// Returns the entry info length in bytes.
     ///
     /// This is the number of bytes necessary to store all fields of the entry info. This also
-    /// includes the internal padding to protect the `committed` bit from the `deleted` bit.
-    fn info_size(&self, is_replace: IsReplace) -> usize {
+    /// includes the internal padding to protect the `committed` bit from the `deleted` bit and to
+    /// protect the entry info from the user data for sensitive entries.
+    fn info_size(&self, is_replace: IsReplace, sensitive: bool) -> usize {
         let suffix_bits = 2; // committed + complete
         let info_bits = match is_replace {
             IsReplace::Replace => self.replace_byte_range.end() + suffix_bits,
             IsReplace::Insert => self.tag_range.end() + suffix_bits,
         };
-        let info_size = self.bits_to_bytes(info_bits);
+        let mut info_size = self.bits_to_bytes(info_bits);
         // If the suffix bits would end up in the header, we need to add one byte for them.
-        if info_size == self.header_size() {
-            info_size + 1
-        } else {
-            info_size
+        let header_size = self.header_size(sensitive);
+        if info_size <= header_size {
+            info_size = header_size + 1;
+        }
+        // If the entry is sensitive, we need to align to the next word boundary.
+        if sensitive {
+            info_size = self.align_word(info_size);
         }
+        info_size
     }
 
     /// Returns the length in bytes of an entry.
@@ -249,8 +278,8 @@ impl Format {
     /// This depends on the length of the user data and whether the entry replaces an old entry or
     /// is an insertion. This also includes the internal padding to protect the `committed` bit from
     /// the `deleted` bit.
-    pub fn entry_size(&self, is_replace: IsReplace, length: usize) -> usize {
-        let mut entry_size = length + self.info_size(is_replace);
+    pub fn entry_size(&self, is_replace: IsReplace, sensitive: bool, length: usize) -> usize {
+        let mut entry_size = length + self.info_size(is_replace, sensitive);
         let word_size = self.word_size;
         entry_size = self.align_word(entry_size);
         // The entry must be at least 2 words such that the `committed` and `deleted` bits are on
@@ -308,6 +337,14 @@ impl Format {
         bitfield::set_zero(self.replace_bit, header, bitfield::NO_GAP)
     }
 
+    pub fn is_sensitive(&self, header: &[u8]) -> bool {
+        bitfield::is_zero(self.sensitive_bit, header, bitfield::NO_GAP)
+    }
+
+    pub fn set_sensitive(&self, header: &mut [u8]) {
+        bitfield::set_zero(self.sensitive_bit, header, bitfield::NO_GAP)
+    }
+
     pub fn get_length(&self, header: &[u8]) -> usize {
         bitfield::get_range(self.length_range, header, bitfield::NO_GAP)
     }
@@ -317,16 +354,19 @@ impl Format {
     }
 
     pub fn get_data<'a>(&self, entry: &'a [u8]) -> &'a [u8] {
-        &entry[self.header_size()..][..self.get_length(entry)]
+        &entry[self.header_offset(entry)..][..self.get_length(entry)]
     }
 
     /// Returns the span of user data in an entry.
     ///
     /// The complement of this gap in the entry is exactly the entry info. The header is before the
     /// gap and the footer is after the gap.
-    fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap {
-        let start = self.header_size();
-        let length = self.get_length(entry);
+    pub fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap {
+        let start = self.header_offset(entry);
+        let mut length = self.get_length(entry);
+        if self.is_sensitive(entry) {
+            length = self.align_word(length);
+        }
         bitfield::ByteGap { start, length }
     }
 
@@ -406,16 +446,23 @@ impl Format {
 
     /// Builds an entry for replace or insert operations.
     pub fn build_entry(&self, replace: Option<Index>, user_entry: StoreEntry) -> Vec<u8> {
-        let StoreEntry { tag, data } = user_entry;
+        let StoreEntry {
+            tag,
+            data,
+            sensitive,
+        } = user_entry;
         let is_replace = match replace {
             None => IsReplace::Insert,
             Some(_) => IsReplace::Replace,
         };
-        let entry_len = self.entry_size(is_replace, data.len());
+        let entry_len = self.entry_size(is_replace, sensitive, data.len());
         let mut entry = Vec::with_capacity(entry_len);
         // Build the header.
-        entry.resize(self.header_size(), 0xff);
+        entry.resize(self.header_size(sensitive), 0xff);
         self.set_present(&mut entry[..]);
+        if sensitive {
+            self.set_sensitive(&mut entry[..]);
+        }
         self.set_length(&mut entry[..], data.len());
         // Add the data.
         entry.extend_from_slice(data);
diff --git a/src/embedded_flash/store/mod.rs b/src/embedded_flash/store/mod.rs
index 0431073009286219699fcce75307c66b1a957e5d..4111068a9e426dec4369745ab38f3e4141aba808 100644
--- a/src/embedded_flash/store/mod.rs
+++ b/src/embedded_flash/store/mod.rs
@@ -57,7 +57,7 @@
 //!     new_page:page_bits
 //!     Padding(word)
 //! Entry := Header Data Footer
-//! // Let X be the byte following `length` in `Info`.
+//! // Let X be the byte (word-aligned for sensitive queries) following `length` in `Info`.
 //! Header := Info[..X]  // must fit in one word
 //! Footer := Info[X..]  // must fit in one word
 //! Info :=
@@ -65,6 +65,7 @@
 //!     deleted:1
 //!     internal=1
 //!     replace:1
+//!     sensitive:1
 //!     length:byte_bits
 //!     tag:tag_bits
 //!     [  // present if `replace` is 0
@@ -109,15 +110,16 @@
 //!    0.1   deleted
 //!    0.2   internal
 //!    0.3   replace
-//!    0.4   length (9 bits)
-//!    1.5   tag (least significant 3 bits out of 5)
+//!    0.4   sensitive
+//!    0.5   length (9 bits)
+//!    1.6   tag (least significant 2 bits out of 5)
 //! (the header ends at the first byte boundary after `length`)
 //!    2.0   <user data> (2 bytes in this example)
 //! (the footer starts immediately after the user data)
-//!    4.0   tag (most significant 2 bits out of 5)
-//!    4.2   replace_page (6 bits)
-//!    5.0   replace_byte (9 bits)
-//!    6.1   padding (make sure the 2 properties below hold)
+//!    4.0   tag (most significant 3 bits out of 5)
+//!    4.3   replace_page (6 bits)
+//!    5.1   replace_byte (9 bits)
+//!    6.2   padding (make sure the 2 properties below hold)
 //!    7.6   committed
 //!    7.7   complete (on a different word than `present`)
 //!    8.0   <end> (word-aligned)
@@ -203,6 +205,11 @@ pub struct StoreEntry<'a> {
 
     /// The data of the entry.
     pub data: &'a [u8],
+
+    /// Whether the data is sensitive.
+    ///
+    /// Sensitive data is overwritten with zeroes when the entry is deleted.
+    pub sensitive: bool,
 }
 
 /// Implements a configurable multi-set on top of any storage.
@@ -262,6 +269,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
                     StoreEntry {
                         tag: self.format.get_tag(entry),
                         data: self.format.get_data(entry),
+                        sensitive: self.format.is_sensitive(entry),
                     },
                 ))
             } else {
@@ -326,7 +334,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
         self.format.validate_entry(new)?;
         let mut old_index = old.index;
         // Find a slot.
-        let entry_len = self.replace_len(new.data.len());
+        let entry_len = self.replace_len(new.sensitive, new.data.len());
         let index = self.find_slot_for_write(entry_len, Some(&mut old_index))?;
         // Build a new entry replacing the old one.
         let entry = self.format.build_entry(Some(old_index), new);
@@ -360,17 +368,20 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
     /// Returns the byte cost of a replace operation.
     ///
     /// Computes the length in bytes that would be used in the storage if a replace operation is
-    /// executed provided the data of the new entry has `length` bytes.
-    pub fn replace_len(&self, length: usize) -> usize {
-        self.format.entry_size(IsReplace::Replace, length)
+    /// executed provided the data of the new entry has `length` bytes and whether this data is
+    /// sensitive.
+    pub fn replace_len(&self, sensitive: bool, length: usize) -> usize {
+        self.format
+            .entry_size(IsReplace::Replace, sensitive, length)
     }
 
     /// Returns the byte cost of an insert operation.
     ///
     /// Computes the length in bytes that would be used in the storage if an insert operation is
-    /// executed provided the data of the inserted entry has `length` bytes.
-    pub fn insert_len(&self, length: usize) -> usize {
-        self.format.entry_size(IsReplace::Insert, length)
+    /// executed provided the data of the inserted entry has `length` bytes and whether this data is
+    /// sensitive.
+    pub fn insert_len(&self, sensitive: bool, length: usize) -> usize {
+        self.format.entry_size(IsReplace::Insert, sensitive, length)
     }
 
     /// Returns the erase count of all pages.
@@ -410,8 +421,11 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
                 let entry_index = index;
                 let entry = self.read_entry(index);
                 index.byte += entry.len();
-                if !self.format.is_alive(entry) {
-                    // Skip deleted entries (or the page padding).
+                if !self.format.is_present(entry) {
+                    // Reached the end of the page.
+                } else if self.format.is_deleted(entry) {
+                    // Wipe sensitive data if needed.
+                    self.wipe_sensitive_data(entry_index);
                 } else if self.format.is_internal(entry) {
                     // Finish page compaction.
                     self.erase_page(entry_index);
@@ -449,6 +463,31 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
     /// The provided index must point to the beginning of an entry.
     fn delete_index(&mut self, index: Index) {
         self.update_word(index, |format, word| format.set_deleted(word));
+        self.wipe_sensitive_data(index);
+    }
+
+    /// Wipes the data of a sensitive entry.
+    ///
+    /// If the entry at the provided index is sensitive, overwrites the data with zeroes. Otherwise,
+    /// does nothing.
+    fn wipe_sensitive_data(&mut self, mut index: Index) {
+        let entry = self.read_entry(index);
+        debug_assert!(self.format.is_present(entry));
+        debug_assert!(self.format.is_deleted(entry));
+        if self.format.is_internal(entry) || !self.format.is_sensitive(entry) {
+            // No need to wipe the data.
+            return;
+        }
+        let gap = self.format.entry_gap(entry);
+        let data = gap.slice(entry);
+        if data.iter().all(|&byte| byte == 0x00) {
+            // The data is already wiped.
+            return;
+        }
+        index.byte += gap.start;
+        self.storage
+            .write_slice(index, &vec![0; gap.length])
+            .unwrap();
     }
 
     /// Finds a page with enough free space.
@@ -555,10 +594,13 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
         } else if self.format.is_internal(first_byte) {
             self.format.internal_entry_size()
         } else {
-            let header = self.read_slice(index, self.format.header_size());
+            // We don't know if the entry is sensitive or not, but it doesn't matter here. We just
+            // need to read the replace, sensitive, and length fields.
+            let header = self.read_slice(index, self.format.header_size(false));
             let replace = self.format.is_replace(header);
+            let sensitive = self.format.is_sensitive(header);
             let length = self.format.get_length(header);
-            self.format.entry_size(replace, length)
+            self.format.entry_size(replace, sensitive, length)
         };
         // Truncate the length to fit the page. This can only happen in case of corruption or
         // partial writes.
@@ -673,7 +715,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
         // Save the old page index and erase count to the new page.
         let erase_index = new_index;
         let erase_entry = self.format.build_erase_entry(old_page, erase_count);
-        self.storage.write_slice(new_index, &erase_entry).unwrap();
+        self.write_entry(new_index, &erase_entry);
         // Erase the page.
         self.erase_page(erase_index);
         // Increase generation.
@@ -728,6 +770,22 @@ impl<C: StoreConfig> Store<BufferStorage, C> {
     pub fn set_erase_count(&mut self, page: usize, erase_count: usize) {
         self.initialize_page(page, erase_count);
     }
+
+    /// Checks whether all deleted sensitive entries have been wiped.
+    pub fn check_wiped(&self) {
+        for (_, entry) in Iter::new(self) {
+            if !self.format.is_present(entry)
+                || !self.format.is_deleted(entry)
+                || self.format.is_internal(entry)
+                || !self.format.is_sensitive(entry)
+            {
+                continue;
+            }
+            let gap = self.format.entry_gap(entry);
+            let data = gap.slice(entry);
+            assert!(data.iter().all(|&byte| byte == 0x00));
+        }
+    }
 }
 
 /// Maps an index from an old page to a new page if needed.
@@ -843,7 +901,27 @@ mod tests {
         let tag = 0;
         let key = 1;
         let data = &[key, 2];
-        let entry = StoreEntry { tag, data };
+        let entry = StoreEntry {
+            tag,
+            data,
+            sensitive: false,
+        };
+        store.insert(entry).unwrap();
+        assert_eq!(store.iter().count(), 1);
+        assert_eq!(store.find_one(&key).unwrap().1, entry);
+    }
+
+    #[test]
+    fn insert_sensitive_ok() {
+        let mut store = new_store();
+        let tag = 0;
+        let key = 1;
+        let data = &[key, 4];
+        let entry = StoreEntry {
+            tag,
+            data,
+            sensitive: true,
+        };
         store.insert(entry).unwrap();
         assert_eq!(store.iter().count(), 1);
         assert_eq!(store.find_one(&key).unwrap().1, entry);
@@ -857,6 +935,7 @@ mod tests {
         let entry = StoreEntry {
             tag,
             data: &[key, 2],
+            sensitive: false,
         };
         store.insert(entry).unwrap();
         assert_eq!(store.find_all(&key).count(), 1);
@@ -866,6 +945,25 @@ mod tests {
         assert_eq!(store.iter().count(), 0);
     }
 
+    #[test]
+    fn delete_sensitive_ok() {
+        let mut store = new_store();
+        let tag = 0;
+        let key = 1;
+        let entry = StoreEntry {
+            tag,
+            data: &[key, 2],
+            sensitive: true,
+        };
+        store.insert(entry).unwrap();
+        assert_eq!(store.find_all(&key).count(), 1);
+        let (index, _) = store.find_one(&key).unwrap();
+        store.delete(index).unwrap();
+        assert_eq!(store.find_all(&key).count(), 0);
+        assert_eq!(store.iter().count(), 0);
+        store.check_wiped();
+    }
+
     #[test]
     fn insert_until_full() {
         let mut store = new_store();
@@ -875,6 +973,7 @@ mod tests {
             .insert(StoreEntry {
                 tag,
                 data: &[key, 0],
+                sensitive: false,
             })
             .is_ok()
         {
@@ -892,6 +991,7 @@ mod tests {
             .insert(StoreEntry {
                 tag,
                 data: &[key, 0],
+                sensitive: false,
             })
             .is_ok()
         {
@@ -903,6 +1003,7 @@ mod tests {
             .insert(StoreEntry {
                 tag: 0,
                 data: &[key, 0],
+                sensitive: false,
             })
             .unwrap();
         for k in 1..=key {
@@ -916,7 +1017,11 @@ mod tests {
         let tag = 0;
         let key = 1;
         let data = &[key, 2];
-        let entry = StoreEntry { tag, data };
+        let entry = StoreEntry {
+            tag,
+            data,
+            sensitive: false,
+        };
         store.insert(entry).unwrap();
 
         // Reboot the store.
@@ -934,10 +1039,12 @@ mod tests {
         let old_entry = StoreEntry {
             tag,
             data: &[key, 2, 3, 4, 5, 6],
+            sensitive: false,
         };
         let new_entry = StoreEntry {
             tag,
             data: &[key, 7, 8, 9],
+            sensitive: false,
         };
         let mut delay = 0;
         loop {
@@ -973,6 +1080,7 @@ mod tests {
                 .insert(StoreEntry {
                     tag,
                     data: &[key, 0],
+                    sensitive: false,
                 })
                 .is_ok()
             {
@@ -983,7 +1091,14 @@ mod tests {
             let (index, _) = store.find_one(&1).unwrap();
             store.arm_snapshot(delay);
             store
-                .replace(index, StoreEntry { tag, data: &[1, 1] })
+                .replace(
+                    index,
+                    StoreEntry {
+                        tag,
+                        data: &[1, 1],
+                        sensitive: false,
+                    },
+                )
                 .unwrap();
             let (complete, store) = match store.get_snapshot() {
                 Err(_) => (true, store.get_storage()),
@@ -995,7 +1110,11 @@ mod tests {
                 assert_eq!(store.find_all(&k).count(), 1);
                 assert_eq!(
                     store.find_one(&k).unwrap().1,
-                    StoreEntry { tag, data: &[k, 0] }
+                    StoreEntry {
+                        tag,
+                        data: &[k, 0],
+                        sensitive: false,
+                    }
                 );
             }
             assert_eq!(store.find_all(&1).count(), 1);
@@ -1012,7 +1131,11 @@ mod tests {
     #[test]
     fn invalid_tag() {
         let mut store = new_store();
-        let entry = StoreEntry { tag: 1, data: &[] };
+        let entry = StoreEntry {
+            tag: 1,
+            data: &[],
+            sensitive: false,
+        };
         assert_eq!(store.insert(entry), Err(StoreError::InvalidTag));
     }
 
@@ -1022,6 +1145,7 @@ mod tests {
         let entry = StoreEntry {
             tag: 0,
             data: &[0; PAGE_SIZE],
+            sensitive: false,
         };
         assert_eq!(store.insert(entry), Err(StoreError::StoreFull));
     }