🔐

TPMを使った鍵の暗号化をRustで実装してみた(Windows API)

に公開
2026/06/25

Windows 上で windows-sys を使用して、TPMベースの暗号化を実装しました。
(Linux 版: TPMでの鍵の暗号化をRustで実装してみた(Linux/仮想・実機対応))

最初は tss-esapi を使った鍵ラップを試みましたが、これは主に Linux 向けに設計されていて、Windows で使用するにはより複雑なセットアップが必要だったため、 windows-sys を採用しました。

この記事では、その実装コードと、各関数についての説明などを公式ドキュメントを用いて解説しています!

Cargo.toml

[dependencies]
getrandom = "0.4"
windows-sys = { version = "0.61", features = [
    "Win32_Security_Cryptography",
    "Win32_Foundation",
] }
zeroize = "1.8"

実装

このライブラリは FFI を使用しているため unsafe で囲う必要があります。

use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr};

use windows_sys::{
    core::HRESULT,
    Win32::{
        Foundation::NTE_BAD_KEYSET, 
        Security::Cryptography::*,
    },
};
use zeroize::Zeroizing;

type TpmResult<T> = Result<T, HRESULT>;

const KEY_NAME: &str = "RSA_KEY";

fn to_utf16(s: &str) -> Vec<u16> {
    let mut utf16: Vec<u16> = OsStr::new(s).encode_wide().collect();
    utf16.push(0);

    utf16
}

struct KeyWrapper {
    hprov: NCRYPT_PROV_HANDLE,
    hkey: NCRYPT_KEY_HANDLE,
}

impl Drop for KeyWrapper {
    fn drop(&mut self) {
        // 取得したプロバイダー・キーハンドルがある場合は解放する
        unsafe {
            if self.hkey != 0 {
                NCryptFreeObject(self.hkey);
            }

            if self.hprov != 0 {
                NCryptFreeObject(self.hprov);
            }
        }
    }
}

// statusはi32(HRESULT)値で、
// 0の場合は関数が正常に実行されたことを意味します。

// 対象の鍵は、推奨されている32バイト(AES-256)を使用しています。

impl KeyWrapper {
    fn open(key_name: &str) -> TpmResult<Self> {
        let hprov: NCRYPT_PROV_HANDLE = Self::open_provider()?;
        let key_name_utf16 = to_utf16(key_name);

        let hkey = match Self::retrieve_key(hprov, key_name_utf16) {
            Ok(h) => h,
            Err(e) => {
                unsafe {
                    NCryptFreeObject(hprov);
                }
                return Err(e);
            }
        };

        Ok(Self { hprov, hkey })
    }

    fn open_provider() -> TpmResult<NCRYPT_PROV_HANDLE> {
        let mut hprov: NCRYPT_PROV_HANDLE = 0;

        let status =
            unsafe { NCryptOpenStorageProvider(&mut hprov, MS_PLATFORM_CRYPTO_PROVIDER, 0) };
        if status != 0 {
            eprintln!("プロバイダーの取得に失敗しました");
            return Err(status);
        }

        Ok(hprov)
    }

    fn retrieve_key(hprov: NCRYPT_PROV_HANDLE, key_name: Vec<u16>) -> TpmResult<NCRYPT_KEY_HANDLE> {
        let mut hkey: NCRYPT_KEY_HANDLE = 0;

        let status = unsafe { NCryptOpenKey(hprov, &mut hkey, key_name.as_ptr(), 0, 0) };
        match status {
            0 => Ok(hkey),
            NTE_BAD_KEYSET => {
                // 指定した名前の鍵が存在しない場合
                println!("新しくRSAキーを作成します");
                Self::create_rsa_key(hprov, key_name)
            }
            _ => {
                eprintln!("鍵の取得に失敗しました");
                Err(status)
            }
        }
    }

    fn create_padding_info() -> BCRYPT_OAEP_PADDING_INFO {
        BCRYPT_OAEP_PADDING_INFO {
            pszAlgId: BCRYPT_SHA256_ALGORITHM,
            pbLabel: ptr::null_mut(),
            cbLabel: 0,
        }
    }

