A .NET library for storing and retrieving credentials from the Windows Credential Store.
Wraps the native CredWrite, CredRead, CredEnumerate, and CredDelete APIs via P/Invoke,
providing a safe managed interface for credential management in desktop applications, CLI tools,
and background services.
- Save, retrieve, enumerate, and remove credentials from Windows Credential Store
- Prompt users for credentials via Windows UI dialog or console
- Store comments and custom attributes (JSON-serialized, up to 64 per credential)
- Configurable persistence (Session, LocalMachine, Enterprise)
- JIT-safe memory zeroing of credential buffers via
RtlZeroMemoryP/Invoke - Full nullable reference type annotations
- Source Link enabled for debugger integration
| Framework | Purpose |
|---|---|
| .NET 8.0 | Primary target, full analyzer coverage |
| .NET Standard 2.0 | Broad compatibility (.NET Framework 4.6.1+, .NET Core 2.0+, Mono, Unity) |
dotnet add package AdysTech.CredentialManager
Or in your .csproj:
<PackageReference Include="AdysTech.CredentialManager" Version="3.1.0" />For local development or when building from source:
<ProjectReference Include="path/to/src/AdysTech.CredentialManager/AdysTech.CredentialManager.csproj" />using System.Net;
using AdysTech.CredentialManager;
// Save
var cred = new NetworkCredential("user", "password", "domain");
CredentialManager.SaveCredentials("MyApp:api-token", cred);
// Retrieve
var stored = CredentialManager.GetCredentials("MyApp:api-token");
if (stored != null)
Console.WriteLine($"User: {stored.UserName}, Domain: {stored.Domain}");
// Remove
CredentialManager.RemoveCredentials("MyApp:api-token");-
Install the NuGet package (see Installation above).
-
Add the using directive:
using AdysTech.CredentialManager;
-
Use
CredentialManageras a static class — no instantiation needed.
Use a consistent prefix for your target names to avoid collisions with other applications:
const string TargetPrefix = "MyApp:";
CredentialManager.SaveCredentials($"{TargetPrefix}api-key", cred);
CredentialManager.SaveCredentials($"{TargetPrefix}oauth-token", cred);public static class SecureStore
{
private const string Prefix = "MyApp:";
public static void StoreToken(string key, string token, string user = "token")
{
var cred = new NetworkCredential(user, token);
CredentialManager.SaveCredentials($"{Prefix}{key}", cred,
persistence: Persistence.LocalMachine);
}
public static string? GetToken(string key)
{
return CredentialManager.GetCredentials($"{Prefix}{key}")?.Password;
}
public static void RemoveToken(string key)
{
try { CredentialManager.RemoveCredentials($"{Prefix}{key}"); }
catch (CredentialAPIException) { /* not found — already removed */ }
}
}All public methods throw ArgumentNullException for null parameters and CredentialAPIException
for Windows API failures. GetCredentials and GetICredential return null when the target
is not found (not an error).
try
{
CredentialManager.SaveCredentials("target", cred);
}
catch (CredentialAPIException ex)
{
// ex.Message — human-readable description
// ex.ApiName — Win32 function that failed (e.g. "CredWrite")
// ex.ErrorCode — Win32 error code
Console.Error.WriteLine($"{ex.ApiName} failed: {ex.Message} (error {ex.ErrorCode})");
}This library is Windows-only (P/Invoke to advapi32.dll and credui.dll). For cross-platform
credential storage, use it behind an abstraction:
public interface ICredentialStore
{
void Save(string key, string user, string password);
NetworkCredential? Get(string key);
void Remove(string key);
}Implement with CredentialManager on Windows and an alternative (e.g., libsecret/keyring)
on Linux/macOS.
| Method | Description |
|---|---|
SaveCredentials(target, credential, ...) |
Save a NetworkCredential to the store. Returns ICredential on success. |
GetCredentials(target, type) |
Retrieve as NetworkCredential. Returns null if not found. |
GetICredential(target, type) |
Retrieve as ICredential (includes comment, attributes, persistence). |
EnumerateCredentials(target?) |
List all credentials, optionally filtered by target prefix. |
EnumerateICredentials(target?) |
Same as above, returning ICredential objects. |
RemoveCredentials(target, type) |
Delete a credential from the store. |
PromptForCredentials(target, ...) |
Show the Windows credential dialog. |
PromptForCredentialsConsole(target) |
Console-based credential prompt. |
GetBasicAuthString(credential) |
Extension method: Base64-encode user:password for HTTP Basic Auth. |
ICredential exposes properties beyond NetworkCredential:
| Property | Type | Description |
|---|---|---|
TargetName |
string |
The target identifier |
UserName |
string? |
Username |
CredentialBlob |
string? |
Password or token |
Comment |
string? |
Comment (visible via API only, not in Windows UI) |
Persistence |
Persistence |
Storage scope |
Type |
CredentialType |
Generic or Windows (domain) |
Attributes |
IDictionary<string, Object>? |
JSON-serialized key-value pairs (max 64, 256 bytes each) |
LastWritten |
DateTime |
Last modification timestamp |
| Value | Behavior |
|---|---|
Session |
Exists only for the current logon session, cleared on reboot |
LocalMachine |
(Default) Persisted locally, not synced to domain controllers |
Enterprise |
Persisted and synced to Active Directory domain controllers |
Store structured metadata alongside credentials using JSON-serialized attributes:
var cred = (new NetworkCredential("user", "token")).ToICredential()!;
cred.TargetName = "MyApp:service";
cred.Attributes = new Dictionary<string, Object>(StringComparer.Ordinal)
{
{ "role", "admin" },
{ "expiresAt", DateTime.UtcNow.AddHours(1).ToString("o") },
{ "retryCount", 3 }
};
cred.SaveCredential();
// Reading back — attributes are returned as JsonElement
var stored = CredentialManager.GetICredential("MyApp:service");
var role = ((JsonElement)stored!.Attributes!["role"]).GetString();
var count = ((JsonElement)stored.Attributes["retryCount"]).GetInt32();Constraints: each attribute value must serialize to 256 bytes or less, maximum 64 attributes per credential, attribute keys maximum 256 characters.
Legacy BinaryFormatter-encoded attributes can no longer be read. The BinaryFormatter compatibility layer has been removed entirely. Legacy attributes are silently skipped with a debug message. Re-save credentials to convert them to JSON format.
Attribute values are now JsonElement objects instead of the original .NET types:
// v2.x
var role = (string)cred.Attributes["role"];
// v3.x
var role = ((JsonElement)cred.Attributes["role"]).GetString();
// Complex types:
var info = ((JsonElement)cred.Attributes["info"]).Deserialize<MyType>();The misspelled Persistance enum, properties, and parameters have been corrected to
Persistence. Update all references accordingly.
The default persistence changed from Enterprise to LocalMachine. If your application
relies on domain-replicated credentials, explicitly pass Persistence.Enterprise.
- No BinaryFormatter — attribute serialization uses
System.Text.Jsonexclusively. BinaryFormatter was removed due to its critical deserialization vulnerability (CWE-502). - JIT-safe memory zeroing — native credential buffers are zeroed using
RtlZeroMemoryvia P/Invoke before being freed. The JIT cannot optimize away an external function call, unlikeMarshal.Copywith zero bytes. - Configurable persistence — credentials default to
LocalMachine(notEnterprise), preventing unintended replication across domain controllers. - Correct buffer sizes — credential UI prompts use the Windows-defined constants
CREDUI_MAX_USERNAME_LENGTH(513) andCREDUI_MAX_PASSWORD_LENGTH(256). - Static analysis — the library builds with zero warnings across 6 Roslyn analyzer suites: Microsoft.CodeAnalysis.NetAnalyzers, StyleCop, SecurityCodeScan, Roslynator, SonarAnalyzer, and Meziantou.Analyzer (~2,000+ combined rules).
Note:
CredentialBlobis a managedstring, which the GC may copy or retain in memory. For maximum security, keep credential objects short-lived and avoid caching password values.
dotnet build -c Release
dotnet test -c Release
The project requires Windows for testing (credential store access via interactive logon session).
MIT — Copyright (c) 2016-2026 Adys Tech
C# wrapper around CredWrite / CredRead functions to store and retrieve from Windows Credential Store. Windows OS comes equipped with a very secure robust Credential Manager from Windows XP onwards, and good set of APIs to interact with it. However .NET Framework did not provide any standard way to interact with this vault.
Microsoft Peer Channel blog (WCF team) has written a blog post in 2005 which provided basic structure of using the Win32 APIs for credential management in .NET.
I used their code, and improved up on it to add PromptForCredentials function to display a dialog to get the credentials from user.
I left the project stale for few years, and an amazing contributor @shakeyourbunny helped me to modernize the framework and fix few security audit findings. Huge thanks!