ZetCode

Dart SSH

最后修改日期:2024 年 1 月 28 日

本文介绍如何使用 Dart 语言操作 SSH 和 SFTP。我们使用 DartSSH 2 库。

安全外壳协议 (SSH) 是一种在两个网络主机之间创建加密通道的协议。 安全文件传输协议 (SFTP) 是一种用于安全访问、传输和管理文件的网络协议。它运行在 SSH 之上。

SSH 身份验证主要有两种方式,用于安全的远程访问:

DartSSH 2 库提供了一个用纯 Dart 编写的 SSH 和 SFTP 客户端。它允许创建 SSH 会话、提供身份验证和转发。它支持 SFTPv3 协议的所有功能,包括上传、下载、删除、重命名或移除。

$ dart pub add dartssh2

Dart SSH 执行命令

在下一个示例中,我们将远程执行一个命令。

main.dart
import 'dart:convert';
import 'dart:io';

import 'package:dartssh2/dartssh2.dart';

void main() async {
  final ip = "192.168.0.25";
  final username = "user7";

  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    onPasswordRequest: () {
      stdout.write('Password: ');
      stdin.echoMode = false;
      return stdin.readLineSync() ?? exit(1);
    },
  );

  final res = await client.run('uname -a');

  print("\n");
  print(utf8.decode(res));

  client.close();
  await client.done;
}

我们连接到本地网络中的一台计算机并运行 uname 命令。

import 'package:dartssh2/dartssh2.dart';

我们导入 dartssh2 库。

final socket = await SSHSocket.connect(ip, 22);

使用 SSHSocket.connect 创建一个套接字。

final client = SSHClient(
  socket,
  username: username,
  onPasswordRequest: () {
    stdout.write('Password: ');
    stdin.echoMode = false;
    return stdin.readLineSync() ?? exit(1);
  },
);

使用该套接字,我们创建一个 SSH 客户端。连接后,客户端会要求输入密码。密码不会显示出来。

final res = await client.run('uname -a');

我们运行 uname 命令。

print(utf8.decode(res));

我们打印解码后的响应。

client.close();

使用 close 终止连接。

await client.done;

我们等待传输关闭。

$ dart main.dart
Password:

Linux debian 4.19.0-23-amd64 #1 SMP Debian 4.19.269-1 ...

Dart SSH shell

使用 shell 函数,我们可以创建一个交互式 shell 会话。

main.dart
import 'dart:io';
import 'dart:typed_data';

import 'package:dartssh2/dartssh2.dart';

void main() async {
  final ip = "192.168.0.25";
  final username = "user7";

  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    onPasswordRequest: () {
      stdout.write('Password: ');
      stdin.echoMode = false;
      return stdin.readLineSync() ?? exit(1);
    },
  );

  final shell = await client.shell();
  stdin.echoMode = true;

  stdout.addStream(shell.stdout);
  stderr.addStream(shell.stderr);
  stdin.cast<Uint8List>().listen(shell.write);

  await shell.done;

  client.close();
  await client.done;

  exit(0);
}

程序创建一个交互式 shell 会话。

final shell = await client.shell();
stdin.echoMode = true;

我们创建一个 shell 会话并开启回显模式,以便我们可以看到我们输入的内容。

stdout.addStream(shell.stdout);
stderr.addStream(shell.stderr);
stdin.cast<Uint8List>().listen(shell.write);

我们将远程标准流连接到我们的本地流。

await shell.done;

我们等待 shell 终止。我们可以使用 exit 命令终止 shell。

Dart SSH 公钥身份验证

在下一个示例中,我们将使用更安全的公钥身份验证方式进行身份验证。

main.dart
import 'dart:convert';
import 'dart:io';

import 'package:dartssh2/dartssh2.dart';

void main() async {
  final ip = "192.168.0.25";
  final username = "user7";
  final rsa_key = "C:\\Users\\Jano\\.ssh\\id_rsa";

  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    identities: [
      ...SSHKeyPair.fromPem(await File(rsa_key).readAsString(), 'passphrase')
    ],
  );

  final uptime = await client.run('uptime');
  print(utf8.decode(uptime));

  client.close();
  await client.done;
}

程序连接到远程系统,使用公钥身份验证进行身份验证,并执行 uptime 命令。

final rsa_key = "C:\\Users\\Jano\\.ssh\\id_rsa";

这是我们 Windows 操作系统上 RSA 私钥的位置。

final client = SSHClient(
  socket,
  username: username,
  identities: [
    ...SSHKeyPair.fromPem(await File(rsa_key).readAsString(), 'passphrase')
  ],
);

我们将私钥传递给 SSHKeyPair.fromPem 函数。密钥也可能受密码保护。另外请注意,远程系统必须在 ~/.ssh/authorized_keys 文件中包含我们的公钥。

final uptime = await client.run('uptime');
print(utf8.decode(uptime));

我们运行命令并打印解码后的响应。

$ dart main.dart
06:45:06 up 30 min,  1 user,  load average: 0.00, 0.00, 0.00

Dart SFTP 列出目录

在下面的示例中,我们将通过 SFTP 列出目录内容。

main.dart
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';

