Dart SSH
last modified October 18, 2026
In this article we show how to work with SSH and SFTP in Dart language. We use the DartSSH 2 library.
Secure SHell (SSH) is a protocol which allows to create an encrypted channel between two networked hosts. Secure File Transfer Protocol (SFTP) is a network protocol for securely accessing, transferring and managing files. It runs on SSH.
There are two widely used ways of SSH authentication for secure remote access:
- password authentication (usernames and passwords)
- public key-based authentication (public and private key pairs)
The DartSSH 2 library provides an SSH and SFTP client written in pure Dart. It allows to create SSH sessions, provides authentication and forwarding. It supports all features of the SFTPv3 protocol, including upload, download, remove, rename, or delete.
$ dart pub add dartssh2
The examples connect to a remote machine, which the reader must provide. We use
the placeholder host 192.0.2.10 (an address reserved for
documentation) and the user name user7; replace them with your own
values. The program output shown below is an example; it depends on the remote
system. The examples read the password from the terminal; never store passwords
in source code.
Dart SSH execute command
In the next example, we execute a command remotely.
import 'dart:convert';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main() async {
const host = '192.0.2.10';
const username = 'user7';
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
final res = await client.run('uname -a');
print('');
print(utf8.decode(res));
client.close();
await client.done;
}
We connect to a computer on a local network and launch the uname
command.
import 'package:dartssh2/dartssh2.dart';
We import the dartssh2 library.
final socket = await SSHSocket.connect(host, 22);
A socket is created with SSHSocket.connect. Port 22 is the default
SSH port.
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
Using the socket, we create an SSH client. Upon connecting, the client will ask for a password. The password is not echoed; we restore the echo mode right after reading it.
final res = await client.run('uname -a');
We run the uname command. The run method waits for the
command to finish and returns its standard output as bytes.
print(utf8.decode(res));
We print the decoded response.
client.close();
The connection is terminated with close.
await client.done;
We wait until the transport is closed.
$ dart main.dart Password: Linux debian 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 x86_64 GNU/Linux
Dart SSH command output and exit code
The run method returns only the standard output. To get the
standard error and the exit code too, we use execute, which returns
an SSHSession.
import 'dart:convert';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main() async {
const host = '192.0.2.10';
const username = 'user7';
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
final session = await client.execute('ls /nonexistent');
final out = await utf8.decodeStream(session.stdout);
final err = await utf8.decodeStream(session.stderr);
await session.done;
print('stdout: "$out"');
print('stderr: "${err.trim()}"');
print('exit code: ${session.exitCode}');
client.close();
await client.done;
}
The session provides the stdout and stderr streams.
After the command finishes, the exitCode property holds its exit
status; a non-zero value signals a failure.
$ dart main.dart Password: stdout: "" stderr: "ls: cannot access '/nonexistent': No such file or directory" exit code: 2
Dart SSH shell
With the shell function, we can create an interactive shell
session.
import 'dart:io';
import 'dart:typed_data';
import 'package:dartssh2/dartssh2.dart';
void main() async {
const host = '192.0.2.10';
const username = 'user7';
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
final shell = await client.shell();
stdout.addStream(shell.stdout);
stderr.addStream(shell.stderr);
stdin.cast<Uint8List>().listen(shell.write);
await shell.done;
client.close();
await client.done;
exit(0);
}
The program creates an interactive shell session.
final shell = await client.shell();
We create a shell session on the remote host.
stdout.addStream(shell.stdout); stderr.addStream(shell.stderr); stdin.cast<Uint8List>().listen(shell.write);
We plug the remote standard streams to our local streams. Everything we type is sent to the remote shell.
await shell.done;
We wait until the shell terminates. We can terminate the shell with the
exit command. The final exit(0) is needed because the
listener on stdin would otherwise keep the program alive.
Dart SSH public key authentication
In the next example, we authenticate using public key authentication, which is more secure.
import 'dart:convert';
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main() async {
const host = '192.0.2.10';
const username = 'user7';
final home = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']!;
final keyFile = File('$home/.ssh/id_rsa');
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
identities: [
...SSHKeyPair.fromPem(await keyFile.readAsString(), 'passphrase'),
],
);
final uptime = await client.run('uptime');
print(utf8.decode(uptime));
client.close();
await client.done;
}
The program connects to the remote system, authenticates using public key
authentication, and executes the uptime command.
final home = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE']!;
final keyFile = File('$home/.ssh/id_rsa');
This is the location of the private key. The home directory is taken from the
HOME environment variable (USERPROFILE on Windows).
identities: [ ...SSHKeyPair.fromPem(await keyFile.readAsString(), 'passphrase'), ],
We pass the private key to the SSHKeyPair.fromPem function. The
second argument is the passphrase; it is needed only when the key is encrypted,
so for an unprotected key we omit it. Also note that the remote system must
contain our public key in the ~/.ssh/authorized_keys file.
final uptime = await client.run('uptime');
print(utf8.decode(uptime));
We run the command and print the decoded response.
$ dart main.dart 06:45:06 up 30 min, 1 user, load average: 0.00, 0.00, 0.00
Dart SFTP list directory
In the following example, we list directory contents via SFTP.
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main(List<String> args) async {
const host = '192.0.2.10';
const username = 'user7';
final dirname = args.first;
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
final sftp = await client.sftp();
final items = await sftp.listdir(dirname);
print('');
for (final item in items) {
print(item.longname);
}
client.close();
await client.done;
}
The program accepts an argument, which is a path to the directory, such as
/home/user7/projects.
final dirname = args.first;
We get the first command-line argument. It is expected to be the path to the remote directory.
final sftp = await client.sftp(); final items = await sftp.listdir(dirname);
An SFTP client is created with the sftp function. The directory
listing is retrieved with listdir, which returns a list of
SftpName objects.
for (final item in items) {
print(item.longname);
}
We go through the list and print the long names, which have the format of the
ls -l command. The filename property contains just
the name and attr has the size, permissions, and times.
$ dart main.dart /home/user7/projects Password: drwxr-xr-x 5 user7 user7 4096 Oct 18 20:00 . drwx------ 56 user7 user7 4096 Oct 11 16:39 .. drwxr-xr-x 2 user7 user7 4096 Oct 4 12:31 dbi drwxr-xr-x 2 user7 user7 4096 Oct 4 12:31 hash drwxr-xr-x 2 user7 user7 4096 Oct 19 13:39 loops drwxr-xr-x 2 user7 user7 4096 Oct 18 20:00 sockets
Dart SFTP make directory
The mkdir creates a new directory. Other useful methods are
remove (a file), rmdir (a directory),
rename, and stat.
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main(List<String> args) async {
const host = '192.0.2.10';
const username = 'user7';
final dirname = args.first;
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
final sftp = await client.sftp();
await sftp.mkdir(dirname);
print('');
print('directory created');
final stat = await sftp.stat(dirname);
print('is directory: ${stat.isDirectory}');
client.close();
await client.done;
}
The program creates a new directory on the remote system. The directory name
is passed as the first command-line argument. After the creation, we verify
it with stat.
$ dart main.dart /home/user7/newdir Password: directory created is directory: true
Dart SFTP upload file
The open function opens a remote file.
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main(List<String> args) async {
const host = '192.0.2.10';
const username = 'user7';
final lname = args.first;
final rname = args.last;
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? 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());
await file.close();
print('');
print('file successfully uploaded');
client.close();
await client.done;
}
The program uploads a file to a remote system.
final lname = args.first; final rname = args.last;
We have two command-line parameters. The first is the local filename to be uploaded and the second is the full path of the remote file.
final file = await sftp.open(
rname,
mode: SftpFileOpenMode.truncate |
SftpFileOpenMode.create |
SftpFileOpenMode.write,
);
We open a file in the write mode on the remote system with open.
The file is truncated if it exists, or created if it does not.
await file.write(File(lname).openRead().cast()); await file.close();
The data is streamed from the local file to the remote file, so even large files do not have to fit into memory. We close the remote file to make sure all data has been written.
$ dart main.dart report.pdf /home/user7/report.pdf Password: file successfully uploaded
Dart SFTP download file
The following program downloads a file.
import 'dart:io';
import 'package:dartssh2/dartssh2.dart';
void main(List<String> args) async {
const host = '192.0.2.10';
const username = 'user7';
final rname = args.first;
final lname = args.last;
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: username,
onPasswordRequest: () {
stdout.write('Password: ');
stdin.echoMode = false;
final password = stdin.readLineSync();
stdin.echoMode = true;
return password ?? exit(1);
},
);
final sftp = await client.sftp();
final rfile = await sftp.open(rname);
final data = await rfile.readBytes();
await rfile.close();
await File(lname).writeAsBytes(data);
print('');
print('file downloaded (${data.length} bytes)');
client.close();
await client.done;
}
The program is similar to the previous one; we again utilize the
open function.
final rname = args.first; final lname = args.last;
The program takes two parameters. The first is the full path to the remote file
(such as /home/user7/report.pdf), and the second is the local
filename.
final rfile = await sftp.open(rname);
We open the remote file. The default mode is read-only.
final data = await rfile.readBytes();
We read the whole file as bytes with readBytes.
await File(lname).writeAsBytes(data);
We write the bytes to the local file. Writing bytes, rather than decoding them to a string, makes the program work for binary files too.
$ dart main.dart /home/user7/report.pdf report-copy.pdf Password: file downloaded (48213 bytes)
Dart SSH host keys
By default, the client accepts any host key. To protect against
man-in-the-middle attacks, we should verify the fingerprint of the server's key
with the onVerifyHostKey callback.
import 'package:dartssh2/dartssh2.dart';
void main() async {
const host = '192.0.2.10';
const trusted = '3f:2a:...';
final socket = await SSHSocket.connect(host, 22);
final client = SSHClient(
socket,
username: 'user7',
onVerifyHostKey: (type, fingerprint) {
final hex = fingerprint
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join(':');
print('$type $hex');
return hex == trusted;
},
);
await client.authenticated;
client.close();
}
The callback receives the key type and the MD5 fingerprint of the key. Returning
false aborts the connection. We compare the fingerprint to a value
we obtained from the administrator of the server, for instance with
ssh-keygen -l -E md5 -f ssh_host_ed25519_key.pub. The
trusted value above is a placeholder.
Source
In this article we have worked with SSH and SFTP in Dart.
Author
List all Dart tutorials.