SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use.
dartssh2 is now a complete rewrite of dartssh.
- Pure Dart: Working with both Dart VM and Flutter.
- SSH Session: Executing commands, spawning shells, setting environment variables, pseudo terminals, etc.
- Authentication: Supports password, private key and interactive authentication method.
- Forwarding: Supports local forwarding and remote forwarding.
- SFTP: Supports all operations defined in SFTPv3 protocol including upload, download, list, link, remove, rename, etc.
ServerBox | NoPorts | DartShell |
![]() ![]() |
![]() |
![]() |
Feel free to add your own app here by opening a pull request.
# Install the `dartssh` command.
dart pub global activate dartssh2_cli
# Then use `dartssh` as regular `ssh` command.
dartssh [email protected]
# Example: execute a command on remote host.
dartssh [email protected] ls -al
# Example: connect to a non-standard port.
dartssh [email protected]:<port>
# Transfer files via SFTP.
dartsftp [email protected]
If the
dartssh
command can't be found after installation, you might need to set up your path.
void main() async {
final client = SSHClient(
await SSHSocket.connect('localhost', 22),
username: '<username>',
onPasswordRequest: () => '<password>',
);
}
SSHSocket
is an interface and it's possible to implement your ownSSHSocket
if you want to use a different underlying transport rather than standard TCP socket. For example WebSocket or Unix domain socket.
void main() async {
final shell = await client.shell();
stdout.addStream(shell.stdout); // listening for stdout
stderr.addStream(shell.stderr); // listening for stderr
stdin.cast<Uint8List>().listen(shell.write); // writing to stdin
await shell.done; // wait for shell to exit
client.close();
}
void main() async {
final uptime = await client.run('uptime');
print(utf8.decode(uptime));
}
Ignoring stderr:
void main() async {
final uptime = await client.run('uptime', stderr: false);
print(utf8.decode(uptime));
}
client.run()
is a convenience method that wrapsclient.execute()
for running non-interactive commands.
void main() async {
final session = await client.execute('cat > file.txt');
await session.stdin.addStream(File('local_file.txt').openRead().cast());
await session.stdin.close(); // Close the sink to send EOF to the remote process.
await session.done; // Wait for session to exit to ensure all data is flushed to the remote process.
print(session.exitCode); // You can get the exit code after the session is done
}
session.write()
is a shorthand forsession.stdin.add()
. It's recommended to usesession.stdin.addStream()
instead ofsession.write()
when you want to stream large amount of data to the remote process.
Killing a remote process by sending signal
void main() async {
session.kill(SSHSignal.KILL);
await session.done;
print('exitCode: ${session.exitCode}'); // -> exitCode: null
print('signal: ${session.exitSignal?.signalName}'); // -> signal: KILL
}
Processes killed by signals do not have an exit code, instead they have an exit signal property.
void main() async {
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);
}
}
void main() async {
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);
}
}
void main() async {
final client = SSHClient(
socket,
username: '<username>',
identities: [
// A single private key file may contain multiple keys.
...SSHKeyPair.fromPem(await File('path/to/id_rsa').readAsString())
],
);
}
void main() async {
// Test whether the private key is encrypted.
final encrypted = SSHKeyPair.isEncrypted(await File('path/to/id_rsa').readAsString());
print(encrypted);
// If the private key is encrypted, you need to provide the passphrase.
final keys = SSHKeyPair.fromPem('<pem text>', '<passphrase>');
print(keys);
}
Decrypt PEM file with compute
in Flutter
void main() async {
List<SSHKeyPair> decryptKeyPairs(List<String> args) {
return SSHKeyPair.fromPem(args[0], args[1]);
}
final keypairs = await compute(decryptKeyPairs, ['<pem text>', '<passphrase>']);
}
void main() async {
await client.authenticated;
print(client.remoteVersion); // SSH-2.0-OpenSSH_7.4p1
}
void main() async {
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>
}
}
void main() async {
final sftp = await client.sftp();
final items = await sftp.listdir('/');
for (final item in items) {
print(item.longname);
}
}
void main() async {
final sftp = await client.sftp();
final file = await sftp.open('/etc/passwd');
final content = await file.readBytes();
print(latin1.decode(content));
}
void main() async {
final sftp = await client.sftp();
final file = await sftp.open('file.txt', mode: SftpFileOpenMode.write);
await file.writeBytes(utf8.encode('hello there!') as Uint8List);
}
Write at specific offset
void main() async {
final data = utf8.encode('world') as Uint8List;
await file.writeBytes(data, offset: 6);
}
void main() async {
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());
}
void main() async {
final uploader = await file.write(File('local_file.txt').openRead().cast());
// ...
await uploader.pause();
// ...
await uploader.resume();
await uploader.done;
}
Clear the remote file before opening it
void main() async {
final file = await sftp.open('file.txt',
mode: SftpFileOpenMode.create | SftpFileOpenMode.truncate | SftpFileOpenMode.write
);
}
void main() async {
final sftp = await client.sftp();
await sftp.mkdir('/path/to/dir');
await sftp.rmdir('/path/to/dir');
}
void main() async {
await sftp.stat('/path/to/file');
await sftp.setStat(
'/path/to/file',
SftpFileAttrs(mode: SftpFileMode(userRead: true)),
);
}
void main() async {
final stat = await sftp.stat('/path/to/file');
print(stat.type);
// or
print(stat.isDirectory);
print(stat.isSocket);
print(stat.isSymbolicLink);
// ...
}
void main() async {
final sftp = await client.sftp();
sftp.link('/from', '/to');
}
void main() async {
final sftp = await client.sftp();
final statvfs = await sftp.statvfs('/root');
print('total: ${statvfs.blockSize * statvfs.totalBlocks}');
print('free: ${statvfs.blockSize * statvfs.freeBlocks}');
}
- example/example.dart
- example/execute.dart
- example/forward_local.dart
- example/forward_remote.dart
- example/pubkey.dart
- example/shell.dart
- example/ssh_jump.dart
- example/sftp_read.dart
- example/sftp_list.dart
- example/sftp_stat.dart
- example/sftp_upload.dart
- example/sftp_filetype.dart
Host key:
ssh-rsa
rsa-sha2-[256|512]
ecdsa-sha2-nistp[256|384|521]
ssh-ed25519
Key exchange:
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
Cipher:
aes[128|192|256]-ctr
aes[128|192|256]-cbc
Integrity:
hmac-md5
hmac-sha1
hmac-sha2-[256|512]
Private key:
Type | Decode | Decrypt | Encode | Encrypt |
---|---|---|---|---|
RSA | βοΈ | βοΈ | βοΈ | WIP |
OpenSSH RSA | βοΈ | βοΈ | βοΈ | WIP |
OpenSSH ECDSA | βοΈ | βοΈ | βοΈ | WIP |
OpenSSH Ed25519 | βοΈ | βοΈ | βοΈ | WIP |
- Fix broken tests.
- Sound null safety.
- Redesign API to allow starting multiple sessions.
- Full SFTP.
- Server.
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 is released under the terms of the MIT license. See LICENSE.