    fn create_rsa_key(
        hprov: NCRYPT_PROV_HANDLE,
        key_name: Vec<u16>,
    ) -> TpmResult<NCRYPT_KEY_HANDLE> {
        let mut hkey: NCRYPT_KEY_HANDLE = 0;

        let status = unsafe {
            // 指定した名前で永続的なRSAキーを作成する
            NCryptCreatePersistedKey(
                hprov,
                &mut hkey,
                BCRYPT_RSA_ALGORITHM,
                key_name.as_ptr(),
                0,
                0,
            )
        };
        if status != 0 {
            eprintln!("鍵の作成に失敗しました");
            return Err(status);
        }

        // 作成した鍵を使用できるようにする
        let status = unsafe { NCryptFinalizeKey(hkey, 0) };
        if status != 0 {
            eprintln!("鍵の作成に失敗しました");
            unsafe {
                NCryptFreeObject(hkey);
            }
            return Err(status);
        }

        Ok(hkey)
    }

    fn wrap_key(&self, target_key: [u8; 32]) -> TpmResult<Vec<u8>> {
        let target_key = Zeroizing::new(target_key);
        
        let padding_info = Self::create_padding_info();
        let mut required_size: u32 = 0;

        // 暗号化後のデータサイズを取得(サイズが分かってる場合は不要)
        let status = unsafe {
            NCryptEncrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                ptr::null_mut(),
                0,
                &mut required_size,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("暗号データサイズの取得に失敗しました");
            return Err(status);
        }

        let mut wrapped_key = vec![0u8; required_size as usize];
        let mut result_len: u32 = 0;

        // 対象の鍵をラップする
        let status = unsafe {
            NCryptEncrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                wrapped_key.as_mut_ptr(),
                wrapped_key.len() as u32,
                &mut result_len,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("鍵ラップに失敗しました");
            return Err(status);
        }

        // バッファを実際の暗号化データのサイズに調整する
        wrapped_key.truncate(result_len as usize);

        Ok(wrapped_key)
    }

    fn unwrap_key(&self, target_key: &[u8]) -> TpmResult<Vec<u8>> {
        let padding_info = Self::create_padding_info();
        let mut required_size: u32 = 0;

        // 復号化後のデータサイズを取得(サイズが分かってる場合は不要)
        let status = unsafe {
            NCryptDecrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                ptr::null_mut(),
                0,
                &mut required_size,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("復号データサイズの取得に失敗しました");
            return Err(status);
        }

        let mut unwrapped_key = vec![0u8; required_size as usize];
        let mut result_len: u32 = 0;

        // 対象の鍵をアンラップする
        let status = unsafe {
            NCryptDecrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                unwrapped_key.as_mut_ptr(),
                unwrapped_key.len() as u32,
                &mut result_len,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("鍵アンラップに失敗しました");
            return Err(status);
        }

        // バッファを実際の復号化データのサイズに調整する
        unwrapped_key.truncate(result_len as usize);

        Ok(unwrapped_key)
    }
}

取得したプロバイダー・キーハンドルは最後に解放する必要があります。
この実装では Drop で解放処理を行っています。

TPM プロバイダーで使用する RSA キーの鍵長に関して NCryptGetProperty() で実際に確認したところ、デフォルトは 2048 ビットで、設定可能な範囲は 1024 ~ 2048 ビットでした。
環境によって変わる可能性があるため、自分の環境でも確認したい場合はプロパティ確認・設定のセクションをご覧ください。


実際に試すためには、この main 関数を実行してみてください!

fn main() {
    let mut key = [0u8; 32];
    getrandom::fill(&mut key).unwrap();
    println!("元の鍵: {:?}", key);

    let key_wrapper = match KeyWrapper::open(KEY_NAME) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("エラーコード: {e}");
            return;
        }
    };

    let wrapped_key = match key_wrapper.wrap_key(key) {
        Ok(k) => {
            println!("ラップした鍵: {:?}", k);
            k
        }
        Err(e) => {
            eprintln!("エラーコード: {e}");
            return;
        }
    };

    match key_wrapper.unwrap_key(&wrapped_key) {
        Ok(k) => println!("アンラップした鍵: {:?}", k),
        Err(e) => eprintln!("エラーコード: {e}"),
    }
}

もし鍵を削除したい場合は、delete_key() を KeyWrapper に追加して実行してください。

fn delete_key(&mut self) -> TpmResult<()> {
    let status = unsafe {
        // 登録した鍵を削除する
        NCryptDeleteKey(self.hkey, 0)
    };
    if status != 0 {
        eprintln!("鍵の削除に失敗しました");
        return Err(status);
    }

    // 成功した場合、キーハンドルはNCryptDeleteKey()によって自動的に解放される
    self.hkey = 0;

    Ok(())
}

