はじめに
業務でWindows資格情報の読み書きを調べたので(結局一切使いませんでしたが...)、忘れないうちに備忘録として残します。
全体のコード
まず、今回作った全体のコードです。
資格情報名と秘密情報を資格情報マネージャーに登録し、それを読み取ります。
using System;
using System.Runtime.InteropServices;
using System.Text;
class Program
{
static void Main()
{
string targetName = "MyApp_HMAC_Key"; // 資格情報のなまえ
string secret = "super-secret-key-123"; // 秘密情報の値
try
{
SaveCredential(targetName, secret);
string? loaded = LoadCredential(targetName);
Console.WriteLine("Loaded: " + loaded);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private enum CredentialPersistence : uint
{
Session = 1,
LocalMachine,
Enterprise
}
private enum CredentialType : uint
{
Generic = 1,
DomainPassword,
DomainCertificate,
DomainVisiblePassword,
GenericCertificate,
DomainExtended,
Maximum,
MaximumEx = Maximum + 1000,
}
// 資格情報を登録
public static void SaveCredential(string target, string secret)
{
var bytes = Encoding.Unicode.GetBytes(secret);
if (bytes.Length > 2560)
throw new ArgumentOutOfRangeException(nameof(secret), "secretの長さが2560バイトを超えてます。");
IntPtr targetPtr = Marshal.StringToCoTaskMemUni(target);
IntPtr secretPtr = Marshal.StringToCoTaskMemUni(secret);
try
{
var credential = new CREDENTIAL
{
Type = (uint)CredentialType.Generic,
TargetName = targetPtr,
CredentialBlob = secretPtr,
CredentialBlobSize = (uint)(secret.Length * 2),
Persist = (uint)CredentialPersistence.LocalMachine,
AttributeCount = 0,
Attributes = IntPtr.Zero,
Comment = IntPtr.Zero,
TargetAlias = IntPtr.Zero,
UserName = IntPtr.Zero
};
if (!CredWrite(ref credential, 0))
throw new Exception($"CredWrite failed with the error code {Marshal.GetLastWin32Error()}.");
}
finally
{
Marshal.FreeCoTaskMem(targetPtr);
Marshal.FreeCoTaskMem(secretPtr);
}
}
// 資格情報を読み取る
public static string? LoadCredential(string target)
{
if (!CredRead(target, (int)CredentialType.Generic, 0, out IntPtr credPtr))
return null;
try
{
var cred = Marshal.PtrToStructure<CREDENTIAL>(credPtr);
if (cred.CredentialBlob == IntPtr.Zero || cred.CredentialBlobSize == 0)
return null;
return Marshal.PtrToStringUni(cred.CredentialBlob, (int)cred.CredentialBlobSize / 2);
}
finally
{
CredFree(credPtr);
}
}
// ネイティブAPI呼び出しの宣言部
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool CredWrite([In] ref CREDENTIAL Credential, [In] uint Flags);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool CredRead(string TargetName, int Type, int Flags, out IntPtr Credential);
[DllImport("advapi32.dll", SetLastError = true)]
static extern void CredFree([In] IntPtr Buffer);
// CREDENTIAL構造体
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct CREDENTIAL
{
public uint Flags;
public uint Type;
public IntPtr TargetName;
public IntPtr Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public uint CredentialBlobSize;
public IntPtr CredentialBlob;
public uint Persist;
public uint AttributeCount;
public IntPtr Attributes;
public IntPtr TargetAlias;
public IntPtr UserName;
}
}
登録
CREDENTIAL構造体に登録したいデータ(ターゲット名や秘密情報など)を詰めて、CredWriteW関数に渡して呼び出し、資格情報マネージャーへ登録をします。
最後にメモリはFreeCoTaskMem
で解放しています。
public static void SaveCredential(string target, string secret)
{
var bytes = Encoding.Unicode.GetBytes(secret);
if (bytes.Length > 2560)
throw new ArgumentOutOfRangeException(nameof(secret), "secretの長さが2560バイトを超えてます。");
IntPtr targetPtr = Marshal.StringToCoTaskMemUni(target);
IntPtr secretPtr = Marshal.StringToCoTaskMemUni(secret);
try
{
var credential = new CREDENTIAL
{
Type = (uint)CredentialType.Generic,
TargetName = targetPtr,
CredentialBlob = secretPtr,
CredentialBlobSize = (uint)(secret.Length * 2),
Persist = (uint)CredentialPersistence.LocalMachine,
AttributeCount = 0,
Attributes = IntPtr.Zero,
Comment = IntPtr.Zero,
TargetAlias = IntPtr.Zero,
UserName = IntPtr.Zero
};
if (!CredWrite(ref credential, 0))
throw new Exception($"CredWrite failed with the error code {Marshal.GetLastWin32Error()}.");
}
finally
{
Marshal.FreeCoTaskMem(targetPtr);
Marshal.FreeCoTaskMem(secretPtr);
}
}
読み取り
CredRead関数にターゲット名や種類を指定して呼び出します。
成功すると、メモリ上に資格情報が格納されたポインターを取得できるので、CREDENTIAL 構造体に変換して内容を読み取ります。
確保したメモリはCredFree関数で解放する必要があります。
public static string? LoadCredential(string target)
{
if (!CredRead(target, (int)CredentialType.Generic, 0, out IntPtr credPtr))
return null;
try
{
var cred = Marshal.PtrToStructure<CREDENTIAL>(credPtr);
if (cred.CredentialBlob == IntPtr.Zero || cred.CredentialBlobSize == 0)
return null;
return Marshal.PtrToStringUni(cred.CredentialBlob, (int)cred.CredentialBlobSize / 2);
}
finally
{
CredFree(credPtr);
}
}
参考にしたサイト
https://gist.github.com/meziantou/10311113
↑どれも参考になり大変ありがたかったです。