3. SecureData & Memory Protection
🧠 SecureData & Memory Protection
So you've got passwords, keys, tokens — stuff you really don't want just sitting around in memory in plain sight. That's what SecureData (in the WISecureData namespace) is for. It's the thing that finally replaces the old SecureString I was leaning on, which had a dumb length limit and generally made me sad.
Basically every function in Pariah that wants a password or key (../7. Walker.Crypto/Walker.Crypto, ../4. DataHandler/Functions/3. DataHandler.SaltAndHashing, ../4. DataHandler/Functions/6. DataHandler.Accounts, ../4. DataHandler/Functions/7. DataHandler.AccountsWithSessions, ../4. DataHandler/Functions/5. DataHandler.SecretManager, etc.) takes a SecureData, so you get all of this for free without having to think about it.
Quick myth-buster while we're here, since people always ask "why not just use SecureString?": Microsoft officially deprecated SecureString and says don't use it in new code. Turns out it only ever encrypted your secret while it sat in memory, and handed you back plaintext the second you actually needed to use it. Which is... exactly what SecureData does — except SecureData has no length limit, works on Linux/Mac too, page-locks the buffer, and wipes it. So you're not missing some magic SecureString had. This IS that, done better. Their own recommended replacement is "use a byte/char array and wipe it fast," which is literally what's going on under the hood here.
✍️ How To Use It
using WISecureData;
// From a string (the original string gets wiped where possible)
SecureData password = "hunter2".ToSecureData();
// or if you enjoy typing more
SecureData key = SecureData.FromString(myString);
// or straight from bytes (the byte[] you hand in gets wiped once it's stored)
SecureData raw = new SecureData(myBytes);
// Pull it back out when you actually need it — do NOT hold onto the result
string plain = password.ConvertToString();
byte[] bytes = password.ConvertToBytes();
// Comparing is constant-time so you don't leak timing info
if (passwordA == passwordB) { /* ... */ }
// Wipe it the second you're done, auto done if you don't
password.Dispose();
You don't configure anything. It just does the stuff below on its own.
🛡️ What It Actually Does
Zero effort on your end:
- It's AES-256 encrypted while it's just sitting there. The bytes stay encrypted the whole time you're not touching them. They only get decrypted into a tiny throwaway buffer for the split second you call ConvertToString/ConvertToBytes/compare, and that buffer gets wiped right after. So if someone fires up Cheat Engine and scans for your password's value, it isn't sitting inside the SecureData for them to find.
- It's pinned and page-locked. The GC can't shuffle it around leaving copies everywhere, and the OS can't page it out to the swap/pagefile (VirtualLock on Windows, mlock on Linux/Mac). So your password doesn't end up chilling on the hard drive somewhere.
- It gets wiped. On Dispose() — and as a backup, whenever the last copy gets garbage collected — it's zeroed out with a method the compiler isn't allowed to "helpfully optimize away."
"Wait, if I read it twice does it vanish?"
Nope, you're fine. Reading is non-destructive — call ConvertToString/ConvertToBytes as many times as you want, from as many copies of the struct as you want, and you'll get your value back every single time. The ONLY thing that empties it is Dispose(). So it's fully backwards compatible, nothing you already wrote breaks. (One gotcha: the raw byte[] you pass into new SecureData(bytes) gets wiped, since it now lives inside the SecureData — so don't go reusing that array afterwards.)
The one actual weak spot
ConvertToString() hands you a normal .NET string. These can't be reliably wiped (immutable) and dump tools list every string by type.
But here's the important part: most of the sensitive operations never make a plaintext copy at all.
- Password checks use
SecureCompare— constant-time, straight on the decrypted bytes, no plaintext string ever created. - Encrypt / decrypt / derive work on the bytes in place.
So for anything Pariah does internally, there's no plaintext string sitting around for a hook to grab. The only place the plaintext genuinely has to exist in the open is when you hand it to code outside our control — a UI textbox, a network API that wants a string. That residual spot is a lot smaller than "every time you touch a secret."
Good news — the soft spot is narrower than it sounds
Here's the part people miss: most of the sensitive operations never make a plaintext copy at all.
- Password checks use
SecureCompare— constant-time, straight on the decrypted bytes, no plaintext string ever created. - Encrypt / decrypt / derive work on the bytes in place.
So for anything Pariah does internally, there's no plaintext string sitting around for a hook to grab. The only place the plaintext genuinely has to exist in the open is when you hand it to code outside our control — a UI textbox, a network API that wants a string. That residual spot is a lot smaller than "every time you touch a secret."
And two things worth keeping in mind about the whole design:
- Reverse-engineering the DLL doesn't break it. The security lives in the keys, not in the code being secret. Someone can decompile the entire thing, know exactly how the crypto works, and still get nothing without the key — that's how real crypto is supposed to work, and obfuscation is just a bonus speed-bump, not load-bearing.
- Without the key, stolen data is noise. Take every encrypted file and cache blob you want; AES-256-GCM without the key is useless — and with
HardwareKeyStore, the key can't even be lifted off the machine.