エラーコード

これは主なエラーコードと、それに対応する定数および説明の一覧です。

コード (i32) コード (hex) 定数名 説明
0 0x00000000 ERROR_SUCCESS 操作が正常に完了しました
-2146893802 0x80090006 NTE_BAD_KEYSET 鍵が存在しないか、TPMに登録されていません
-2146893809 0x8009000F NTE_EXISTS 同じ名前の鍵がすでに存在します
-2146893786 0x80090026 NTE_INVALID_HANDLE ハンドル(プロバイダーまたはキー)が無効です
-2146893785 0x80090027 NTE_INVALID_PARAMETER 1つ以上のパラメータが無効です
-2146893810 0x8009000E NTE_NO_MEMORY 操作を完了するためのメモリが不足しています
-2146893808 0x80090010 NTE_PERM アクセスが拒否されたか、操作が許可されていません
-2146893792 0x80090020 NTE_FAIL 内部エラーが発生しました
-2146893783 0x80090029 NTE_NOT_SUPPORTED アルゴリズムまたはオプションがサポートされていません
-2146893815 0x80090009 NTE_BAD_FLAGS dwFlags に指定されたフラグが無効です
-2146893784 0x80090028 NTE_BUFFER_TOO_SMALL 出力バッファが小さすぎます
-2147479534 0x80090032 NTE_INVALID_STATE オブジェクトの状態が無効です(例:未初期化など)
-2146893816 0x80090008 NTE_BAD_ALGID アルゴリズム識別子が無効です
-2146893814 0x8009000A NTE_BAD_TYPE 鍵またはオブジェクトの型が無効です

解説

どの関数を使用するかや各引数の意味を理解するために Microsoft の公式ドキュメントを参照し、引数の型の確認には windows-sys の公式ドキュメントを参照しました。

参考資料:

ここでは、これらの公式資料から学んだ内容をもとに、コード内で使用している各関数と引数について解説していきます。


1: TPM プロバイダーを開く

まず、NCryptOpenStorageProvider() を使用してプロバイダーを開く必要があります。

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

phprovider には、プロバイダーハンドルを受け取る変数(&mut usize)を指定します。

pszprovidername には、ロードするキーストレージプロバイダーのエイリアスを指定します。
std::ptr::null() を指定した場合は、既定のキーストレージプロバイダーがロードされます。

今回は TPM を使用するので MS_PLATFORM_CRYPTO_PROVIDER を選択しましたが、TPM が使用できない場合は MS_KEY_STORAGE_PROVIDER を使用することもできます。

この dwflags 引数は現在予約されているため、0 を指定してください。

let mut hprov: NCRYPT_PROV_HANDLE = 0;

let status =
    unsafe { NCryptOpenStorageProvider(&mut hprov, MS_PLATFORM_CRYPTO_PROVIDER, 0) };

戻り値(一部):

この関数が失敗した場合は、プロバイダーハンドルを他の関数で使用しないでください。
↓ これについては Microsoft のドキュメントにも記載されています


2: 永続キーを作成(存在しない場合)

NCryptCreatePersistedKey() を使用し、指定した名前で永続キーを作成できます。

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

phkey には、キーハンドルを受け取る変数(&mut usize)を指定します。

pszalgid には、暗号アルゴリズムを表すヌル終端の UTF-16 文字列を指定します。

通常は CNG アルゴリズム識別子 のいずれかを指定しますが、それ以外にも、プロバイダーが独自に登録しているアルゴリズム識別子も指定できます。
この実装では BCRYPT_RSA_ALGORITHM を選択しています。

pszkeyname には、鍵の名前を含むヌル終端の UTF-16 文字列へのポインタを指定します。
このパラメータを std::ptr::null() に設定すると、永続化されない一時キー(エフェメラルキー)が作成されます。

fn to_utf16(s: &str) -> Vec<u16> {
    let mut utf16: Vec<u16> = OsStr::new(s).encode_wide().collect();
    utf16.push(0);

    utf16
}

encode_wide() は &OsStr を UTF-16 に変換してくれます。
また、pszkeyname の末尾にはヌル文字が必要なため、文字列の最後に 0 を追加しています。

dwlegacykeyspec には、キーの種類を表すレガシ識別子を指定します。

通常の CNG 鍵作成では、この値は 0を指定してください。
他の値は CryptoAPI(CAPI)/CSP 互換向けのため、0 以外を指定するとエラー(NTE_NOT_SUPPORTED)になる場合があります。

