{"id":111,"date":"2025-06-19T22:34:18","date_gmt":"2025-06-20T02:34:18","guid":{"rendered":"https:\/\/brockcooper.info\/?p=111"},"modified":"2025-06-20T13:40:33","modified_gmt":"2025-06-20T17:40:33","slug":"openai-api-credential-manager","status":"publish","type":"post","link":"https:\/\/brockcooper.info\/index.php\/2025\/06\/19\/openai-api-credential-manager\/","title":{"rendered":"C# &#8211; OpenAI API Credential Management With Windows Credential  Manager"},"content":{"rendered":"\n<p>Not complete at all but maybe someone will find this useful?<\/p>\n\n\n\n<p>Aside from just validating the API key works and its valid, this also use the Windows Credential Manager with a user based &#8220;Pepper&#8221; to seed the encryption for storing of and access to the API key of the given application \/ service \ud83d\ude42<br><br>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 \ud83d\ude1b Maybe someone will find it useful.<\/p>\n\n\n\n<pre class=\"wp-block-syntaxhighlighter-code\">using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http.Headers;\nusing System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Security.Cryptography;\nusing System.Security.Principal;\nusing System.Text;\nusing System.Text.RegularExpressions;\nusing System.Threading.Tasks;\nusing System.Windows.Forms;\nusing Meziantou.Framework.Win32;\n\nnamespace Advanced_ChatGPT.CredentialManager\n{   \/\/TODO - fix broken shit\n\tpublic static class ApiKeyValidator\n\t{\n\t\tprivate static readonly HttpClient _httpClient = new HttpClient\n\t\t{\n\t\t\tBaseAddress = new Uri(\"https:\/\/api.openai.com\/\")\n\t\t};\n\n\t\tpublic static async Task&lt;bool> ValidateAsync(string apiKey)\n\t\t{\n\t\t\t_httpClient.DefaultRequestHeaders.Authorization =\n\t\t\t\tnew AuthenticationHeaderValue(\"Bearer\", apiKey);\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tusing var response = await _httpClient\n\t\t\t\t\t.GetAsync(\"v1\/models\")\n\t\t\t\t\t.ConfigureAwait(false);           \/\/ &lt;\u2014 avoids capturing sync context\n\t\t\t\treturn response.IsSuccessStatusCode;\n\t\t\t}\n\t\t\tcatch (HttpRequestException)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\tfile class ApiKeyProtector\n\t{\n\t\tprivate static byte[] Pepper\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\t\/\/ derive entropy from the current user\u2019s SID (no hard-coded string!)\n\t\t\t\tvar sidString = WindowsIdentity.GetCurrent().User!.Value;\n\t\t\t\treturn Encoding.UTF8.GetBytes(sidString);\n\t\t\t}\n\t\t}\n\n\t\t\/\/ Encrypt for the current user\n\t\tpublic static string Encrypt(string plaintext)\n\t\t{\n\t\t\tbyte[] data = Encoding.UTF8.GetBytes(plaintext);\n\t\t\tbyte[] cipher = ProtectedData.Protect(\n\t\t\t\tdata,\n\t\t\t\toptionalEntropy: Pepper,\n\t\t\t\tscope: DataProtectionScope.CurrentUser);\n\t\t\treturn Convert.ToBase64String(cipher);\n\t\t}\n\n\t\t\/\/ Decrypt for the current user\n\t\t\/\/TODO - Crashes if the creds are changed manualy to an incorect value :(\n\t\tpublic static string Decrypt(string encryptedText)\n\t\t{\n\t\t\t\/\/ 1) Turn your stored string back into the original cipher bytes\n\t\t\tbyte[] cipherBytes = Convert.FromBase64String(encryptedText);\n\n\t\t\t\/\/ 2) Unprotect with the same Pepper and scope\n\t\t\tbyte[] plainBytes = ProtectedData.Unprotect(\n\t\t\t\tcipherBytes,\n\t\t\t\toptionalEntropy: Pepper,\n\t\t\t\tscope: DataProtectionScope.CurrentUser);\n\n\t\t\treturn Encoding.UTF8.GetString(plainBytes);\n\t\t}\n\t}\n\n\tinternal class API_Key_Manager\n\t{\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Gets the name of the application.\n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;value>\n\t\t\/\/\/ The name of the application.\n\t\t\/\/\/ &lt;\/value>\n\t\tprivate static string App_Name\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\treturn Assembly\n\t\t\t\t\t.GetEntryAssembly()?\n\t\t\t\t\t.GetName()\n\t\t\t\t\t.Name ??\n\t\t\t\t\t\"ChatGPT API Key\";\n\t\t\t}\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Gets the name of the currently logged in username.\n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;value>\n\t\t\/\/\/ The name of the windows account user.\n\t\t\/\/\/ &lt;\/value>\n\t\tprivate static string User_Name\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\treturn WindowsIdentity.GetCurrent().Name;\n\t\t\t}\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Returns the static Windows Credential Manager comment.\n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;value>\n\t\t\/\/\/ The comment.\n\t\t\/\/\/ &lt;\/value>\n\t\tprivate static string Comment\n\t\t{\n\t\t\tget\n\t\t\t{\n\t\t\t\treturn \"Advanced ChatGPT Application API key. Used to authenticate with the OpenAI servers.\";\n\t\t\t}\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Saves the API key.\n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;param name=\"api_key\">The API key.&lt;\/param>\n\t\tpublic static void SaveAPIKey(string api_key)\n\t\t{\n\t\t\tMeziantou.Framework.Win32.CredentialManager.WriteCredential(\n\t\t\t\tapplicationName: App_Name,\n\t\t\t\tuserName: User_Name,\n\t\t\t\tsecret: ApiKeyProtector.Encrypt(api_key),\n\t\t\t\tcomment: Comment,\n\t\t\t\tpersistence: CredentialPersistence.LocalMachine);\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Reads the API key.\n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;returns>The API key.&lt;\/returns>\n\t\tpublic static string ReadAPIKey()\n\t\t{\n\t\t\tvar credential = ReadCredentials();\n\n\t\t\tif (credential == null)\n\t\t\t{\n\t\t\t\tConsole.WriteLine(\"No credential found.\");\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn ApiKeyProtector.Decrypt(credential.Password);\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Reads the Windows Credential Manager credentials.\n\t\t\/\/\/ Note: The API key is returned as an encrypted string.\n\t\t\/\/\/ The API key can only be decrypted within the API_Key_Manager class. \n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;returns>Win32.Credentials&lt;\/returns>\n\t\tpublic static Credential ReadCredentials()\n\t\t{\n\t\t\tvar credential = Meziantou.Framework.Win32.CredentialManager.ReadCredential(App_Name);\n\n\t\t\tif (credential == null)\n\t\t\t{\n\t\t\t\tConsole.WriteLine(\"No credential found.\");\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn credential;\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Updates the API key in Windows Credential Manager.\n\t\t\/\/\/ &lt;\/summary>\n\t\t\/\/\/ &lt;param name=\"api_key\">The API key.&lt;\/param>\n\t\tpublic static void UpdateAPIKey(string api_key)\n\t\t{\n\t\t\tSaveAPIKey(ApiKeyProtector.Encrypt(api_key));\n\t\t}\n\n\t\t\/\/\/ &lt;summary>\n\t\t\/\/\/ Deletes the API key from Windows Credential Manager.\n\t\t\/\/\/ &lt;\/summary>\n\t\tpublic static void DeleteAPIKey ()\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tMeziantou.Framework.Win32.CredentialManager.DeleteCredential(App_Name);\n\t\t\t\tConsole.WriteLine(\"Credential deleted successfully.\");\n\t\t\t}\n\t\t\tcatch (Exception ex)\n\t\t\t{\n\t\t\t\tConsole.WriteLine($\"Error deleting credential: {ex.Message}\");\n\t\t\t}\n\t\t}\n\t}\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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&#46;&#46;&#46;<\/p>\n","protected":false},"author":1,"featured_media":117,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[11,12,9,14,13,8],"class_list":["post-111","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c","tag-api-key","tag-c","tag-chatgpt","tag-credentialmanager","tag-csharp","tag-security"],"_links":{"self":[{"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/posts\/111","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/comments?post=111"}],"version-history":[{"count":13,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/posts\/111\/revisions"}],"predecessor-version":[{"id":143,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/posts\/111\/revisions\/143"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/media\/117"}],"wp:attachment":[{"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/media?parent=111"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/categories?post=111"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/brockcooper.info\/index.php\/wp-json\/wp\/v2\/tags?post=111"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}