C# – OpenAI API Credential Management With Windows Credential Manager

June 19, 2025
Share

Not complete at all but maybe someone will find this useful?

Aside from just validating the API key works and its valid, this also use the Windows Credential Manager with a user based “Pepper” to seed the encryption for storing of and access to the API key of the given application / service πŸ™‚

Please note this is not final and there it a lot to correct and finish such as error handling and correction as well as better structure but it works for now πŸ˜› Maybe someone will find it useful.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using Meziantou.Framework.Win32;

namespace Advanced_ChatGPT.CredentialManager
{   //TODO - fix broken shit
	public static class ApiKeyValidator
	{
		private static readonly HttpClient _httpClient = new HttpClient
		{
			BaseAddress = new Uri("https://api.openai.com/")
		};

		public static async Task<bool> ValidateAsync(string apiKey)
		{
			_httpClient.DefaultRequestHeaders.Authorization =
				new AuthenticationHeaderValue("Bearer", apiKey);

			try
			{
				using var response = await _httpClient
					.GetAsync("v1/models")
					.ConfigureAwait(false);           // <β€” avoids capturing sync context
				return response.IsSuccessStatusCode;
			}
			catch (HttpRequestException)
			{
				return false;
			}
		}
	}

	file class ApiKeyProtector
	{
		private static byte[] Pepper
		{
			get
			{
				// derive entropy from the current user’s SID (no hard-coded string!)
				var sidString = WindowsIdentity.GetCurrent().User!.Value;
				return Encoding.UTF8.GetBytes(sidString);
			}
		}

		// Encrypt for the current user
		public static string Encrypt(string plaintext)
		{
			byte[] data = Encoding.UTF8.GetBytes(plaintext);
			byte[] cipher = ProtectedData.Protect(
				data,
				optionalEntropy: Pepper,
				scope: DataProtectionScope.CurrentUser);
			return Convert.ToBase64String(cipher);
		}

		// Decrypt for the current user
		//TODO - Crashes if the creds are changed manualy to an incorect value :(
		public static string Decrypt(string encryptedText)
		{
			// 1) Turn your stored string back into the original cipher bytes
			byte[] cipherBytes = Convert.FromBase64String(encryptedText);

			// 2) Unprotect with the same Pepper and scope
			byte[] plainBytes = ProtectedData.Unprotect(
				cipherBytes,
				optionalEntropy: Pepper,
				scope: DataProtectionScope.CurrentUser);

			return Encoding.UTF8.GetString(plainBytes);
		}
	}

	internal class API_Key_Manager
	{
		/// <summary>
		/// Gets the name of the application.
		/// </summary>
		/// <value>
		/// The name of the application.
		/// </value>
		private static string App_Name
		{
			get
			{
				return Assembly
					.GetEntryAssembly()?
					.GetName()
					.Name ??
					"ChatGPT API Key";
			}
		}

		/// <summary>
		/// Gets the name of the currently logged in username.
		/// </summary>
		/// <value>
		/// The name of the windows account user.
		/// </value>
		private static string User_Name
		{
			get
			{
				return WindowsIdentity.GetCurrent().Name;
			}
		}

		/// <summary>
		/// Returns the static Windows Credential Manager comment.
		/// </summary>
		/// <value>
		/// The comment.
		/// </value>
		private static string Comment
		{
			get
			{
				return "Advanced ChatGPT Application API key. Used to authenticate with the OpenAI servers.";
			}
		}

		/// <summary>
		/// Saves the API key.
		/// </summary>
		/// <param name="api_key">The API key.</param>
		public static void SaveAPIKey(string api_key)
		{
			Meziantou.Framework.Win32.CredentialManager.WriteCredential(
				applicationName: App_Name,
				userName: User_Name,
				secret: ApiKeyProtector.Encrypt(api_key),
				comment: Comment,
				persistence: CredentialPersistence.LocalMachine);
		}

		/// <summary>
		/// Reads the API key.
		/// </summary>
		/// <returns>The API key.</returns>
		public static string ReadAPIKey()
		{
			var credential = ReadCredentials();

			if (credential == null)
			{
				Console.WriteLine("No credential found.");
				return null;
			}

			return ApiKeyProtector.Decrypt(credential.Password);
		}

		/// <summary>
		/// Reads the Windows Credential Manager credentials.
		/// Note: The API key is returned as an encrypted string.
		/// The API key can only be decrypted within the API_Key_Manager class. 
		/// </summary>
		/// <returns>Win32.Credentials</returns>
		public static Credential ReadCredentials()
		{
			var credential = Meziantou.Framework.Win32.CredentialManager.ReadCredential(App_Name);

			if (credential == null)
			{
				Console.WriteLine("No credential found.");
				return null;
			}

			return credential;
		}

		/// <summary>
		/// Updates the API key in Windows Credential Manager.
		/// </summary>
		/// <param name="api_key">The API key.</param>
		public static void UpdateAPIKey(string api_key)
		{
			SaveAPIKey(ApiKeyProtector.Encrypt(api_key));
		}

		/// <summary>
		/// Deletes the API key from Windows Credential Manager.
		/// </summary>
		public static void DeleteAPIKey ()
		{
			try
			{
				Meziantou.Framework.Win32.CredentialManager.DeleteCredential(App_Name);
				Console.WriteLine("Credential deleted successfully.");
			}
			catch (Exception ex)
			{
				Console.WriteLine($"Error deleting credential: {ex.Message}");
			}
		}
	}
}