「1 ライセンスにつき 3 台まで」のような端末数制限や、クラッシュレポートの端末単位の集計には、ホスト名より変わりにくい ID が必要です。各 OS はインストール時に生成した固有 ID を持っており、Rust の machine-uid クレートで 1 行で取得できます。ただし出どころは OS ごとに違い、ハードウェアに紐づくのは macOS だけです。何をもとにした ID か、生の値を送ってはいけない理由、初回起動時に自分で UUID を作って保存する代替案も示します。
前提条件
src-tauri で次を実行します。sha2 はハッシュ化用、uuid は代替案用です。
cargo add machine-uid sha2 uuid --features uuid/v4
代替案で Store プラグインを使う場合は npm run tauri add store を実行し、capabilities に store:default を追加します。
1. フロントエンドから実装する (TypeScript)
import { invoke } from '@tauri-apps/api/core';
// OS のマシン ID をアプリ固有のソルトでハッシュ化した 64 文字の hex
const deviceId = await invoke<string>('device_id');
console.log(deviceId); // "3f9a...c21e"
フロントに生の ID を渡す必要はまずありません。サーバーへ送るのもハッシュ後の値にしておくと、他社アプリと突き合わせて同一端末を追跡する材料にならず、プライバシーポリシーにも書きやすくなります。
2. バックエンドから実装する (Rust)
src-tauri/src/lib.rs:
use sha2::{Digest, Sha256};
// アプリ固有のソルト。別アプリと同じ値にならないようにする
const SALT: &str = "com.example.myapp/v1";
#[tauri::command]
fn device_id() -> Result<String, String> {
// Windows: MachineGuid / macOS: IOPlatformUUID / Linux: /etc/machine-id
let raw = machine_uid::get().map_err(|e| e.to_string())?;
let mut h = Sha256::new();
h.update(SALT.as_bytes());
h.update(raw.trim().as_bytes());
// 16 進の文字列にする(sha2 0.10 / 0.11 のどちらでも動く書き方)
Ok(h.finalize().iter().map(|b| format!("{:02x}", b)).collect())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![device_id])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
machine_uid::get() は Result<String, Box<dyn Error>> を返し、管理者権限は不要です。
各 OS で何をもとにした ID か
| OS | 取得元 | 変わるタイミング |
|---|---|---|
| Windows | レジストリ HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid | Windows の再インストール。ハードウェア交換では変わらない |
| macOS | IOPlatformUUID(ioreg -rd1 -c IOPlatformExpertDevice で見える値) | ロジックボード交換。OS 再インストールでは変わらない |
| Linux | /var/lib/dbus/machine-id または /etc/machine-id | ディストリビューションの再インストール。クローンした VM やコンテナでは同じ値になりうる |
つまり Windows と Linux では「OS インストール ID」であって、CPU やマザーボードには紐づきません。「ハードウェア ID」と呼んで設計すると、OS 再インストールで「別端末」と判定されライセンス枠を消費する問い合わせが必ず来ます。再アクティベーションの導線を用意してください。
代替案: 初回起動時に UUID を生成して保存する
端末を識別したいだけなら、アプリが自分で発行した UUID を保存する方が扱いやすいことも多いです。ユーザーがアプリデータを消せばリセットでき、説明もしやすくなります。
import { load } from '@tauri-apps/plugin-store';
export async function installId(): Promise<string> {
const store = await load('device.json', { autoSave: true });
let id = await store.get<string>('install_id');
if (!id) {
id = crypto.randomUUID(); // WebView2 / WebKit どちらでも使える
await store.set('install_id', id);
await store.save();
}
return id;
}
Rust 側で生成するなら uuid::Uuid::new_v4().to_string() です。マシン ID のハッシュとこの UUID を両方送り、片方が一致すれば同一端末とみなす緩い判定が実運用では現実的です。
動作確認
npm run tauri dev で起動し、コンソールに 64 文字の 16 進文字列が出れば成功です。再起動しても同じ値が返ること、Windows なら PowerShell で (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Cryptography').MachineGuid を表示し、同じソルトでハッシュした値と一致することを確認できます。
よくあるエラーと対処法
- Linux で
machine_uid::get()がErr:/etc/machine-idが無い最小構成のコンテナや、systemdを使わない環境で起きます。systemd-machine-id-setupで生成するか、Store 保存の UUID にフォールバックします。 - Linux で複数台が同じ ID になる: VM のテンプレートをクローンした環境です。
/etc/machine-idを空にして再起動すると再生成されます。「同一 ID の端末が異常に多い」ケースを想定しておきます。 store.load not allowed. Permissions associated with this command: store:default, ...という趣旨のエラー: capabilities にstore:defaultが入っていません。crypto.randomUUID is not a function: 安全でないオリジン(http://の非 localhost で開いた独自 devServer など)で動かしています。Rust 側のuuidで生成して返します。
OS ごとの違いと注意点
- macOS:
IOPlatformUUIDは最も「ハードウェア」に近い ID ですが、App Store 審査はデバイスのフィンガープリンティングに敏感なので、用途と保存期間をプライバシーポリシーに明記してください。 - Linux:
/etc/machine-idはsystemdの管理下で、ユーザーが自由に書き換えられます。ライセンス保護としての強度は期待できません。 - プライバシー: 端末 ID は個人を識別しうる情報として扱われます。生の値を送信・保存しない、用途を限定する、削除の手段を用意する、の 3 点を守ってください。
