はじめに
Windows環境でOpenSSHサーバーを構築し、TermuxやWSL、Macなどから公開鍵認証でSSH接続しようとしたとき、「なぜか公開鍵が無視され、無言でパスワード入力を求められる」 という経験はないでしょうか。
LinuxやmacOSであれば ssh-copy-id コマンド一発で終わる設定ですが、Windowsには特有の厳格な仕様と罠がいくつも隠されています。筆者自身、この問題に数時間ハマりました。
この記事では、実際のトラブルシューティングの過程をもとに、Windows OpenSSH特有の 「4つの罠」 とその確実な解決方法をまとめます。同じ問題で困っている方の参考になれば幸いです。
環境・前提条件
- OS: Windows 10(ビルド1809以降)/ Windows 11 / Windows Server 2019以降
- OpenSSH: Windows組み込みのOpenSSHサーバー(
sshd) - クライアント: Termux(Android)、WSL、macOSなど
- 鍵の種類: Ed25519(RSAでも同様の問題が発生します)
ハマったポイント:Windows OpenSSH「4つの罠」
罠1:PowerShellの「文字コード」の罠(BOM付きUTF-16)
Linuxの感覚で、公開鍵をサーバーに送った後に以下のようなコマンドで追記してしまうと、最初の罠にハマります。
# ❌ やってはいけない書き方
cat id_ed25519.pub >> .ssh\authorized_keys
PowerShellの >>(リダイレクト演算子)や Out-File を使用すると、デフォルトで BOM付きUTF-16LE という文字コードでファイルが作成されます。これはMicrosoft公式ドキュメントでも明記されている仕様です(参考: PowerShell の文字エンコードについて – Microsoft Learn)。
しかし、OpenSSHは BOMなしUTF-8またはASCII しか正しく読み込めません。そのため、鍵ファイルが文字化けした無効なデータとして扱われ、完全に無視されてしまいます。
補足: PowerShell 7.x以降ではデフォルトがBOMなしUTF-8に変更されていますが、Windows標準搭載のWindows PowerShell 5.1ではUTF-16LEのままです。ご自身のバージョンをご確認ください。
罠2:親フォルダの権限(NTFS ACL継承)の罠
文字コードを直しても繋がらない場合、最大の原因は NTFSのアクセス権限(ACL) です。
Windows OpenSSHは非常に厳格な権限チェックを行います。authorized_keys ファイル自体だけでなく、その親フォルダ(C:\Users\ユーザー名 など)に少しでも緩い権限が設定されていると、鍵認証を無効化します(参考: OpenSSH Server Configuration for Windows – Microsoft Learn)。
例えば、開発ツール(Cursorなど)が作成した CodexSandboxUsers などのグループや、Everyone に変更権限が付与されていると、「第三者に鍵をすり替えられるリスクがある」と判定され、サイレントに拒否 されます。エラーメッセージも表示されないため、原因の特定が非常に困難です。
この問題はGitHubのWin32-OpenSSHプロジェクトでも多くのユーザーから報告されており、長年にわたる課題として認識されています(参考: PowerShell/Win32-OpenSSH Issue #1942)。
罠3:管理者ユーザー特有の「ProgramData」の罠
接続先のWindowsユーザーが Administratorsグループ(管理者グループ) に属している場合、デフォルトの sshd_config の設定により、ユーザーフォルダの .ssh\authorized_keys ではなく、以下の特別なパスの鍵ファイルが参照されます。
C:\ProgramData\ssh\administrators_authorized_keys
これはMicrosoft公式ドキュメントにも明記されている仕様です。管理者グループに属するユーザーの場合、%programdata%/ssh/administrators_authorized_keys が使用されると記載されています(参考: Key-Based Authentication in OpenSSH for Windows – Microsoft Learn)。
つまり、いくら ~/.ssh/authorized_keys に正しい鍵を配置しても、管理者ユーザーの場合はそもそも 見に行く場所が違う ということです。
罠4:灯台下暗し「鍵の不一致」
権限も文字コードも完璧なのに弾かれる場合、最後に疑うべきは 「手元にある公開鍵と、サーバーに登録した公開鍵が物理的に別物になっている」 ことです。
複数のデバイスを使っていたり、鍵を再生成したりした際に混同してしまうケースは想像以上に多いです。「文字列が完全に一致しているか」を必ず目視またはハッシュ値で確認してください。
解決策
ステップ1:BOMなしUTF-8で公開鍵を書き込む(罠1の解決)
PowerShellの >> リダイレクトは使わず、.NETのクラスを利用して明示的にBOMなしUTF-8で書き込みます。
# BOMなしUTF-8で公開鍵を書き込む
$pubKey = "ssh-ed25519 AAAAC3...(あなたの公開鍵)... user@host"
$authFile = "C:\Users\ユーザー名\.ssh\authorized_keys"
# [IO.File]::WriteAllTextはデフォルトでBOMなしUTF-8を使用
[IO.File]::WriteAllText($authFile, $pubKey + "`n", [Text.Encoding]::UTF8)
ポイント:
[IO.File]::WriteAllTextは .NET Frameworkのメソッドで、PowerShellのOut-Fileとは異なり、明示的にエンコーディングを制御できます。
ステップ2:.sshフォルダとauthorized_keysの権限を厳格化する(罠2の解決)
ユーザーフォルダ全体の権限を修正するのは影響範囲が大きすぎるため、.ssh フォルダと authorized_keys ファイルの 「親からの権限継承」を断ち切り、必要な権限だけを明示的に付与します。
# .sshフォルダの継承を切り、必要なアカウントだけに権限を付与
$dotSsh = "C:\Users\ユーザー名\.ssh"
icacls $dotSsh /inheritance:r
icacls $dotSsh /grant:r "$($env:USERNAME):(OI)(CI)F"
icacls $dotSsh /grant:r "SYSTEM:(OI)(CI)F"
icacls $dotSsh /grant:r "Administrators:(OI)(CI)F"
# authorized_keysファイルも同様に設定
$authFile = "C:\Users\ユーザー名\.ssh\authorized_keys"
icacls $authFile /inheritance:r
icacls $authFile /grant:r "$($env:USERNAME):F"
icacls $authFile /grant:r "SYSTEM:F"
各オプションの意味は以下の通りです。
/inheritance:r: 親フォルダからの権限継承を削除/grant:r: 既存の権限をリセットして新しい権限を付与(OI)(CI): オブジェクト継承・コンテナ継承(フォルダ内のファイルにも適用)F: フルコントロール
ステップ3:管理者ユーザーのauthorized_keys参照先を変更する(罠3の解決)
個人開発環境などで、Linuxと同じように ~/.ssh/authorized_keys で一元管理したい場合は、設定ファイルを修正してこのルールを無効化します。
C:\ProgramData\ssh\sshd_configを管理者権限のテキストエディタで開きます。- ファイル末尾にある以下の2行をコメントアウト(
#を追記)します。
# 変更前
Match Group administrators
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
# 変更後
#Match Group administrators
# AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
- PowerShell(管理者)でsshdサービスを再起動します。
Restart-Service sshd
注意: サーバーの用途によっては、
administrators_authorized_keysを使う方がセキュリティ上望ましい場合もあります。共有サーバーや本番環境では、コメントアウトではなくadministrators_authorized_keys側に鍵を正しく配置する方法もご検討ください。
ステップ4:鍵の一致を確認する(罠4の解決)
クライアント側とサーバー側で公開鍵が一致しているかを確認します。
# クライアント側(Termux / WSL / Mac)
cat ~/.ssh/id_ed25519.pub
# サーバー側(Windows PowerShell)
Get-Content C:\Users\ユーザー名\.ssh\authorized_keys
両方の出力を比較し、一文字でも違いがないかを確認してください。スペースや改行の有無も確認対象です。
動作確認
デバッグコマンドで原因を特定する
WindowsのOpenSSHが「なぜ拒否したのか」を突き止めるには、イベントビューアのログを確認するのが最も確実です。PowerShell(管理者)で以下を実行します。
# OpenSSHの直近のログを確認
Get-WinEvent -LogName "OpenSSH/Operational" -MaxEvents 10 |
Select-Object -ExpandProperty Message
ログを読む際のポイントは以下の通りです。
Bad permissionsというメッセージが出ている → 罠2(権限の問題) が原因- エラーが出ずに
Accepted passwordになっている → サイレント拒否(罠1~3のいずれか) key not foundや認証失敗のメッセージ → 罠4(鍵の不一致) の可能性
接続テスト
すべての設定が完了したら、クライアントから接続テストを行います。
# -v オプションで詳細ログを出力
ssh -v ユーザー名@サーバーのIPアドレス
-v オプションを付けることで、鍵認証のネゴシエーション過程が表示されます。Authenticated と表示されればパスワードなしでの接続が成功です。
おまけ:Go言語版「Windows対応 ssh-pushkey」を作りました
これまで解説した「UTF-8での書き込み」「継承の無効化」「厳密なicaclsによる権限付与」を毎回手動で行うのは非常に苦痛です。
そこで、これらのWindows特有の罠をすべて自動で回避し、Linuxの ssh-copy-id と同じ感覚で一発で公開鍵を登録できる Go言語製のCLIツール を開発しました。
- GitHub: kwrkb/ssh-pushkey
- GitLab: kwrkb/ssh-pushkey
これを使えば、面倒なPowerShellのコマンドを叩くことなく、数秒で安全なSSH鍵認証のセットアップが完了します。Windows環境とのSSH接続に消耗している方は、ぜひ試してみてください。
まとめ
Windows OpenSSHで公開鍵認証が弾かれる原因は、主に以下の4つです。
- 罠1(文字コード): PowerShellの
>>はBOM付きUTF-16LEで出力する →[IO.File]::WriteAllTextでBOMなしUTF-8を使う - 罠2(NTFS権限): 親フォルダから継承された緩い権限がサイレント拒否を引き起こす →
icaclsで継承を切り、必要最小限の権限を付与する - 罠3(管理者ユーザー): 管理者グループのユーザーは
administrators_authorized_keysを参照する →sshd_configのMatch Groupルールをコメントアウトする - 罠4(鍵の不一致): 複数デバイス間で鍵を取り違えている → クライアントとサーバーの鍵を目視で照合する
Linuxでは ssh-copy-id 一発で済む作業が、Windowsではこれだけの落とし穴があります。一つひとつは単純な原因ですが、複数が同時に重なると原因の特定が非常に困難になるのが厄介なところです。
この記事が、同じ問題で何時間も消耗している方の助けになれば幸いです。
参考リンク
- OpenSSH Server Configuration for Windows – Microsoft Learn
- Key-Based Authentication in OpenSSH for Windows – Microsoft Learn
- OpenSSH Client Can’t Connect To a Server via SSH – Microsoft Learn
- PowerShell の文字エンコードについて(about_Character_Encoding)- Microsoft Learn
- PowerShell/Win32-OpenSSH Issue #1942 – GitHub
- kwrkb/ssh-pushkey – GitHub
- kwrkb/ssh-pushkey – GitLab

コメント