Erasing Memory in a Secure Way
Overwriting a secret as soon as it is no longer needed reduces the time that plaintext remains in process memory. Erbsland Core provides explicit erasure for borrowed memory, fixed arrays, shared byte blocks, dynamic buffers, and ring buffers. This page shows how to choose the matching operation and, just as importantly, how ownership and copies affect what an erase can reach.
Why and When to Erase Memory
An ordinary assignment to zero is not a reliable security operation.
A compiler may prove that the bytes are never read again and remove the assignment, while a container may retain old
bytes in unused capacity or in an allocation abandoned during growth.
secureErase() uses a platform-backed operation that is kept even when the
erased storage is no longer observed by normal program logic.
Secure erasure is useful for passwords, private keys, session material, authentication tokens, and temporary cryptographic state after their final use. Apply it at the earliest point where the value is no longer needed, and protect dynamic containers throughout their lifetime when they can reallocate or discard ranges. For the allocation-level protection available to strings and byte containers, also read About Sensitive Strings and Byte Blocks.
Important
Erasure is defense in depth, not a promise that no copy exists. Values may also have lived in registers, stack temporaries, crash dumps, swap, operating-system buffers, or third-party storage. Avoid unnecessary copies and use higher-level protected storage when the threat model requires stronger isolation.
Fixed Arrays and Borrowed Spans
A ByteArray owns a fixed number of bytes directly and provides
ByteArray::secureErase() for the complete array.
This operation is explicit: the array does not remember a sensitive mode and will not erase itself automatically on
scope exit.
Arrange control flow so cleanup also happens on early returns and exceptions when those paths are possible.
For writable memory owned by another object, pass a ByteSpan to the free
secureErase() function.
The span is only a view, so the operation overwrites exactly the referenced range and does not manage the owner’s
lifetime.
Make sure the span covers every byte that may contain the value, including relevant scratch capacity rather than only a
short logical prefix.
The overload for writable std::span values can similarly erase trivially copyable native word arrays used as
mathematical state.
/// Securely erase fixed byte storage and a borrowed writable span.
///
/// `ByteArray::secureErase()` handles the complete array. The free
/// `secureErase()` function applies the same platform-backed operation to any
/// writable `ByteSpan` without taking ownership of its storage.
void eraseFixedStorage() {
auto authenticationTag = el::ByteArray{
el::Byte{0x41U},
el::Byte{0x70U},
el::Byte{0x6fU},
el::Byte{0x66U},
el::Byte{0x69U},
el::Byte{0x73U},
};
auto decoderScratch = std::array{
el::Byte{0x53U},
el::Byte{0x70U},
el::Byte{0x65U},
el::Byte{0x6bU},
el::Byte{0x74U},
el::Byte{0x72U},
};
// Erase owned fixed storage through its member function.
authenticationTag.secureErase();
const auto tagIsZero = authenticationTag == el::ByteArray<6>{};
// Erase caller-owned storage through a writable borrowed span.
el::mem::secureErase(el::ByteSpan{decoderScratch});
const auto scratchIsZero = decoderScratch == std::array<el::Byte, 6>{};
el::io::printLine("Observation : Apofis-spektrum"_el);
el::io::printLine("Tag erased : "_el, el::BooleanFormat::yesNo(), tagIsZero);
el::io::printLine("Scratch erased : "_el, el::BooleanFormat::yesNo(), scratchIsZero);
}
Observation : Apofis-spektrum
Tag erased : yes
Scratch erased : yes
Uniquely Owned Dynamic Buffers
ByteBuffer owns its allocation without copy-on-write sharing.
Calling ByteBuffer::secureErase() overwrites the complete capacity
immediately, including bytes beyond the visible length, and preserves the buffer’s length and capacity for reuse.
For a buffer that remains in use across several operations, call
ByteBuffer::setSensitive() before placing secrets in it.
Sensitive mode also erases removed ranges and replaced allocations during resizing, clearing, and growth.
An explicit secureErase() is still useful at a known lifecycle boundary because it overwrites the current allocation
without waiting for destruction.
Disabling sensitive mode is intentionally destructive: it securely erases the allocation, discards all visible bytes,
and retains the capacity.
/// Protect and explicitly erase a uniquely owned dynamic byte buffer.
///
/// Sensitive mode erases discarded ranges and replaced allocations throughout
/// the buffer's lifetime. `secureErase()` immediately overwrites its complete
/// capacity while preserving length, capacity, and sensitive mode.
void eraseDynamicBuffer() {
auto sessionMaterial = el::ByteBuffer{el::ByteLength{6U}};
sessionMaterial.setSensitive(true);
sessionMaterial.set(el::ByteIndex{0U}, el::Byte{0x4eU});
sessionMaterial.set(el::ByteIndex{1U}, el::Byte{0x45U});
sessionMaterial.set(el::ByteIndex{2U}, el::Byte{0x4fU});
sessionMaterial.set(el::ByteIndex{3U}, el::Byte{0x2dU});
sessionMaterial.set(el::ByteIndex{4U}, el::Byte{0x31U});
sessionMaterial.set(el::ByteIndex{5U}, el::Byte{0x37U});
sessionMaterial.reserve(el::ByteLength{32U});
const auto lengthBeforeErase = sessionMaterial.length();
const auto capacityBeforeErase = sessionMaterial.capacity();
// Overwrite visible bytes and unused capacity as soon as the session ends.
sessionMaterial.secureErase();
const auto zeros = el::ByteArray<6>{};
el::io::printLine("Session : NEO-17"_el);
el::io::printLine(
"Buffer erased : "_el, el::BooleanFormat::yesNo(), sessionMaterial.isEqualConstTime(zeros.span()));
el::io::printLine(
"Length preserved : "_el, el::BooleanFormat::yesNo(), sessionMaterial.length() == lengthBeforeErase);
el::io::printLine(
"Capacity preserved : "_el, el::BooleanFormat::yesNo(), sessionMaterial.capacity() == capacityBeforeErase);
el::io::printLine("Sensitive mode : "_el, el::BooleanFormat::yesNo(), sessionMaterial.isSensitive());
}
Session : NEO-17
Buffer erased : yes
Length preserved : yes
Capacity preserved : yes
Sensitive mode : yes
Queues That Discard Data Over Time
RingBuffer and its integer-aware subclass
ByteRingBuffer reuse storage as bytes are read and overwritten.
Enable RingBuffer::setSensitive() before queueing secrets so
consumed bytes, discarded data, old growth allocations, and the final allocation are securely erased at the appropriate
lifecycle points.
Calling RingBuffer::secureErase() provides an immediate session
boundary: it overwrites the complete allocation and empties the queue while preserving its capacity and sensitive mode.
The operation must not overlap an active unsafe-access lease; violating that exclusive-access rule terminates the
program.
Ordinary safe reads and writes need no special coordination.
/// Securely discard queued bytes from ring buffers.
///
/// Sensitive mode erases bytes as they are consumed or discarded. An explicit
/// `secureErase()` overwrites the complete ring allocation immediately and
/// leaves the buffer empty without changing its capacity.
void eraseRingBuffer() {
auto records = el::ByteRingBuffer{el::ByteLength{8U}};
records.setSensitive(true);
const auto writeResult = records.writeInteger<uint32_t>(0x4e454f31U);
const auto capacityBeforeErase = records.capacity();
const auto lengthBeforeErase = records.length();
// End the protocol session by erasing all queued and unused storage.
records.secureErase();
el::io::printLine("Kø : Asteroide-poster"_el);
el::io::printLine("Record accepted : "_el, el::BooleanFormat::yesNo(), writeResult.isSuccessful());
el::io::printLine("Bytes before erase: "_el, lengthBeforeErase.toSizeT());
el::io::printLine("Queue empty : "_el, el::BooleanFormat::yesNo(), records.isEmpty());
el::io::printLine("Capacity preserved: "_el, el::BooleanFormat::yesNo(), records.capacity() == capacityBeforeErase);
el::io::printLine("Sensitive mode : "_el, el::BooleanFormat::yesNo(), records.isSensitive());
}
Kø : Asteroide-poster
Record accepted : yes
Bytes before erase: 4
Queue empty : yes
Capacity preserved: yes
Sensitive mode : yes
Choosing the Boundary
The most reliable design starts with the owner of the sensitive bytes. Use an owning container with sensitive lifecycle behavior for values that grow, move, or remain alive across several operations. Use direct erasure for fixed scratch state or for a borrowed region whose owner cannot provide that behavior.
Whichever type you choose, erase the complete relevant allocation, keep aliases visible in the design, and avoid creating ordinary converted or formatted copies. Erasure then becomes a deliberate boundary around the data you actually control rather than a last-minute attempt to recover copies that have already escaped.