Ciphers in SSH & FTP

Understanding Ciphers in SSH & FTP: Why Outdated Ciphers Are Dangerous and How to Detect Them

Introduction

Cryptography sits at the heart of secure communication. Every time you log into a server using SSH or transfer files over FTPS/SFTP, a cipher ensures your data remains protected.

But not all ciphers are equal. Some modern ciphers provide strong confidentiality and integrity, while older ones expose organisations to serious risk.

What This Article Covers

  • What ciphers are used for
  • How they operate in SSH and FTP
  • Why older ciphers (DES, 3DES, RC4, CBC modes) are dangerous
  • Real attack scenarios where weak ciphers can be exploited
  • Security risks of outdated cryptography
  • A Python script to check what cipher your server is actually using

What is a Cipher Used For?

A cipher is a mathematical algorithm that transforms plaintext into ciphertext and back again using encryption keys. Its core goals are:

Confidentiality

So attackers cannot read intercepted data.

Integrity

So attackers cannot alter data unnoticed.

Authenticity

So users and services trust who they’re communicating with.

Modern systems rely heavily on symmetric ciphers like AES, and asymmetric algorithms like RSA and Elliptic Curve Cryptography.

How Ciphers Are Used in SSH

SSH uses ciphers in three main phases:

1. Cipher Negotiation

The client and server exchange their supported cryptographic algorithms:

  • Key exchange methods
  • Host key algorithms
  • Data encryption ciphers
  • MAC/AEAD algorithms

Then both sides agree on one cipher suite for the session.

2. Data Encryption

Once negotiated, SSH encrypts:

  • Commands
  • Keystrokes
  • Passwords
  • File transfers (SCP/SFTP)
  • Terminal output

Everything inside the SSH tunnel is encrypted.

3. Integrity Protection

SSH uses:

  • AEAD modes (AES-GCM, ChaCha20-Poly1305), or
  • MAC algorithms (HMAC-SHA2)

to prevent tampering and injection attacks.

How Ciphers Are Used in FTP (FTP, FTPS, and SFTP)

Traditional FTP is NOT encrypted. So two secure alternatives emerged:

FTPS (FTP over TLS)

FTPS uses TLS/SSL to protect connections, just like HTTPS.

TLS provides:

  • Server authentication
  • Encryption (AES-GCM, ChaCha20-Poly1305)
  • Integrity protection

Both the command channel and data channel can be encrypted.

Examples of strong TLS ciphers:

  • TLS_AES_256_GCM_SHA384
  • ECDHE-RSA-AES128-GCM-SHA256

SFTP (SSH File Transfer Protocol)

Not related to FTP at all. It is a file-transfer protocol tunneled through SSH, so it uses SSH ciphers (AES-CTR, AES-GCM, ChaCha20-Poly1305).

SFTP inherits:

  • SSH encryption
  • SSH integrity protections
  • SSH authentication

This makes it generally more secure and simpler to configure than FTPS.

Why Old Ciphers Are Dangerous

Many older ciphers were considered safe decades ago—but today they are broken or severely weakened.

DES / 3DES (Triple DES) – Weak Keys

Vulnerability: Too small key size

  • DES uses 56-bit keys – broken by brute force long ago
  • 3DES uses 112-bit effective security – borderline today
  • 3DES is vulnerable to the Sweet32 attack

Real Exploitation Scenario
An attacker captures a lot of FTPS traffic using 3DES-CBC and performs a birthday attack to recover plaintext, particularly cookies or session IDs.

RC4 – Completely Broken

Vulnerability: Statistical bias
RC4 has predictable key-stream output, allowing plaintext recovery.

Real Exploitation Scenario
In a downgraded SSH or TLS connection, attacker forces RC4 and recovers your encrypted session cookies (BEAST-style attack).

CBC Mode Ciphers – Padding Oracle Attacks

CBC itself isn’t broken, but CBC in TLS/SSH is vulnerable due to:

CBC leaks information about plaintext.

Real Exploitation Scenario
An attacker intercepts FTPS/TLS traffic and uses a timing oracle to slowly decrypt encrypted traffic (Lucky13).

Export-Grade Ciphers (40-bit/56-bit) – Legacy US Regulations

Old export ciphers were intentionally weakened for export laws.

Real Exploitation Scenario
Attackers force the connection down to EXPORT ciphers via a downgrade attack (FREAK attack). Once downgraded:

  • They brute force the key
  • Impersonate the server
  • Read all encrypted traffic

Weak Key Exchange (Diffie-Hellman Group 1 / Group 2)

DH Group 1 uses 1024-bit keys, which can be broken by modern computing power.

Real Exploitation Scenario
Attackers break DH key exchange and derive the session key, fully decrypting the SSH or FTPS session.

Security Risks of Using Outdated Ciphers