dwflags には、この関数の動作を変更するためのフラグ(1つ以上)を指定します。
不要な場合は 0 を指定してください。

let mut hkey: NCRYPT_KEY_HANDLE = 0;

let status = unsafe {
    // 指定した名前で永続的なRSAキーを作成する
    NCryptCreatePersistedKey(
        hprov,
        &mut hkey,
        BCRYPT_RSA_ALGORITHM,
        key_name.as_ptr(),
        0,
        0,
    )
};

戻り値(一部):

鍵作成後に NCryptSetProperty() を使用すると鍵のプロパティを設定できます。
鍵長の設定に関しては 鍵のプロパティ設定・確認のセクションに記載しています。

鍵を作成した後、それを使用できるようにするために NCryptFinalizeKey() を呼び出す必要があります。

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

dwflags には、この関数の動作を変更するためのフラグ(1つ以上)を指定します。
不要な場合は 0 を指定してください。

NCRYPT_SILENT_FLAG はユーザーインターフェイスが表示されないようにするためのフラグですが、今回の TPM ベースの処理では通常は不要です。

let status = unsafe { NCryptFinalizeKey(hkey, 0) };

戻り値(一部):


3: 既存の鍵を取得

すでに鍵が存在する場合は、NCryptOpenKey() を使用してプロバイダーからその鍵を取得できます。

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

これらの引数は、NCryptCreatePersistedKey() とほぼ同じです。

dwflags には、この関数の動作を変更するためのフラグ(1つ以上)を指定します。
不要な場合は 0 を指定してください。

fn retrieve_key(hprov: NCRYPT_PROV_HANDLE, key_name: Vec<u16>) -> TpmResult<NCRYPT_KEY_HANDLE> {
    let mut hkey: NCRYPT_KEY_HANDLE = 0;

    let status = unsafe { NCryptOpenKey(hprov, &mut hkey, key_name.as_ptr(), 0, 0) };
    match status {
        0 => Ok(hkey),
        NTE_BAD_KEYSET => {
            // 指定した名前の鍵が存在しない場合
            println!("新しくRSAキーを作成します");
            Self::create_rsa_key(hprov, key_name)
        }
        _ => {
            eprintln!("鍵の取得に失敗しました");
            Err(status)
        }
    }
}

指定した鍵の名前が存在しない場合、NTE_BAD_KEYSET エラーが発生します。

戻り値(一部):


4: 暗号化データのサイズを取得

暗号化を実行する前に、暗号化後のデータサイズを取得する必要があります。
ただし、サイズが分かってる場合は必要ありません。

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

pbinput には、暗号化対象のデータを指すバッファへのポインタを指定します。

cdinput には、データ(pbInput)のバイト数を指定します。

ppaddinginfo には、パディング情報を含んだ BCRYPT_OAEP_PADDING_INFO 構造体へのポインタを指定します。
dwflags で NCRYPT_PAD_OAEP_FLAG を指定しない場合、または対称キーの場合は std::ptr::null() を指定してください。

pboutput には、暗号化データを書き込む出力バッファへのポインタを指定します。
暗号化データのサイズだけを取得する場合は、std::ptr::null_mut() を指定してください。

cboutput には、出力バッファ(pdoutput)のバイト数を指定します。
pboutput に std::ptr::null_mut() を渡した場合、この引数は無視されるらしいですが、実際に 0 以外を指定した場合に NTE_INVALID_PARAMETER エラーが発生しました。

pcbresult には、pboutput に書き込まれた暗号化データのバイト数を受け取る変数(&mut u32)を指定します。
pboutput に std::ptr::null_mut() を渡した場合、暗号化データに必要なバイト数を受け取ります。

dwflags には、この関数の動作を変更するためのフラグを指定します。
許可されるフラグは、hkey で指定した鍵の種類によって異なります。

NCRYPT_PAD_OAEP_FLAG は、現代的なセキュリティでは推奨されるフラグです。
使用する場合は、ppaddinginfo に BCRYPT_OAEP_PADDING_INFO 構造体へのポインタを指定する必要があります。
対称キーの場合は 0 を指定してください。

let padding_info = Self::create_padding_info();
let mut required_size: u32 = 0;

