|
~ To be, or not to be, or to ask someone to be. ~ null-i.net |
| AndroidJava/sshクライアントをJSchで | |
|
JSch を Android で動かす(2026-04-18)
※ApacheMINA で実装する方法はこちら -> sshクライアントをApacheMINAで
import android.content.Context;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.PrintWriter;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
public class MySsh01 {
private static final String user = "your_user";
private static final String pass_word = "your_password";
private static final String host = "your.remote.server";
private static final int port = 22; // you should change from default(22)!
private static final String pass_phrase = "test"; // cl_private_key のパスフレーズ
private static final String cl_private_key = "-----BEGIN EC PRIVATE KEY-----\n" +
"Proc-Type: 4,ENCRYPTED\n" +
"DEK-Info: AES-256-CBC,0D2B70580F6876EEAF5E024E680F72D7\n" +
"\n" +
"wn5Cup9EGYSwJDxtZXahXyyYUJ9W5jR/SuZf9/peBi9ycJ6zZCvTT/+5EItmZ53f\n" +
"5eCN2NjN+CvG/nj+Y3KycfQK8lSVKZWzW8RnPL66PhZzMd8OA3JVSZZmOCrBLNEt\n" +
"K84EBLcsuAnIQ5kOpDlp3LK4NZarHSdQP6Kwj5Hehfw=\n" +
"-----END EC PRIVATE KEY-----\n";
// サーバに置く公開鍵の例(OpenSSH形式、ECDSA)
// ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKLiIIQ4Or7MvZgPUGIrhiQp8cEE/uj9KgR+bss2hBCqKjF0ZKTwcURSMyyuBbKnvccNTE2RtANg7KbExg6s0Lk= ecdsa
private final JSch jsch = new JSch();
private Session session;
private Channel channel;
private boolean isRun;
MyUserInfo myUserInfo = new MyUserInfo();
private Context context;
MySsh01(Context context){
this.context = context;
}
// とりあえずログは全部 StringBuffer に入れておく
private final StringBuffer buffer = new StringBuffer();
void log(String s){
System.out.println(s);
if(buffer.length()>0) buffer.append("\n");
buffer.append(s);
}
String getLog(){
return buffer.toString();
}
// Android ではネットワーク関連の処理は
// メインスレッドとは別スレッドで動かす必要がある
void run(){
if(isRun) return;
isRun = true;
new Thread(new Runnable() {
@Override
public void run() {
try {
sshThread();
}catch (Exception e){
log("Exception:" + e + ", cause:" + e.getCause());
e.printStackTrace();
}
isRun = false;
}
}).start();
}
// ssh 接続から、認証、切断までの例
private void sshThread(){
try {
log("start ssh");
buffer.setLength(0);
jsch.removeAllIdentity(); // 前回分を破棄
session = jsch.getSession(user, host, port);
// 接続処理を対話式にしたい場合は UserInfo を使う
session.setUserInfo(myUserInfo);
// userInfo を使わなくても可
//session.setPassword(pass_word);
// クライアント証明書を使う場合
// 直接String形式で JSch に渡せない(?)ので一時ファイルを経由する
log("make temporary file");
File tmp_pem;
{
tmp_pem = File.createTempFile("temp.pem", null, context.getCacheDir());
FileWriter fw2 = new FileWriter(tmp_pem, false);
BufferedWriter bw2 = new BufferedWriter(fw2);
PrintWriter pw2 = new PrintWriter(bw2);
log("cl_key:" + cl_private_key);
pw2.write(cl_private_key);
pw2.flush();
pw2.close();
}
jsch.addIdentity(tmp_pem.getPath());
// tmp_pem.delete();
// サーバ側の公開鍵を検証する場合は
// 取得済みの鍵を PUB_KEY_OF_SERVER として
//session.setConfig("StrictHostKeyChecking", "ask");
//jsch.getHostKeyRepository().add(new HostKey(host, PUB_KEY_OF_SERVER), null);
//log("sever_key:" + session.getHostKey().getKey()); // 必要に応じて取得して保存する
log("connect");
session.setTimeout(0);
session.connect();
log("exec");
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand("echo 'hello world'");
log("connect");
// xterm などを自力で実装する場合
//ChannelShell ch_shell;
//ch_shell = (ChannelShell) session.openChannel("shell");
//ch_shell.setPtyType("xterm");
//ch_shell.setPtySize(SCREEN_COLUMNS, SCREEN_LINES, SCREEN_WIDTH, SCREEN_HEIGHT); // 実際の画面サイズに合わせる
//InputStream out_shell = new PipedInputStream();
//PipeOutputStream ps = new PipedOutputStream((PipedInputStream) out_shell);
//ch_shell.setInputStream(out_shell);
//ps.write(TEXT_FROM_KEYBOARD.getBytes(Charset.forName("utf-8")));
// 〜
InputStream in = channel.getInputStream();
channel.connect();
log("connected");
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0)
break;
log(new String(tmp, 0, i));
}
if (channel.isClosed()) {
if (in.available() > 0)
continue;
log("exit-status: " + channel.getExitStatus());
break;
}
try {
Thread.sleep(100);
} catch (Exception e) {
// do nothing
}
}
channel.disconnect();
session.disconnect();
}
catch (Exception e){
log("Exception:" + e + ", cause:" + e.getCause());
e.printStackTrace();
}
}
// JSch が提供している ssh 接続・認証用のクラス
// implements して自環境に合わせた処理を実装する
private class MyUserInfo implements UserInfo {
@Override
public boolean promptPassword(String message){
// パスワード未設定で接続した時に
// 「Password for ユーザ名@ホスト名」 とかが message に渡されて呼ばれるので
// ユーザからパスワードを要求する処理をここのタイミングで入れる
log("promptPassword:" + message);
return true; // キャンセルなら false
}
@Override
public String getPassword() {
// 認証発生時に呼ばれるので、
// promptPassword などで取得したパスワードを returnする
// パスワード不一致の場合は promptPasswordが呼ばれる
//log("getPassword" + pass_word);
return pass_word;
}
@Override
public boolean promptPassphrase(String message){
log("promptPassphrase:" + message);
// クライアント証明書のパスフレーズを要求する処理
// 内容は promptPassword と同じ
return true; // キャンセルなら false
}
@Override
public String getPassphrase(){
// ここで返した証明書パスフレーズが不一致なら promptPassphraseが呼ばれる
log("getPassphrase:" + pass_phrase);
return pass_phrase;
}
@Override
public boolean promptYesNo(String message){
// これが呼ばれた理由は message に入るので、
// それをユーザに表示しつつ
// Yes(true) or No(false) を求めて、return で返す。
// 例として、StrictHostKeyChecking 設定で、鍵が不一致だった場合に
// 処理を続行するか中断するかを選ばせる、など
log("promptYesNo:" + message);
return true; // 拒否なら falseで
}
@Override
public void showMessage(String message){
// 接続時の「接続しました」的なメッセージなど
log("showMessage:" + message);
}
//public String[] promptKeyboardInteractive(String destination,
// String name,
// String instruction,
// String[] prompt,
// boolean[] echo){
// return null;
//}
}
}
上記の MySsh01クラスを初期化時に Context を渡して、 run() で実行して、getLog() で結果を取得します(あるいは logcat で確認)。 dependencies {
implementation 'com.jcraft:jsch:0.1.55'
// 手元のAndroid端末で、アプリのサイズが 5.8 MBくらい
//implementation 'com.github.mwiede:jsch:2.28.0'
// 手元のAndroi端末で、アプリのサイズが 10.1 MBくらい
}
jsch 1.55 だとED25519をはじめとした後発の暗号化方式には未対応です。 |
|