#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" )] #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] //! Securely zero memory with a simple trait ([`Zeroize`]) built on stable Rust //! primitives which guarantee the operation will not be "optimized away". //! //! ## About //! //! [Zeroing memory securely is hard] - compilers optimize for performance, and //! in doing so they love to "optimize away" unnecessary zeroing calls. There are //! many documented "tricks" to attempt to avoid these optimizations and ensure //! that a zeroing routine is performed reliably. //! //! This crate isn't about tricks: it uses [`core::ptr::write_volatile`] //! and [`core::sync::atomic`] memory fences to provide easy-to-use, portable //! zeroing behavior which works on all of Rust's core number types and slices //! thereof, implemented in pure Rust with no usage of FFI or assembly. //! //! - No insecure fallbacks! //! - No dependencies! //! - No FFI or inline assembly! **WASM friendly** (and tested)! //! - `#![no_std]` i.e. **embedded-friendly**! //! - No functionality besides securely zeroing memory! //! - (Optional) Custom derive support for zeroing complex structures //! //! ## Minimum Supported Rust Version //! //! Requires Rust **1.60** or newer. //! //! In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope //! for this crate's SemVer guarantees), however when we do it will be accompanied //! by a minor version bump. //! //! ## Usage //! //! ``` //! use zeroize::Zeroize; //! //! fn main() { //! // Protip: don't embed secrets in your source code. //! // This is just an example. //! let mut secret = b"Air shield password: 1,2,3,4,5".to_vec(); //! // [ ... ] open the air shield here //! //! // Now that we're done using the secret, zero it out. //! secret.zeroize(); //! } //! ``` //! //! The [`Zeroize`] trait is impl'd on all of Rust's core scalar types including //! integers, floats, `bool`, and `char`. //! //! Additionally, it's implemented on slices and `IterMut`s of the above types. //! //! When the `alloc` feature is enabled (which it is by default), it's also //! impl'd for `Vec` for the above types as well as `String`, where it provides //! [`Vec::clear`] / [`String::clear`]-like behavior (truncating to zero-length) //! but ensures the backing memory is securely zeroed with some caveats. //! //! With the `std` feature enabled (which it is **not** by default), [`Zeroize`] //! is also implemented for [`CString`]. After calling `zeroize()` on a `CString`, //! its internal buffer will contain exactly one nul byte. The backing //! memory is zeroed by converting it to a `Vec` and back into a `CString`. //! (NOTE: see "Stack/Heap Zeroing Notes" for important `Vec`/`String`/`CString` details) //! //! //! The [`DefaultIsZeroes`] marker trait can be impl'd on types which also //! impl [`Default`], which implements [`Zeroize`] by overwriting a value with //! the default value. //! //! ## Custom Derive Support //! //! This crate has custom derive support for the `Zeroize` trait, //! gated under the `zeroize` crate's `zeroize_derive` Cargo feature, //! which automatically calls `zeroize()` on all members of a struct //! or tuple struct. //! //! Attributes supported for `Zeroize`: //! //! On the item level: //! - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead //! - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds //! inferred by zeroize //! //! On the field level: //! - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` //! //! Attributes supported for `ZeroizeOnDrop`: //! //! On the field level: //! - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` //! //! Example which derives `Drop`: //! //! ``` //! # #[cfg(feature = "zeroize_derive")] //! # { //! use zeroize::{Zeroize, ZeroizeOnDrop}; //! //! // This struct will be zeroized on drop //! #[derive(Zeroize, ZeroizeOnDrop)] //! struct MyStruct([u8; 32]); //! # } //! ``` //! //! Example which does not derive `Drop` (useful for e.g. `Copy` types) //! //! ``` //! #[cfg(feature = "zeroize_derive")] //! # { //! use zeroize::Zeroize; //! //! // This struct will *NOT* be zeroized on drop //! #[derive(Copy, Clone, Zeroize)] //! struct MyStruct([u8; 32]); //! # } //! ``` //! //! Example which only derives `Drop`: //! //! ``` //! # #[cfg(feature = "zeroize_derive")] //! # { //! use zeroize::ZeroizeOnDrop; //! //! // This struct will be zeroized on drop //! #[derive(ZeroizeOnDrop)] //! struct MyStruct([u8; 32]); //! # } //! ``` //! //! ## `Zeroizing`: wrapper for zeroizing arbitrary values on drop //! //! `Zeroizing` is a generic wrapper type that impls `Deref` //! and `DerefMut`, allowing access to an inner value of type `Z`, and also //! impls a `Drop` handler which calls `zeroize()` on its contents: //! //! ``` //! use zeroize::Zeroizing; //! //! fn main() { //! let mut secret = Zeroizing::new([0u8; 5]); //! //! // Set the air shield password //! // Protip (again): don't embed secrets in your source code. //! secret.copy_from_slice(&[1, 2, 3, 4, 5]); //! assert_eq!(secret.as_ref(), &[1, 2, 3, 4, 5]); //! //! // The contents of `secret` will be automatically zeroized on drop //! } //! ``` //! //! ## What guarantees does this crate provide? //! //! This crate guarantees the following: //! //! 1. The zeroing operation can't be "optimized away" by the compiler. //! 2. All subsequent reads to memory will see "zeroized" values. //! //! LLVM's volatile semantics ensure #1 is true. //! //! Additionally, thanks to work by the [Unsafe Code Guidelines Working Group], //! we can now fairly confidently say #2 is true as well. Previously there were //! worries that the approach used by this crate (mixing volatile and //! non-volatile accesses) was undefined behavior due to language contained //! in the documentation for `write_volatile`, however after some discussion //! [these remarks have been removed] and the specific usage pattern in this //! crate is considered to be well-defined. //! //! Additionally this crate leverages [`core::sync::atomic::compiler_fence`] //! with the strictest ordering //! ([`Ordering::SeqCst`]) as a //! precaution to help ensure reads are not reordered before memory has been //! zeroed. //! //! All of that said, there is still potential for microarchitectural attacks //! (ala Spectre/Meltdown) to leak "zeroized" secrets through covert channels. //! This crate makes no guarantees that zeroized values cannot be leaked //! through such channels, as they represent flaws in the underlying hardware. //! //! ## Stack/Heap Zeroing Notes //! //! This crate can be used to zero values from either the stack or the heap. //! //! However, be aware several operations in Rust can unintentionally leave //! copies of data in memory. This includes but is not limited to: //! //! - Moves and [`Copy`] //! - Heap reallocation when using [`Vec`] and [`String`] //! - Borrowers of a reference making copies of the data //! //! [`Pin`][`core::pin::Pin`] can be leveraged in conjunction with this crate //! to ensure data kept on the stack isn't moved. //! //! The `Zeroize` impls for `Vec`, `String` and `CString` zeroize the entire //! capacity of their backing buffer, but cannot guarantee copies of the data //! were not previously made by buffer reallocation. It's therefore important //! when attempting to zeroize such buffers to initialize them to the correct //! capacity, and take care to prevent subsequent reallocation. //! //! The `secrecy` crate provides higher-level abstractions for eliminating //! usage patterns which can cause reallocations: //! //! //! //! ## What about: clearing registers, mlock, mprotect, etc? //! //! This crate is focused on providing simple, unobtrusive support for reliably //! zeroing memory using the best approach possible on stable Rust. //! //! Clearing registers is a difficult problem that can't easily be solved by //! something like a crate, and requires either inline ASM or rustc support. //! See for background on //! this particular problem. //! //! Other memory protection mechanisms are interesting and useful, but often //! overkill (e.g. defending against RAM scraping or attackers with swap access). //! In as much as there may be merit to these approaches, there are also many //! other crates that already implement more sophisticated memory protections. //! Such protections are explicitly out-of-scope for this crate. //! //! Zeroing memory is [good cryptographic hygiene] and this crate seeks to promote //! it in the most unobtrusive manner possible. This includes omitting complex //! `unsafe` memory protection systems and just trying to make the best memory //! zeroing crate available. //! //! [Zeroing memory securely is hard]: http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html //! [Unsafe Code Guidelines Working Group]: https://github.com/rust-lang/unsafe-code-guidelines //! [these remarks have been removed]: https://github.com/rust-lang/rust/pull/60972 //! [good cryptographic hygiene]: https://github.com/veorq/cryptocoding#clean-memory-of-secret-data //! [`Ordering::SeqCst`]: core::sync::atomic::Ordering::SeqCst #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "std")] extern crate std; #[cfg(feature = "zeroize_derive")] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize_derive")))] pub use zeroize_derive::{Zeroize, ZeroizeOnDrop}; #[cfg(all(feature = "aarch64", target_arch = "aarch64"))] mod aarch64; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86; use core::{ marker::{PhantomData, PhantomPinned}, mem::{self, MaybeUninit}, num::{ self, NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }, ops, ptr, slice::IterMut, sync::atomic, }; #[cfg(feature = "alloc")] use alloc::{boxed::Box, string::String, vec::Vec}; #[cfg(feature = "std")] use std::ffi::CString; /// Trait for securely erasing values from memory. pub trait Zeroize { /// Zero out this object from memory using Rust intrinsics which ensure the /// zeroization operation is not "optimized away" by the compiler. fn zeroize(&mut self); } /// Marker trait signifying that this type will [`Zeroize::zeroize`] itself on [`Drop`]. pub trait ZeroizeOnDrop {} /// Marker trait for types whose [`Default`] is the desired zeroization result pub trait DefaultIsZeroes: Copy + Default + Sized {} /// Fallible trait for representing cases where zeroization may or may not be /// possible. /// /// This is primarily useful for scenarios like reference counted data, where /// zeroization is only possible when the last reference is dropped. pub trait TryZeroize { /// Try to zero out this object from memory using Rust intrinsics which /// ensure the zeroization operation is not "optimized away" by the /// compiler. #[must_use] fn try_zeroize(&mut self) -> bool; } impl Zeroize for Z where Z: DefaultIsZeroes, { fn zeroize(&mut self) { volatile_write(self, Z::default()); atomic_fence(); } } macro_rules! impl_zeroize_with_default { ($($type:ty),+) => { $(impl DefaultIsZeroes for $type {})+ }; } #[rustfmt::skip] impl_zeroize_with_default! { PhantomPinned, (), bool, char, f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize } /// `PhantomPinned` is zero sized so provide a ZeroizeOnDrop implementation. impl ZeroizeOnDrop for PhantomPinned {} /// `()` is zero sized so provide a ZeroizeOnDrop implementation. impl ZeroizeOnDrop for () {} macro_rules! impl_zeroize_for_non_zero { ($($type:ty),+) => { $( impl Zeroize for $type { fn zeroize(&mut self) { const ONE: $type = match <$type>::new(1) { Some(one) => one, None => unreachable!(), }; volatile_write(self, ONE); atomic_fence(); } } )+ }; } impl_zeroize_for_non_zero!( NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize ); impl Zeroize for num::Wrapping where Z: Zeroize, { fn zeroize(&mut self) { self.0.zeroize(); } } /// Impl [`Zeroize`] on arrays of types that impl [`Zeroize`]. impl Zeroize for [Z; N] where Z: Zeroize, { fn zeroize(&mut self) { self.iter_mut().zeroize(); } } /// Impl [`ZeroizeOnDrop`] on arrays of types that impl [`ZeroizeOnDrop`]. impl ZeroizeOnDrop for [Z; N] where Z: ZeroizeOnDrop {} impl Zeroize for IterMut<'_, Z> where Z: Zeroize, { fn zeroize(&mut self) { for elem in self { elem.zeroize(); } } } impl Zeroize for Option where Z: Zeroize, { fn zeroize(&mut self) { if let Some(value) = self { value.zeroize(); // Ensures self is None and that the value was dropped. Without the take, the drop // of the (zeroized) value isn't called, which might lead to a leak or other // unexpected behavior. For example, if this were Option>, the above call to // zeroize would not free the allocated memory, but the the `take` call will. self.take(); } // Ensure that if the `Option` were previously `Some` but a value was copied/moved out // that the remaining space in the `Option` is zeroized. // // Safety: // // The memory pointed to by `self` is valid for `mem::size_of::()` bytes. // It is also properly aligned, because `u8` has an alignment of `1`. unsafe { volatile_set((self as *mut Self).cast::(), 0, mem::size_of::()); } // Ensures self is overwritten with the `None` bit pattern. volatile_write can't be // used because Option is not copy. // // Safety: // // self is safe to replace with `None`, which the take() call above should have // already done semantically. Any value which needed to be dropped will have been // done so by take(). unsafe { ptr::write_volatile(self, None) } atomic_fence(); } } impl ZeroizeOnDrop for Option where Z: ZeroizeOnDrop {} /// Impl [`Zeroize`] on [`MaybeUninit`] types. /// /// This fills the memory with zeroes. /// Note that this ignore invariants that `Z` might have, because /// [`MaybeUninit`] removes all invariants. impl Zeroize for MaybeUninit { fn zeroize(&mut self) { // Safety: // `MaybeUninit` is valid for any byte pattern, including zeros. unsafe { ptr::write_volatile(self, MaybeUninit::zeroed()) } atomic_fence(); } } /// Impl [`Zeroize`] on slices of [`MaybeUninit`] types. /// /// This impl can eventually be optimized using an memset intrinsic, /// such as [`core::intrinsics::volatile_set_memory`]. /// /// This fills the slice with zeroes. /// /// Note that this ignore invariants that `Z` might have, because /// [`MaybeUninit`] removes all invariants. impl Zeroize for [MaybeUninit] { fn zeroize(&mut self) { let ptr = self.as_mut_ptr().cast::>(); let size = self.len().checked_mul(mem::size_of::()).unwrap(); assert!(size <= isize::MAX as usize); // Safety: // // This is safe, because every valid pointer is well aligned for u8 // and it is backed by a single allocated object for at least `self.len() * size_pf::()` bytes. // and 0 is a valid value for `MaybeUninit` // The memory of the slice should not wrap around the address space. unsafe { volatile_set(ptr, MaybeUninit::zeroed(), size) } atomic_fence(); } } /// Impl [`Zeroize`] on slices of types that can be zeroized with [`Default`]. /// /// This impl can eventually be optimized using an memset intrinsic, /// such as [`core::intrinsics::volatile_set_memory`]. For that reason the /// blanket impl on slices is bounded by [`DefaultIsZeroes`]. /// /// To zeroize a mut slice of `Z: Zeroize` which does not impl /// [`DefaultIsZeroes`], call `iter_mut().zeroize()`. impl Zeroize for [Z] where Z: DefaultIsZeroes, { fn zeroize(&mut self) { assert!(self.len() <= isize::MAX as usize); // Safety: // // This is safe, because the slice is well aligned and is backed by a single allocated // object for at least `self.len()` elements of type `Z`. // `self.len()` is also not larger than an `isize`, because of the assertion above. // The memory of the slice should not wrap around the address space. unsafe { volatile_set(self.as_mut_ptr(), Z::default(), self.len()) }; atomic_fence(); } } impl Zeroize for str { fn zeroize(&mut self) { // Safety: // A zeroized byte slice is a valid UTF-8 string. unsafe { self.as_bytes_mut().zeroize() } } } /// [`PhantomData`] is always zero sized so provide a [`Zeroize`] implementation. impl Zeroize for PhantomData { fn zeroize(&mut self) {} } /// [`PhantomData` is always zero sized so provide a ZeroizeOnDrop implementation. impl ZeroizeOnDrop for PhantomData {} macro_rules! impl_zeroize_tuple { ( $( $type_name:ident ),+ ) => { impl<$($type_name: Zeroize),+> Zeroize for ($($type_name,)+) { fn zeroize(&mut self) { #[allow(non_snake_case)] let ($($type_name,)+) = self; $($type_name.zeroize());+ } } impl<$($type_name: ZeroizeOnDrop),+> ZeroizeOnDrop for ($($type_name,)+) { } } } // Generic implementations for tuples up to 10 parameters. impl_zeroize_tuple!(A); impl_zeroize_tuple!(A, B); impl_zeroize_tuple!(A, B, C); impl_zeroize_tuple!(A, B, C, D); impl_zeroize_tuple!(A, B, C, D, E); impl_zeroize_tuple!(A, B, C, D, E, F); impl_zeroize_tuple!(A, B, C, D, E, F, G); impl_zeroize_tuple!(A, B, C, D, E, F, G, H); impl_zeroize_tuple!(A, B, C, D, E, F, G, H, I); impl_zeroize_tuple!(A, B, C, D, E, F, G, H, I, J); #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl Zeroize for Vec where Z: Zeroize, { /// "Best effort" zeroization for `Vec`. /// /// Ensures the entire capacity of the `Vec` is zeroed. Cannot ensure that /// previous reallocations did not leave values on the heap. fn zeroize(&mut self) { // Zeroize all the initialized elements. self.iter_mut().zeroize(); // Set the Vec's length to 0 and drop all the elements. self.clear(); // Zero the full capacity of `Vec`. self.spare_capacity_mut().zeroize(); } } #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl ZeroizeOnDrop for Vec where Z: ZeroizeOnDrop {} #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl Zeroize for Box<[Z]> where Z: Zeroize, { /// Unlike `Vec`, `Box<[Z]>` cannot reallocate, so we can be sure that we are not leaving /// values on the heap. fn zeroize(&mut self) { self.iter_mut().zeroize(); } } #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl ZeroizeOnDrop for Box<[Z]> where Z: ZeroizeOnDrop {} #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl Zeroize for Box { fn zeroize(&mut self) { self.as_mut().zeroize(); } } #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl Zeroize for String { fn zeroize(&mut self) { unsafe { self.as_mut_vec() }.zeroize(); } } #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Zeroize for CString { fn zeroize(&mut self) { // mem::take uses replace internally to swap the pointer // Unfortunately this results in an allocation for a Box::new(&[0]) as CString must // contain a trailing zero byte let this = mem::take(self); // - CString::into_bytes_with_nul calls ::into_vec which takes ownership of the heap pointer // as a Vec // - Calling .zeroize() on the resulting vector clears out the bytes // From: https://github.com/RustCrypto/utils/pull/759#issuecomment-1087976570 let mut buf = this.into_bytes_with_nul(); buf.zeroize(); // expect() should never fail, because zeroize() truncates the Vec let zeroed = CString::new(buf).expect("buf not truncated"); // Replace self by the zeroed CString to maintain the original ptr of the buffer let _ = mem::replace(self, zeroed); } } /// `Zeroizing` is a a wrapper for any `Z: Zeroize` type which implements a /// `Drop` handler which zeroizes dropped values. #[derive(Debug, Default, Eq, PartialEq)] pub struct Zeroizing(Z); impl Zeroizing where Z: Zeroize, { /// Move value inside a `Zeroizing` wrapper which ensures it will be /// zeroized when it's dropped. #[inline(always)] pub fn new(value: Z) -> Self { Self(value) } } impl Clone for Zeroizing { #[inline(always)] fn clone(&self) -> Self { Self(self.0.clone()) } #[inline(always)] fn clone_from(&mut self, source: &Self) { self.0.zeroize(); self.0.clone_from(&source.0); } } impl From for Zeroizing where Z: Zeroize, { #[inline(always)] fn from(value: Z) -> Zeroizing { Zeroizing(value) } } impl ops::Deref for Zeroizing where Z: Zeroize, { type Target = Z; #[inline(always)] fn deref(&self) -> &Z { &self.0 } } impl ops::DerefMut for Zeroizing where Z: Zeroize, { #[inline(always)] fn deref_mut(&mut self) -> &mut Z { &mut self.0 } } impl AsRef for Zeroizing where T: ?Sized, Z: AsRef + Zeroize, { #[inline(always)] fn as_ref(&self) -> &T { self.0.as_ref() } } impl AsMut for Zeroizing where T: ?Sized, Z: AsMut + Zeroize, { #[inline(always)] fn as_mut(&mut self) -> &mut T { self.0.as_mut() } } impl Zeroize for Zeroizing where Z: Zeroize, { fn zeroize(&mut self) { self.0.zeroize(); } } impl ZeroizeOnDrop for Zeroizing where Z: Zeroize {} impl Drop for Zeroizing where Z: Zeroize, { fn drop(&mut self) { self.0.zeroize() } } #[cfg(feature = "serde")] impl serde::Serialize for Zeroizing where Z: Zeroize + serde::Serialize, { #[inline(always)] fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.0.serialize(serializer) } } #[cfg(feature = "serde")] impl<'de, Z> serde::Deserialize<'de> for Zeroizing where Z: Zeroize + serde::Deserialize<'de>, { #[inline(always)] fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { Ok(Self(Z::deserialize(deserializer)?)) } } /// Use fences to prevent accesses from being reordered before this /// point, which should hopefully help ensure that all accessors /// see zeroes after this point. #[inline(always)] fn atomic_fence() { atomic::compiler_fence(atomic::Ordering::SeqCst); } /// Perform a volatile write to the destination #[inline(always)] fn volatile_write(dst: &mut T, src: T) { unsafe { ptr::write_volatile(dst, src) } } /// Perform a volatile `memset` operation which fills a slice with a value /// /// Safety: /// The memory pointed to by `dst` must be a single allocated object that is valid for `count` /// contiguous elements of `T`. /// `count` must not be larger than an `isize`. /// `dst` being offset by `mem::size_of:: * count` bytes must not wrap around the address space. /// Also `dst` must be properly aligned. #[inline(always)] unsafe fn volatile_set(dst: *mut T, src: T, count: usize) { // TODO(tarcieri): use `volatile_set_memory` when stabilized for i in 0..count { // Safety: // // This is safe because there is room for at least `count` objects of type `T` in the // allocation pointed to by `dst`, because `count <= isize::MAX` and because // `dst.add(count)` must not wrap around the address space. let ptr = dst.add(i); // Safety: // // This is safe, because the pointer is valid and because `dst` is well aligned for `T` and // `ptr` is an offset of `dst` by a multiple of `mem::size_of::()` bytes. ptr::write_volatile(ptr, src); } } /// Internal module used as support for `AssertZeroizeOnDrop`. #[doc(hidden)] pub mod __internal { use super::*; /// Auto-deref workaround for deriving `ZeroizeOnDrop`. pub trait AssertZeroizeOnDrop { fn zeroize_or_on_drop(self); } impl AssertZeroizeOnDrop for &&mut T { fn zeroize_or_on_drop(self) {} } /// Auto-deref workaround for deriving `ZeroizeOnDrop`. pub trait AssertZeroize { fn zeroize_or_on_drop(&mut self); } impl AssertZeroize for T { fn zeroize_or_on_drop(&mut self) { self.zeroize() } } }