void main(List<String> args) async {
  final ip = "93.184.216.34";
  final username = "user7";

  final dirname = args.first;
  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    onPasswordRequest: () {
      stdout.write('Password: ');
      stdin.echoMode = false;
      return stdin.readLineSync() ?? exit(1);
    },
  );

  final sftp = await client.sftp();
  final items = await sftp.listdir(dirname);

  print("\n");

  for (final item in items) {
    print(item.longname);
  }

  client.close();
  await client.done;
}

程序接受一个参数,该参数是目录的路径,例如 /web/perl/

final dirname = args.first;

我们获取第一个命令行参数。预期它是远程目录的路径。

final sftp = await client.sftp();
final items = await sftp.listdir(dirname);

使用 sftp 函数创建一个 SFTP 客户端。使用 listdir 检索目录列表。

for (final item in items) {
  print(item.longname);
}

我们遍历路径名列表并打印它们。

$ dart main.dart /web/perl
Password: 

drwx---r-x   12 user7 117992         12 Jan 23 20:00 .
drwx------   56 user7 117992         61 Jan 11 16:39 ..
drwx---r-x    2 user7 117992          3 Jan  4 12:31 string2
drwx---r-x    2 user7 117992          3 Jan  4 12:31 dbi
drwx---r-x    2 user7 117992          3 Jan  4 12:31 string
drwx---r-x    2 user7 117992          3 Jan  4 12:31 hash
drwxr-xr-x    2 user7 117992          3 Jan 19 13:39 read-file
drwxr-xr-x    2 user7 117992          3 Jan 23 20:00 loops
drwxr-xr-x    2 user7 117992          3 Jan  4 12:31 grep
drwx---r-x    2 user7 117992          3 Jan  4 12:31 array
drwx---r-x    2 user7 117992          3 Jan  4 12:31 socket
drwx---r-x    2 user7 117992          3 Jan  4 12:31 lwp

Dart SFTP 创建目录

mkdir 用于创建一个新目录。

main.dart
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';

void main(List<String> args) async {
  final ip = "93.184.216.34";
  final username = "user7";
  
  final dirname = args.first;

  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    onPasswordRequest: () {
      stdout.write('Password: ');
      stdin.echoMode = false;
      return stdin.readLineSync() ?? exit(1);
    },
  );

  final sftp = await client.sftp();
  await sftp.mkdir(dirname);

  print('directory created');

  client.close();
  await client.done;
}

程序在远程系统上创建一个新目录。目录名称作为第一个命令行参数传递。

Dart SFTP 上传文件

open 函数用于打开一个远程文件。

main.dart
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';

void main(List<String> args) async {
  final ip = "93.184.216.34";
  final username = "user7";
  
  final lname = args.first;
  final rname = args.last;

  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    onPasswordRequest: () {
      stdout.write('Password: ');
      stdin.echoMode = false;
      return stdin.readLineSync() ?? exit(1);
    },
  );

  final sftp = await client.sftp();
  final file = await sftp.open(
    rname,
    mode: SftpFileOpenMode.truncate |
        SftpFileOpenMode.create |
        SftpFileOpenMode.write,
  );

  await file.write(File(lname).openRead().cast());

  print("\n");
  print('file successfully uploaded');

  client.close();
  await client.done;
}

程序将一个文件上传到远程系统。

final lname = args.first;
final rname = args.last;

我们有两个命令行参数。第一个是要上传的本地文件名,第二个是远程文件的完整路径。

final file = await sftp.open(
  rname,
  mode: SftpFileOpenMode.truncate |
      SftpFileOpenMode.create |
      SftpFileOpenMode.write,
);

我们以写入模式在远程系统上使用 open 打开文件。如果文件存在,则会被截断,如果不存在,则会被创建。

  await file.write(File(lname).openRead().cast());

数据从本地文件读取并写入远程文件。

Dart SFTP 下载文件

以下程序用于下载文件。

main.dart
import 'dart:convert';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';

void main(List<String> args) async {
  final ip = "93.184.216.34";
  final username = "user7";
  
  final rname = args.first;
  final lname = args.last;

  final socket = await SSHSocket.connect(ip, 22);

  final client = SSHClient(
    socket,
    username: username,
    onPasswordRequest: () {
      stdout.write('Password: ');
      stdin.echoMode = false;
      return stdin.readLineSync() ?? exit(1);
    },
  );

  final sftp = await client.sftp();
  final rfile = await sftp.open(
    rname,
    mode: SftpFileOpenMode.read,
  );

  final data = await rfile.readBytes();

  final lfile = File(lname).openWrite();
  lfile.write(utf8.decode(data));

  print('file downloaded');

  client.close();
  await client.done;
}

程序与上一个类似;我们再次利用 open 函数。

final rname = args.first;
final lname = args.last;

程序接受两个参数。第一个是远程文件的完整路径(例如 /web/dart/ssh/index.html),第二个是本地文件名。

final rfile = await sftp.open(
  rname,
  mode: SftpFileOpenMode.read,
);

我们以读取模式打开远程文件。

final data = await rfile.readBytes();

我们使用 readBytes 读取字节。

final lfile = File(lname).openWrite();
lfile.write(utf8.decode(data));

我们将解码后的数据写入本地文件。

来源

dartssh2 文档

在本文中,我们已使用 Dart 操作了 SSH 和 SFTP。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在教学编程方面拥有十多年的经验。

列出 所有 Dart 教程