// 暗号化後のデータサイズを取得(サイズが分かってる場合は不要)
let status = unsafe {
    NCryptEncrypt(
        self.hkey,
        target_key.as_ptr(),
        target_key.len() as u32,
        (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
        ptr::null_mut(),
        0,
        &mut required_size,
        NCRYPT_PAD_OAEP_FLAG,
    )
};

戻り値(一部):

BCRYPT_OAEP_PADDING_INFO 構造体

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

pszAlgId には、OAEP パディングで使用するハッシュアルゴリズムを指定します。
ハッシュアルゴリズムは CNG アルゴリズム識別子 から確認できます。

pbLabel には、パディング作成に使用するラベルデータを含むバッファへのポインタを指定します。
不要な場合は std::ptr::null_mut() を指定してください。

cbLabel には、pbLabel バッファ内のバイト数を指定します。
pbLabel に std::ptr::null_mut() を渡す場合は、0 を指定してください。

fn create_padding_info() -> BCRYPT_OAEP_PADDING_INFO {
    BCRYPT_OAEP_PADDING_INFO {
        pszAlgId: BCRYPT_SHA256_ALGORITHM,
        pbLabel: ptr::null_mut(),
        cbLabel: 0,
    }
}

このコードでは、ハッシュアルゴリズムに BCRYPT_SHA256_ALGORITHM を使用しています。


5: 対象の鍵をラップ

最後に、NCryptEncrypt() で鍵ラップ処理に入ります。

let mut wrapped_key = vec![0u8; required_size as usize];
let mut result_len: u32 = 0;

// 対象の鍵をラップする
let status = unsafe {
    NCryptEncrypt(
        self.hkey,
        target_key.as_ptr(),
        target_key.len() as u32,
        (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
        wrapped_key.as_mut_ptr(),
        wrapped_key.len() as u32,
        &mut result_len,
        NCRYPT_PAD_OAEP_FLAG,
    )
};

暗号化データを受け取る際は、必要なバイト数のバッファを用意し、そのバッファへのポインタを pboutput に指定してください。
この関数でサイズを取得した場合は、pcbresult に渡した変数に格納されたサイズを使ってバッファを確保してください。

NCryptEncrypt() の説明は前のパートに記載しています。

最後に、pcbresult で返された実際のサイズに合わせて切り詰める必要があります。

wrapped_key.truncate(result_len as usize);

対象の鍵をアンラップ

暗号化された鍵の復号は、暗号化とほぼ同じ手順です。
違いは、NCryptEncrypt() を NCryptDecrypt() に置き換えるだけです。

fn unwrap_key(&self, target_key: &[u8]) -> TpmResult<Vec<u8>> {
    let padding_info = Self::create_padding_info();
    let mut required_size: u32 = 0;

    // 復号化後のデータサイズを取得(サイズが分かってる場合は不要)
    let status = unsafe {
        NCryptDecrypt(
            self.hkey,
            target_key.as_ptr(),
            target_key.len() as u32,
            (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
            ptr::null_mut(),
            0,
            &mut required_size,
            NCRYPT_PAD_OAEP_FLAG,
        )
    };
    if status != 0 {
        eprintln!("復号データサイズの取得に失敗しました");
        return Err(status);
    }

    let mut unwrapped_key = vec![0u8; required_size as usize];
    let mut result_len: u32 = 0;

    // 対象の鍵をアンラップする
    let status = unsafe {
        NCryptDecrypt(
            self.hkey,
            target_key.as_ptr(),
            target_key.len() as u32,
            (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
            unwrapped_key.as_mut_ptr(),
            unwrapped_key.len() as u32,
            &mut result_len,
            NCRYPT_PAD_OAEP_FLAG,
        )
    };
    if status != 0 {
        eprintln!("鍵アンラップに失敗しました");
        return Err(status);
    }

    // バッファを実際の復号化データのサイズに調整する
    unwrapped_key.truncate(result_len as usize);

    Ok(unwrapped_key)
}

戻り値(一部):


鍵のプロパティ確認・設定

オブジェクトのプロパティを確認するには、NCryptGetProperty() を使用します。

Microsoft ドキュメント:

windows-sys ドキュメント:

hobject には、対象のオブジェクトハンドルを指定します。
鍵のプロパティを取得するにはキーハンドルを指定してください。

pszproperty には、取得したいプロパティ識別子を指定します。
識別子一覧は キー ストレージ プロパティ識別子 から確認できます。
対応可能な鍵長範囲やデフォルト値などを確認したい場合は NCRYPT_LENGTHS_PROPERTY を指定してください。

pboutput には、プロパティ値を受け取るバッファへのポインタを指定します。
必要なバッファサイズを取得したい場合は、std::ptr::null_mut() を指定してください。

cboutput には、出力バッファ(pboutput)のバイト数を指定します。
pboutput が std::ptr::null_mut() の場合は、0 を指定してください。

pcbresult には、実際に書き込まれたバイト数、または必要なバッファサイズを受け取るための変数(&mut u32)へのポインタを指定します。

dwflags には、この関数の動作を変更するためのフラグを指定します。
不要な場合は 0 を指定してください。

fn check_key_lengths(&self) -> TpmResult<()> {
    let mut supported_lengths = NCRYPT_SUPPORTED_LENGTHS {
        dwMinLength: 0,
        dwMaxLength: 0,
        dwIncrement: 0,
        dwDefaultLength: 0,    
    };
    let mut size: u32 = 0;

    let status = unsafe {
        NCryptGetProperty(
            self.hkey, 
            NCRYPT_LENGTHS_PROPERTY,
            (&mut supported_lengths as *mut NCRYPT_SUPPORTED_LENGTHS).cast(),
            size_of::<NCRYPT_SUPPORTED_LENGTHS>() as u32,
            &mut size,
            0,
        )
    };
    if status != 0 {
        eprintln!("プロパティ取得に失敗しました");
        return Err(status)
    }

    println!(
        "デフォルト値: {} bits, 設定可能範囲: {} ~ {} bits", 
        supported_lengths.dwDefaultLength,
        supported_lengths.dwMinLength,
        supported_lengths.dwMaxLength,
    );

    Ok(())
}

現在の鍵長を取得したい場合は、プロパティ識別子に NCRYPT_LENGTH_PROPERTY を指定します。

fn check_current_key_length(&self) -> TpmResult<()> {
    let mut cur_key_length: u32 = 0;
    let mut size: u32 = 0;

    let status = unsafe {
        NCryptGetProperty(
            self.hkey, 
            NCRYPT_LENGTH_PROPERTY,
            (&mut cur_key_length as *mut u32).cast(),
            size_of::<u32>() as u32,
            &mut size,
            0,
        )
    };
    if status != 0 {
        eprintln!("プロパティ取得に失敗しました");
        return Err(status)
    }

    println!("現在の鍵長: {cur_key_length} bits");

    Ok(())
}

戻り値(一部):


オブジェクトのプロパティを設定するには、NCryptSetProperty() を使用します。
新規作成中の鍵に対しては、NCryptFinalizeKey() の前に設定する必要があります。

Microsoft ドキュメント:

windows-sys ドキュメント:

pszproperty には、設定するプロパティの名前を指定します。
鍵長を設定する場合は NCRYPT_LENGTH_PROPERTY を指定してください。

pbinput には、新しいプロパティ値を含むバッファへのポインタを指定します。

cbinput には、pbinput に渡したバッファのバイト数を指定します。

dwflags には、この関数の動作を変更するためのフラグ(1つ以上)を指定します。
不要な場合は 0 を指定してください。

fn set_key_length(hkey: NCRYPT_KEY_HANDLE, length: u32) -> TpmResult<()> {
    let status = unsafe {
        NCryptSetProperty(
            hkey,
            NCRYPT_LENGTH_PROPERTY,
            (&length as *const u32).cast(),
            size_of::<u32>() as u32,
            NCRYPT_PERSIST_FLAG,
        )
    };
    if status != 0 {
        eprintln!("プロパティ設定に失敗しました");
        return  Err(status);
    }

    Ok(())
}

戻り値(一部):


登録済みの鍵を削除

NCryptDeleteKey() を使用して登録済みの鍵を削除できます。

Microsoft ドキュメント:

Image description

windows-sys ドキュメント:

Image description

dwflags には、この関数の動作を変更するためのフラグを指定します。
指定できる値は NCRYPT_SILENT_FLAG のみです。
不要な場合は 0 を指定してください。

let status = unsafe {
    // 登録した鍵を削除する
    NCryptDeleteKey(self.hkey, 0)
};

この関数が成功した場合は、キーハンドルが自動的に開放されます。
↓ これについては Microsoft のドキュメントにも記載されています。

戻り値(一部):

まとめ

最初は C++ にあまり詳しくなかったため、どっちが引数名か型なのかすら分かりませんでしたが、実装を進める中で読み方だけでなく、TPM の仕組みについてやその他いろいろなことを学ぶことができました。

最後まで読んでいただき、ありがとうございました!

Discussion