Flutter SSH连接插件dartssh2的使用
Flutter SSH连接插件dartssh2的使用
✨ 特性
- 纯Dart:适用于Dart VM和Flutter。
- SSH会话:执行命令、启动shell、设置环境变量、伪终端等。
- 认证:支持密码、私钥和交互式认证方法。
- 转发:支持本地转发和远程转发。
- SFTP:支持所有在SFTPv3协议中定义的操作,包括上传、下载、列出、链接、删除、重命名等。
🧬 使用dartssh2构建的应用程序
应用名称 | 描述 |
---|---|
ServerBox | 服务器管理工具 |
Ssh! No Ports | 不需要开放端口即可进行SSH连接 |
DartShell | 提供终端和会话信息的SSH操作 |
🧪 尝试使用
# 安装`dartssh`命令
dart pub global activate dartssh2_cli
# 使用`dartssh`作为常规`ssh`命令
dartssh user@example.com
# 示例:在远程主机上执行命令
dartssh user@example.com ls -al
# 示例:连接到非标准端口
dartssh user@example.com:<port>
# 通过SFTP传输文件
dartsftp user@example.com
如果安装后找不到dartssh
命令,请参考设置路径。
🚀 快速开始
连接到远程主机
final client = SSHClient(
await SSHSocket.connect('localhost', 22),
username: '<username>',
onPasswordRequest: () => '<password>',
);
在远程主机上启动shell
final shell = await client.shell();
stdout.addStream(shell.stdout); // 监听stdout
stderr.addStream(shell.stderr); // 监听stderr
stdin.cast<Uint8List>().listen(shell.write); // 写入stdin
await shell.done; // 等待shell退出
client.close();
在远程主机上执行命令
final uptime = await client.run('uptime');
print(utf8.decode(uptime));
忽略stderr:
final uptime = await client.run('uptime', stderr: false);
print(utf8.decode(uptime));
在远程主机上启动进程
final session = await client.execute('cat > file.txt');
await session.stdin.addStream(File('local_file.txt').openRead().cast());
await session.stdin.close(); // 关闭sink以向远程进程发送EOF
await session.done; // 等待会话退出以确保所有数据已刷新到远程进程
print(session.exitCode); // 可以在会话完成后获取退出代码
杀死远程进程:
session.kill(SSHSignal.KILL);
await session.done;
print('exitCode: ${session.exitCode}'); // -> exitCode: null
print('signal: ${session.exitSignal?.signalName}'); // -> signal: KILL
通过本地端口8080转发连接到服务器
final serverSocket = await ServerSocket.bind('localhost', 8080);
await for (final socket in serverSocket) {
final forward = await client.forwardLocal('httpbin.org', 80);
forward.stream.cast<List<int>>().pipe(socket);
socket.pipe(forward.sink);
}
将连接转发到服务器上的端口2222到本地端口22
final forward = await client.forwardRemote(port: 2222);
if (forward == null) {
print('Failed to forward remote port');
return;
}
await for (final connection in forward.connections) {
final socket = await Socket.connect('localhost', 22);
connection.stream.cast<List<int>>().pipe(socket);
socket.pipe(connection.sink);
}
使用公钥认证
final client = SSHClient(
socket,
username: '<username>',
identities: [
...SSHKeyPair.fromPem(await File('path/to/id_rsa').readAsString())
],
);
使用加密的PEM文件
// 测试私钥是否加密
final encrypted = SSHKeyPair.isEncrypted(await File('path/to/id_rsa').readAsString());
print(encrypted);
// 如果私钥加密,则需要提供密码短语
final keys = SSHKeyPair.fromPem('<pem text>', '<passphrase>');
print(keys);
在Flutter中使用compute
解密PEM文件:
List<SSHKeyPair> decryptKeyPairs(List<String> args) {
return SSHKeyPair.fromPem(args[0], args[1]);
}
final keypairs = await compute(decryptKeyPairs, ['<pem text>', '<passphrase>']);
获取SSH服务器版本
await client.authenticated;
print(client.remoteVersion); // SSH-2.0-OpenSSH_7.4p1
通过跳转服务器连接
final jumpServer = SSHClient(
await SSHSocket.connect('<jump server>', 22),
username: '...',
onPasswordRequest: () => '...',
);
final client = SSHClient(
await jumpServer.forwardLocal('<target server>', 22),
username: '...',
onPasswordRequest: () => '...',
);
print(utf8.decode(await client.run('hostname'))); // -> hostname of <target server>
SFTP
列出远程目录
final sftp = await client.sftp();
final items = await sftp.listdir('/');
for (final item in items) {
print(item.longname);
}
读取远程文件
final sftp = await client.sftp();
final file = await sftp.open('/etc/passwd');
final content = await file.readBytes();
print(latin1.decode(content));
写入远程文件
final sftp = await client.sftp();
final file = await sftp.open('file.txt', mode: SftpFileOpenMode.write);
await file.writeBytes(utf8.encode('hello there!') as Uint8List);
在特定偏移量写入:
final data = utf8.encode('world') as Uint8List
await file.writeBytes(data, offset: 6);
文件上传
final sftp = await client.sftp();
final file = await sftp.open('file.txt', mode: SftpFileOpenMode.create | SftpFileOpenMode.write);
await file.write(File('local_file.txt').openRead().cast());
暂停和恢复文件上传:
final uploader = await file.write(File('local_file.txt').openRead().cast());
// ...
await uploader.pause();
// ...
await uploader.resume();
await uploader.done;
清除远程文件后再打开:
final file = await sftp.open('file.txt',
mode: SftpFileOpenMode.create | SftpFileOpenMode.truncate | SftpFileOpenMode.write
);
目录操作
final sftp = await client.sftp();
await sftp.mkdir('/path/to/dir');
await sftp.rmdir('/path/to/dir');
获取/设置远程文件/目录属性
await sftp.stat('/path/to/file');
await sftp.setStat(
'/path/to/file',
SftpFileAttrs(mode: SftpFileMode(userRead: true)),
);
获取远程文件类型
final stat = await sftp.stat('/path/to/file');
print(stat.type);
// 或者
print(stat.isDirectory);
print(stat.isSocket);
print(stat.isSymbolicLink);
// ...
创建链接
final sftp = await client.sftp();
sftp.link('/from', '/to');
获取远程文件系统的(估计)总空间和可用空间
final sftp = await client.sftp();
final statvfs = await sftp.statvfs('/root');
print('total: ${statvfs.blockSize * statvfs.totalBlocks}');
print('free: ${statvfs.blockSize * statvfs.freeBlocks}');
🪜 示例
SSH客户端示例
- example/example.dart
- example/execute.dart
- example/forward_local.dart
- example/forward_remote.dart
- example/pubkey.dart
- example/shell.dart
- example/ssh_jump.dart
SFTP示例
- example/sftp_read.dart
- example/sftp_list.dart
- example/sftp_stat.dart
- example/sftp_upload.dart
- example/sftp_filetype.dart
🔐 支持的算法
主机密钥
ssh-rsa
rsa-sha2-[256|512]
ecdsa-sha2-nistp[256|384|521]
ssh-ed25519
密钥交换
curve25519-sha256
ecdh-sha2-nistp[256|384|521]
diffie-hellman-group-exchange-sha[1|256]
diffie-hellman-group14-sha[1|256]
diffie-hellman-group1-sha1
加密
aes[128|192|256]-ctr
aes[128|192|256]-cbc
完整性
hmac-md5
hmac-sha1
hmac-sha2-[256|512]
私钥
类型 | 解码 | 解密 | 编码 | 加密 |
---|---|---|---|---|
RSA | ✔️ | ✔️ | ✔️ | WIP |
OpenSSH RSA | ✔️ | ✔️ | ✔️ | WIP |
OpenSSH ECDSA | ✔️ | ✔️ | ✔️ | WIP |
OpenSSH Ed25519 | ✔️ | ✔️ | ✔️ | WIP |
⏳ 路线图
- ✅ 修复损坏的测试。
- ✅ 声明空安全。
- ✅ 重新设计API以允许启动多个会话。
- ✅ 完整的SFTP。
- ❌ 服务器。
参考文献
- RFC 4250 The Secure Shell (SSH) Protocol Assigned Numbers.
- RFC 4251 The Secure Shell (SSH) Protocol Architecture.
- RFC 4252 The Secure Shell (SSH) Authentication Protocol.
- RFC 4253 The Secure Shell (SSH) Transport Layer Protocol.
- RFC 4254 The Secure Shell (SSH) Connection Protocol.
- RFC 4255 Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints.
- RFC 4256 Generic Message Exchange Authentication for the Secure Shell Protocol (SSH).
- RFC 4419 Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol.
- RFC 4716 The Secure Shell (SSH) Public Key File Format.
- RFC 5656 Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer.
- RFC 8332 Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol.
- RFC 8731 Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448.
- draft-miller-ssh-agent-03 SSH Agent Protocol.
- draft-ietf-secsh-filexfer-02 SSH File Transfer Protocol.
- draft-dbider-sha2-mac-for-ssh-06 SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol.
致谢
- https://github.com/GreenAppers/dartssh by GreenAppers.
许可证
dartssh遵循MIT许可证。参见LICENSE。
完整示例demo
下面是一个完整的示例,展示了如何使用dartssh2
插件在Flutter应用程序中执行SSH命令并处理结果:
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:dartssh2/dartssh2.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SSH Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SSHExamplePage(),
);
}
}
class SSHExamplePage extends StatefulWidget {
@override
_SSHExamplePageState createState() => _SSHExamplePageState();
}
class _SSHExamplePageState extends State<SSHExamplePage> {
String _output = '';
Future<void> _runCommand() async {
try {
final socket = await SSHSocket.connect('localhost', 22);
final client = SSHClient(
socket,
username: 'root',
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
return stdin.readLineSync() ?? exit(1);
},
);
final uptime = await client.run('uptime');
setState(() {
_output = utf8.decode(uptime);
});
client.close();
await client.done;
} catch (e) {
setState(() {
_output = 'Error: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SSH Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: _runCommand,
child: Text('Run Command'),
),
SizedBox(height: 20),
Text(_output),
],
),
),
);
}
}
此示例创建了一个简单的Flutter应用程序,用户点击按钮后,将尝试连接到SSH服务器并执行uptime
命令,然后显示输出结果。请根据实际情况调整主机名、用户名和密码。
更多关于Flutter SSH连接插件dartssh2的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter SSH连接插件dartssh2的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个使用 dartssh2
插件在 Flutter 中实现 SSH 连接的示例代码。dartssh2
是一个 Dart 库,用于在 Dart 应用程序中通过 SSH 协议连接到远程服务器。
首先,确保你在 pubspec.yaml
文件中添加了 dartssh2
依赖:
dependencies:
flutter:
sdk: flutter
dartssh2: ^x.y.z # 请将 x.y.z 替换为最新版本号
然后,运行 flutter pub get
来获取依赖。
以下是一个简单的 Flutter 应用示例,它展示了如何使用 dartssh2
连接到 SSH 服务器并执行命令:
import 'package:flutter/material.dart';
import 'package:dartssh2/dartssh2.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SSH Connection Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SSHConnectionScreen(),
);
}
}
class SSHConnectionScreen extends StatefulWidget {
@override
_SSHConnectionScreenState createState() => _SSHConnectionScreenState();
}
class _SSHConnectionScreenState extends State<SSHConnectionScreen> {
final TextEditingController _hostController = TextEditingController();
final TextEditingController _portController = TextEditingController(text: '22');
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final TextEditingController _commandController = TextEditingController(text: 'ls -l');
String _result = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SSH Connection Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
controller: _hostController,
decoration: InputDecoration(labelText: 'Host'),
),
TextField(
controller: _portController,
decoration: InputDecoration(labelText: 'Port'),
keyboardType: TextInputType.number,
),
TextField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
TextField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
TextField(
controller: _commandController,
decoration: InputDecoration(labelText: 'Command'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _connectAndExecuteCommand,
child: Text('Connect and Execute'),
),
SizedBox(height: 20),
Text('Result:\n$_result'),
],
),
),
);
}
Future<void> _connectAndExecuteCommand() async {
setState(() {
_result = '';
});
String host = _hostController.text;
int port = int.parse(_portController.text);
String username = _usernameController.text;
String password = _passwordController.text;
String command = _commandController.text;
try {
SSHClient ssh = SSHClient();
await ssh.connect(host, port: port);
await ssh.authPassword(username, password);
SSHChannel channel = await ssh.startSession();
SSHChannelExec exec = await channel.exec(command);
List<int> data = await exec.readToEnd();
String result = String.fromCharCodes(data);
setState(() {
_result = result;
});
await exec.close();
await channel.close();
await ssh.disconnect();
} catch (e) {
setState(() {
_result = 'Error: ${e.toString()}';
});
}
}
}
在这个示例中,我们创建了一个简单的 Flutter 界面,允许用户输入 SSH 连接所需的主机、端口、用户名、密码和要执行的命令。点击按钮后,应用将尝试连接到指定的 SSH 服务器,并执行命令,然后将结果显示在屏幕上。
请注意,dartssh2
插件的 API 可能会随着版本的更新而发生变化,因此请确保查阅最新的文档和示例代码以获取最准确的信息。