3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#【Windows資格情報マネージャー】に資格情報を登録/読み取りする

Posted at

はじめに

業務で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);
        }
    }

参考にしたサイト

[C#]Windows資格情報を操作する

https://gist.github.com/meziantou/10311113

↑どれも参考になり大変ありがたかったです。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?