This requirement may be very simple to comply with if the only passwords we have to worry about were SSH passwords. SSH and Linux does exactly what is asked for, namely:

8.4 Render all passwords unreadable during transmission and storage on all system components using strong cryptography.

However, often life is not as simple.

Sometimes legacy applications which are essential to an environment’s operation don’t support the use of SSL and attempt to authenticate by sending plain text passwords.

How can we comply with this PCI requirement in these cases?

As always there are a few options.

Lets assume we have an application server (APP_SRV1) which connects to a database server (DB_SRV1) listening on port 5432, but it does this by sending a clear text password for the database to DB_SRV1:5432.

SSH Tunnelling

We could create an SSH tunnel which the application would use to communicate with the database.

The rough process on APP_SRV1 server would be as follows.

SSH key exchange:

ssh-keygen -t rsa
ssh-copy-id db_user@DB_SRV1

Then create a tunnel:

ssh -L 5555:localhost:5432 db_user@DB_SRV1

(you must ensure the DB server would be listening on localhost:5432).

The command is not immediately transparent as to what it’s doing. This says, create a local port listening on 5555, and use SSH to tunnel to DB_SRV1. Once there, pass the traffic to localhost:5432.
The traffic actually uses the standard SSH port of 22 between the two servers which you need to know for firewall purposes.

So APP_SRV1 would send its request to localhost:5555 instead of DB_SRV1:5432. Then on DB_SRV1, the request would emerge from the SSH tunnel and be passed to localhost:5432 where the database is listening.

Since this would need to be a persistent connection, something like authssh would need to use used to monitor and maintain the tunnel in a matter similar to the following:

autossh -M 0 -f -q -N  -o "ServerAliveInterval 10" -o "ServerAliveCountMax 3" -L 5555:localhost:5432 db_user@DB_SRV1

IPSEC

IPsec could also be used. The encrypted connection is designed to be persistent but it would encrypt all communication between 2 specific hosts which may be overkill depending on your configuration.

Stunnel

We found a good compromise between persistence and targeted encryption was stunnel.

This is quite similar in concept to SSH tunnelling but packaged in a more convenient way.

Stunnel Installation

Stunnel will need to be installed on both the application server and the database server.

Installation is via the debian package:

apt-get install stunnel4

Stunnel Server Configuration

First we will configure the server side of connection which will listen on DB_SRV1. Edit /etc/stunnel/stunnel.conf so it reads:

; Some debugging stuff useful for troubleshooting
;debug = 7
;foreground = yes
;output = /var/log/stunnel4/stunnel.log

pid = /var/run/stunnel4.pid

[db_srv1]
cert = /etc/ssl/certs/stunnel.cert
key = /etc/ssl/private/stunnel.key
options = NO_SSLv2
ciphers = ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM
accept = DB_SRV1:5433
connect = localhost:5432

This will listen on port 5433 on DB_SRV1 and pass traffic onto the DB which is listening locally on localhost:5432. The options and ciphers lines ensure we do not use weak ciphers for encryption (“Render all passwords unreadable during transmission [..] using strong cryptography“).

And to create the required certificate and key:

[david@debian:~]$ sudo openssl genrsa -out  /etc/ssl/private/stunnel.key 2048
Generating RSA private key, 2048 bit long modulus
.................................................................................+++
.+++
e is 65537 (0x10001)
[david@debian:~]$ sudo openssl req -new -x509 -key /etc/ssl/private/stunnel.key -out /etc/ssl/certs/stunnel.cert -days 365
You are about to be asked to enter information that will be incorporated
into your certificate request.
...snip...
[david@debian:~]$ chmod 400 /etc/ssl/private/stunnel.key
[david@debian:~]$ chmod 400 /etc/ssl/certs/stunnel.cert

We want this connection to restart if the server restarts so in /etc/default/stunnel4 ensure the following is present:

ENABLED=1

Now we can restart stunnel and the connection will start listening:

/etc/init.d/stunnel4 restart

Stunnel Client Configuration

And for the client side of the connection which will run on APP_SRV1 we use the same process except using the following configuration in /etc/stunnel4/stunnel.conf:

; Some debugging stuff useful for troubleshooting
;debug = 7
;foreground = yes
;output = /var/log/stunnel4/stunnel.log
    
pid = /var/run/stunnel4.pid

[db_srv1]
cert = /etc/ssl/certs/stunnel.cert
key = /etc/ssl/private/stunnel.key
client = yes
accept = localhost:5432
connect = DB_SRV1:5433

This configuration says stunnel will listen locally on localhost:5432 and will connect to DB_SRV1:5433 as a client to pass on any connection it receives. DB_SRV1:5433 of course is the server side of the stunnel connection and everything sent over that connection will be encrypted.

Once you have configured the keys, /etc/default/stunnel4, and restarted, you configure your application to speak to localhost:5432 instead of DB_SRV1:5432, and you now have encrypted communication between the two points.

Although the configuration effort appears greater for stunnel than for SSH tunnels, once up and running, we found stunnel to be very robust and reliable.