Using outdated or deprecated ciphers exposes your organisation to several risks:

1. Confidential Data Exposure

Attackers can decrypt traffic, revealing:

  • Passwords
  • Tokens
  • PII
  • Financial data
  • Configuration files

2. Session Hijacking

Weak ciphers allow:

  • Cookie stealing
  • SSH session hijacking
  • SFTP takeover
  • Credential replay

3. MITM Downgrade Attacks

Attackers force a connection to fall back to a weaker cipher, then exploit it.

4. Compliance Violations

Outdated ciphers violate:

  • PCI-DSS
  • GDPR security requirements
  • Australian Essential Eight
  • CIS Benchmarks
  • ISO 27001 controls

5. Increased Attack Surface

Weak crypto encourages:

  • Padding oracles
  • Chosen-ciphertext attacks
  • Key-stream leakage
  • Brute-forceable keys

What Strong Cryptography Looks Like Today

For SSH/SFTP:

  • chacha20-poly1305@openssh.com
  • aes256-gcm@openssh.com
  • aes128-gcm@openssh.com
  • aes256-ctr

For FTPS/TLS 1.2/1.3:

  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • ECDHE-RSA-AES128-GCM-SHA256

Almost all other legacy ciphers should be disabled.

Python Script to Check SSH/TLS Ciphers on a Server

Below is a Python script that detects what cipher your server is actually using.

Requires: pip install paramiko

python

#!/usr/bin/env python3
"""
Check negotiated ciphers for SSH and TLS services.
"""

import argparse
import socket
import ssl
import getpass
import paramiko

def check_ssh_cipher(host: str, port: int, username: str, password: str | None = None) -> None:
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    if password is None:
        try:
            password = getpass.getpass(f"Password for {username}@{host}: ")
        except Exception:
            password = None

    try:
        client.connect(
            hostname=host,
            port=port,
            username=username,
            password=password,
            allow_agent=True,
            look_for_keys=True,
            timeout=10,
        )
        transport = client.get_transport()
        remote_cipher = transport.remote_cipher
        local_cipher = transport.local_cipher

        print(f"[+] SSH connection established to {host}:{port}")
        print(f"    SSH cipher (client -> server): {remote_cipher}")
        print(f"    SSH cipher (server -> client): {local_cipher}")

    except Exception as e:
        print(f"[!] SSH connection failed: {e}")
    finally:
        client.close()

def check_tls_cipher(host: str, port: int) -> None:
    context = ssl.create_default_context()
    try:
        with socket.create_connection((host, port), timeout=10) as sock:
            with context.wrap_socket(sock, server_hostname=host) as ssock:
                cipher, protocol, bits = ssock.cipher()
                print(f"[+] TLS connection to {host}:{port}")
                print(f"    Protocol: {protocol}")
                print(f"    Cipher:   {cipher} ({bits} bits)")
    except Exception as e:
        print(f"[!] TLS connection failed: {e}")

def main():
    parser = argparse.ArgumentParser(description="Check negotiated ciphers for SSH and TLS services.")
    parser.add_argument("--host", required=True, help="Target host")
    parser.add_argument("--port", type=int, help="Port number")
    parser.add_argument("--ssh", action="store_true", help="Check SSH cipher")
    parser.add_argument("--tls", action="store_true", help="Check TLS/FTPS cipher")
    parser.add_argument("--user", help="SSH username")

    args = parser.parse_args()

    if args.ssh:
        if not args.user:
            parser.error("--user is required for SSH checks")
        check_ssh_cipher(args.host, args.port or 22, args.user)

    if args.tls:
        check_tls_cipher(args.host, args.port or 443)

if __name__ == "__main__":
    main()

 

Security Best Practices Table

Practice Implementation Benefit
Use AES-GCM SSH: aes256-gcm@openssh.com
TLS: TLS_AES_256_GCM_SHA384
Strong encryption with built-in integrity
Disable Weak Ciphers Remove DES, 3DES, RC4 from config Eliminates known vulnerabilities
Regular Audits Use provided Python script monthly Early detection of misconfigurations
TLS 1.3+ Only Disable TLS 1.0/1.1 Prevents downgrade attacks
Strong Key Exchange Use ECDHE or modern DH groups Prevents key compromise

Final Thoughts

Ciphers are the backbone of secure communication—but only if they’re strong, modern, and correctly implemented. Outdated ciphers such as DES, 3DES, RC4, and CBC modes leave organisations vulnerable to:

  • Traffic decryption
  • Session hijacking
  • MITM attacks
  • Downgrade attacks
  • Compliance issues

SSH, FTPS, and SFTP rely heavily on modern encryption to maintain confidentiality and integrity, but misconfigurations can silently undermine that protection.

With the Python script included above, you can quickly assess whether your servers are using secure ciphers—and take corrective action.