Flutter SSH连接插件dartssh2的使用

发布于 1周前 作者 sinazl 来自 Flutter

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客户端示例

SFTP示例

🔐 支持的算法

主机密钥

  • 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.

致谢

许可证

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

1 回复

更多关于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 可能会随着版本的更新而发生变化,因此请确保查阅最新的文档和示例代码以获取最准确的信息。

回到顶部