はじめに(背景)
Java 8環境で最新の暗号化アルゴリズム(ed25519)SFTPサーバーに接続しようとした際、SSHJライブラリで net.schmizz.sshj.transport.TransportException: Algorithm X25519 not available というエラーに直面しました。その解決までの経過を備忘がてら投稿します。
1. 発生した問題
Java 8 (8u411) 上で SSHJ を使用し、秘密鍵認証によるSFTP接続を試みたところ、接続(connect)フェーズで以下の例外が発生しました。
```
Caused by: java.security.NoSuchAlgorithmException: Algorithm X25519 not available
at javax.crypto.KeyAgreement.getInstance(KeyAgreement.java:184)
at net.schmizz.sshj.common.SecurityUtils.getKeyAgreement(SecurityUtils.java:152)
```
2. 解決に向けたアプローチ
ステップ1:Bouncy Castleの導入
Java標準で足りない暗号アルゴリズムを補うため、Bouncy Castle をプロジェクトに追加します。
ちなみに、Bouncy Castle のバージョンについて 1.70 を指定しています。より新しいバージョンも存在しますが、調べたところ、Java 8 環境(特に古いアップデート版や32bit VM)での動作実績が豊富で、依存関係の競合が少ないこのバージョンを今回は採用しました。 要するにJava8で動かすのに適したバージョンがこちら、ということです。
プロジェクトの要件や最新の脆弱性情報を確認した上で、必要に応じて最新版(1.7x〜)への差し替えを検討してください。
Maven依存関係:(pom.xml)
```xml
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
```
ステップ2:セキュリティプロバイダの優先順位設定
単にライブラリを入れるだけでなく、Javaのセキュリティプロバイダの先頭に登録し、SSHJが確実にそれを使用するように設定します。
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
// 優先順位1位で登録
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
ステップ3:問題のあるアルゴリズムの除外(決定打)
Bouncy Castleを登録してもなお、名前の解決順位や環境依存によりエラーが消えない場合があります。その場合、「Java 8で不安定な X25519 を使用リストから外す」 設定が最も確実です。
DefaultConfig が返すリストは固定長(Unmodifiable)であるため、一度 ArrayList にコピーしてから操作するのがポイントです。
ちなみに、このポイントで私はかなり沼りました。。
// 修正不可能なリストをコピーしてそれを設定し、上書きする形で編集する
List<Factory.Named<KeyExchange>> kexFactories = new ArrayList<>(config.getKeyExchangeFactories());
// Java 8でエラーの原因となるCurve25519を除外
kexFactories.removeIf(f -> f.getName().contains("curve25519"));
config.setKeyExchangeFactories(kexFactories);
3. 最終的なコード(Java 8対応版)
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.transport.kex.KeyExchange;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
public class SftpSafeDownloader {
public void download() throws Exception {
// 1. Bouncy Castleの登録
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
// 2. 設定のカスタマイズ
DefaultConfig config = new DefaultConfig();
// 修正不可能なリストをコピーして編集可能にする
List<Factory.Named<KeyExchange>> kexFactories = new ArrayList<>(config.getKeyExchangeFactories());
// Java 8でエラーの原因となるCurve25519を除外
kexFactories.removeIf(f -> f.getName().contains("curve25519"));
config.setKeyExchangeFactories(kexFactories);
// 3. 接続実行
try (SSHClient ssh = new SSHClient(config)) {
// 注意: PromiscuousVerifierはホストキー検証をスキップするため、本番環境での使用は推奨されません。
// 実際の運用では、KnownHostsManagerなどを用いて適切にホストキーを検証してください。
ssh.addHostKeyVerifier(new PromiscuousVerifier());
ssh.connect("hostname");
// 秘密鍵による認証
ssh.authPublickey("user", ssh.loadKeys("path/to/private_key.pem"));
try (SFTPClient sftp = ssh.newSFTPClient()) {
sftp.get("remote_path", new FileSystemFile("local_path"));
}
}
}
}
※補足
PromiscuousVerifier(直訳すると「無差別な検証者」)は、接続先のサーバーが本物かどうかを確認せず、全てのホストキーを無条件で受け入れる設定です。
まとめ
- Java 8で最新のSSHアルゴリズムを扱うには Bouncy Castle が必須。
- Algorithm X25519 not available が出た場合、中途半端に対応するより、使用アルゴリズムから除外して安定したアルゴリズム(NIST P-256等)にフォールバックさせるのが有効です。レガシー環境での運用においてはこちらを検討ください。
- DefaultConfig のリスト操作時は UnsupportedOperationException に注意(コピーしてから操作する)。