Published 2025-10-03
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.
| ServerBox | Ssh! No Ports | 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 user@example.com
# Example: execute a command on remote host.
dartssh user@example.com ls -al
# Example: connect to a non-standard port.
dartssh user@example.com:<port>
# Transfer files via SFTP.
dartsftp user@example.com
If the
dartsshcommand can't be found after installation, you might need to set up your path.
final client = SSHClient(
await SSHSocket.connect('localhost', 22),
username: '<username>',
onPasswordRequest: () => '<password>',
);
SSHSocketis an interface and it's possible to implement your ownSSHSocketif you want to use a different underlying transport rather than standard TCP socket. For example WebSocket or Unix domain socket.
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();
final uptime = await client.run('uptime');
print(utf8.decode(uptime));
Ignoring stderr:
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.
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
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.
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);
}
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: [
// A single private key file may contain multiple keys.
...SSHKeyPair.fromPem(await File('path/to/id_rsa').readAsString())
],
);
// 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
List<SSHKeyPair> decryptKeyPairs(List<String> args) {
return SSHKeyPair.fromPem(args[0], args[1]);
}
final keypairs = await compute(decryptKeyPairs, ['<pem text>', '<passphrase>']);
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>
}
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);
Write at specific offset
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;
Clear the remote file before opening it
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);
// or
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}');
Host key:
ssh-rsarsa-sha2-[256|512]ecdsa-sha2-nistp[256|384|521]ssh-ed25519Key exchange:
curve25519-sha256ecdh-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]-ctraes[128|192|256]-cbcIntegrity:
hmac-md5hmac-sha1hmac-sha2-[256|512]Private key:
| Type | Decode | Decrypt | Encode | Encrypt |
|---|---|---|---|---|
| RSA | โ๏ธ | โ๏ธ | โ๏ธ | WIP |
| OpenSSH RSA | โ๏ธ | โ๏ธ | โ๏ธ | WIP |
| OpenSSH ECDSA | โ๏ธ | โ๏ธ | โ๏ธ | WIP |
| OpenSSH Ed25519 | โ๏ธ | โ๏ธ | โ๏ธ | WIP |
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 is released under the terms of the MIT license. See LICENSE.
SSHForwardChannel.sink to StreamSink<List<int>> to match
its super class.SSHHttpClient for easy http request forwarding.SSHForwardChannel implement SSHSocket for better interoperability.SftpFileWriter implement Future<void> for backward compatibility.SftpFileWriterSftpFile.write now returns a SftpFileWriter that can be used to control
the writing process.SftpClient.statvfs and SftpFile.statvfs.pinenacl to 0.5.0.@ in dartssh2 command #24SSHClient.run.SSHAuthFailError and SSHAuthAbortError.kill() #17dartsftp commanddartssh command now supports login with public key.dartssh command now supports terminal window resize.--verbose option in dartssh command.pedantic with package:lints