はじめに(背景)
直近のプロジェクトにおいて、SFTP接続によるファイル取得をシステム化し実装をする要件がありました。
これまでJava8でのSFTP接続においてJSchを使っていましたが、JSchはメンテナンスが停滞しており、近年の強力な暗号化アルゴリズム(ed25519、rsa-sha2-256など)に対応できないという課題があります。
例えば、下記のような問題点があります。
- メンテナンスの停止: オリジナルのJSchは2018年頃から更新が止まっており、最新のRFCに対応していない。
- セキュリティリスク: 脆弱性のある古い暗号化方式しか使えない。
- 実装の難易度: 無理に最新アルゴリズムをJSchで使おうとすると、Bouncy Castleなどの外部プロバイダを複雑に組み合わせる必要があり、メンテナンス性が低い。
これらの理由より、自身の学習を兼ねて、代替案を考えることとしました。
代替案の候補とそれぞれの特徴
1. Apache MINA SSHD (推奨)
現在、JavaでSFTPを扱う際のもっとも標準的かつ強力な選択肢です。
-
特色:
- 高い柔軟性: SSHD(サーバー)とSSHC(クライアント)の両方をサポートしており、非常に多機能です。
- モダンな実装: 非同期I/O(NIO)をベースにしており、パフォーマンスが高いです。
- 活発なメンテナンス: Apache財団によって管理されており、最新の暗号化アルゴリズム(ed25519など)への対応が早いです。
- Springとの親和性: Spring IntegrationのSFTPアダプタも、内部の実装を後述のJSchからこのApache MINA SSHDへ移行しています。
-
向いているケース:
- 新規プロジェクトや、セキュリティ要件が厳しいエンタープライズ用途。
2. sshj
JSchよりも直感的で使いやすいAPIを目指して作られたライブラリです。
-
特色:
- モダンな設計: APIがクリーンで、Javaらしい読みやすいコードが書けます。
- BouncyCastle依存: 暗号化処理にBouncyCastleライブラリを使用しており、セキュリティ設定の柔軟性が高いです。
- メンテナンス状況: 現在も継続的にメンテナンスされています。
-
向いているケース:
- JSchの古いAPIスタイルを避けたい、あるいは可読性の高いコードを重視する場合。
3. 他言語のアプリ等の起動
Javaアプリから他のサービスやアプリを起動する。
例)python,Go
3-1. Python (Paramiko / Fabric)
- 特徴: シンプルな記述で、スクリプト作成が非常に速い。
- 適したケース: 運用自動化スクリプト、データ分析基盤へのファイル収集。
- メリット: Paramiko は非常に成熟しており、最新のアルゴリズム対応も進んでいます。数行で実装できるため、プロトタイプ作成にも向いています。
3-2. Go (golang.org/x/crypto/ssh)
- 特徴: シングルバイナリで動作し、高速。
- 適したケース: 軽量なCLIツールの作成、マイクロサービスの一部としての転送機能。
- メリット: 標準ライブラリの拡張パッケージ(x/crypto/ssh)が非常に優秀で、依存関係の悩み(JSchで起きたような古いライブラリ問題)がほとんどありません。
※参考として、各言語の比較を以下にまとめます。
| 項目 | Java (MINA sshd) | Python (Paramiko) | Go (crypto/ssh) |
|---|---|---|---|
| 開発スピード | 普通 | 非常に速い | 速い |
| 実行速度 | 速い | 普通 | 非常に速い |
| 型安全性 | 高い | 低い | 高い |
| デプロイ | JVMが必要 | Python環境が必要 | バイナリ1つでOK |
| 用途 | 基幹システム | 運用スクリプト | クラウドネイティブツール |
実際に実装してみた
- 前提条件や環境
- Java8(他Python)
- ビルドはMavenで
- DockerでSFTPサーバをローカルに構築して検証
- 鍵方式がどんなものでも対応できるようにしたい (実装は
ed25519に対応したものを作成)
github(実際のソース)
今回実装したSFTP接続処理は
・Java SSHJライブラリ
・python(Paramiko)の外部スクリプト起動
2点での実装をしてみました。
ソースは↓
https://github.com/wtrsb/SFTPDownload
※2026/1/8現在 実装したソースの内容のみ
動作環境の為に用意したDocker使用して作成したSFTP環境等は追々アップ予定
実装時の感想
-
python(Paramiko)の外部スクリプト起動
- 一番簡潔にできそうなものであった
- 最新へのアルゴリズム対応
実際に、一番簡単に実装でき、動作検証成功するまでも簡単だったので、そこまで苦労しなかったですが、Python(Paramiko)等の環境準備作業は必要です。
-
Java SSHJライブラリ
- Javaでの実装をしたい
- 簡潔にできそうなものであった
- SSHDと比較し、可読性が高いらしい
- 後述の通りだが、SSHDを使用しての実装が不可能だった
実際にやってみたところ、色んな点で躓きました。。
また、結果こちらは採用しなかったのですが、その理由として、動作させるため、本番環境への実装は非推奨のコーディングをする必要があったためです。
詳細は別の記事で解説していますので、併せてご確認ください。
https://qiita.com/wtwww/items/36b0539edc5192635971
Apache MINA SSHDに関して
Apache MINA SSHDでも実装しようとしたのですが、Java8での実装の場合、
下記点が問題となり実装を辞めました。
- Java 8固有のセキュリティ制約
Java 8環境において、セキュリティ要件(SFTP接続など)でApache SSHDを導入する際、モダンなed25519鍵を使用しようとすると、外部ライブラリである Bouncy Castle (BC) の導入が必須となります。しかし、この組み合わせは「Java 8固有のセキュリティ制約」により、実行時にエラーを引き起こすことがあります。そのため、対応できる鍵方式が限定される実装になります。
直面するエラー(ビルド自体は上手くいくが、実行時下記エラーとなる)
org.apache.sshd.common.SshException: [ssh-connection]: Failed (NoSuchProviderException) to execute: JCE cannot authenticate the provider BC
...
Caused by: java.util.jar.JarException: ...jar has unsigned entries
- Jar構成に関して
Bouncy Castleとの相性の問題で、BCは単独のJARであれば有効な署名を持っていますが、Fat-JAR化(1つのjarにまとめる)されることを想定していません。Java 9以降ではこの制約が緩和されていますが、Java 8ではこの「JARの構成」が致命的な障壁となります。Fat-JAR化を辞める事で対応できますが、現場の他ツール同様、構成としてはFat-JAR化したかったため、やめました。
まとめ
-
結論
結果、現場ではpythonの外部スクリプトを起動する形をとりました。
理由は下記です。- どんな鍵方式でも対応できる方法であること
- 実装、検証が簡単であったこと
- SSHJライブラリではJavaバージョンに依存して動作しない場合があること
-
所感
- Java8での実装では多少強引な箇所や不可能な場合がある。最新アルゴリズムに対応していくなら、そもそもJavaのバージョンアップも検討しないといけない
- Javaメインでの現場なので、Javaに固執していたが、pythonでの実装は手軽かつ簡単であったのと、SFTP実装における言語選定に関する勉強にもなってよかった
- 今後Goでの実装もしてみたい