Trying out post-quantum TLS key exchange with RDS for PostgreSQL
This page has been translated by machine translation. View original
Introduction
On September 24, 2026, Amazon RDS for PostgreSQL added support for post-quantum key exchange in TLS.
Post-quantum key exchange for Aurora MySQL is introduced in a previous article.
Since RDS for PostgreSQL also added support, I connected from OpenSSL and JDK 27 (Corretto 27) to verify the key exchange group. Post-quantum TLS support on the Corretto 27 side is covered in the following article.
Test Environment
| Item | Value |
|---|---|
| RDS Engine | PostgreSQL 18.6 |
| Source EC2 | Amazon Linux 2023 (aarch64) |
| OpenSSL (Source EC2) | 3.5.8 |
| JDK | Amazon Corretto 27 |
| PostgreSQL JDBC Driver | 42.7.7 |
| Region | ap-northeast-1 |
Setting X25519MLKEM768 in ssl_groups
The target DB instance has the pre-created parameter group pq-tls-pg18 applied. I set the post-quantum group X25519MLKEM768 in the ssl_groups of this parameter group.
aws rds modify-db-parameter-group \
--db-parameter-group-name pq-tls-pg18 \
--parameters "ParameterName=ssl_groups,ParameterValue=X25519MLKEM768,ApplyMethod=immediate" \
--region ap-northeast-1
The value after configuration and the values that can be specified (AllowedValues) are as follows.
[
{
"ParameterName": "ssl_groups",
"ParameterValue": "X25519MLKEM768",
"Description": "Sets the named group(s) to use for TLS key exchange",
"Source": "user",
"ApplyType": "dynamic",
"DataType": "list",
"AllowedValues": "X25519MLKEM768, SecP256r1MLKEM768, X25519, prime256v1, secp384r1",
"IsModifiable": true,
"ApplyMethod": "immediate"
}
]
Only the groups listed in AllowedValues can be specified. For this verification, I set only X25519MLKEM768 to confirm post-quantum key exchange.
Verifying the Key Exchange Group by Connecting with OpenSSL
I connected from the source EC2 using s_client.
echo "" | openssl s_client \
-connect pq-tls-pg18.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:5432 \
-starttls postgres \
-tls1_3 \
-groups X25519MLKEM768:x25519:prime256v1 \
-trace 2>&1
Extracting only the lines for key exchange group, cipher suite, and protocol from the output:
Negotiated TLS1.3 group: X25519MLKEM768
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
Key exchange was established with X25519MLKEM768 set in ssl_groups.
Verifying with JDBC Connection from Corretto 27
Next, I connected via JDBC from Corretto 27 running in a Docker container on the source EC2. The connection properties included only the user, password, ssl, and sslmode, with no key exchange group specified (the code is placed at the end of this section). Handshake logs were captured with -Djavax.net.debug=ssl:handshake.
The supported_groups in the ClientHello were in the following order.
"supported_groups (10)": {
"named groups": [X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096]
}
Even without specifying anything on the application side, X25519MLKEM768 appears at the top. In response to this, the key_share in the ServerHello returned by the server was also X25519MLKEM768.
"key_share (51)": {
"server_share": {
"named group": X25519MLKEM768
The protocol is TLS 1.3.
Negotiated protocol version: TLSv1.3
Connection from JDBC was also successful using X25519MLKEM768, the only group permitted by the server.
Dockerfile, Java code, and execution steps used for the connection
Dockerfile
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ARG TARGETARCH
RUN case "${TARGETARCH}" in \
amd64) JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-27-x64-linux-jdk.tar.gz" ;; \
arm64) JDK_URL="https://corretto.aws/downloads/latest/amazon-corretto-27-aarch64-linux-jdk.tar.gz" ;; \
*) echo "Unsupported arch: ${TARGETARCH}" && exit 1 ;; \
esac \
&& curl -fsSL "${JDK_URL}" | tar -xz -C /opt \
&& ln -s /opt/amazon-corretto-27* /opt/java
ENV JAVA_HOME=/opt/java
ENV PATH="${JAVA_HOME}/bin:${PATH}"
WORKDIR /app
ADD https://repo1.maven.org/maven2/org/postgresql/postgresql/42.7.7/postgresql-42.7.7.jar /app/postgresql.jar
COPY PgTlsCheck.java .
RUN javac -cp postgresql.jar PgTlsCheck.java
CMD ["java", \
"-Djavax.net.debug=ssl:handshake", \
"-cp", ".:postgresql.jar", \
"PgTlsCheck"]
PgTlsCheck.java
import java.sql.*;
import java.util.Properties;
public class PgTlsCheck {
public static void main(String[] args) throws Exception {
String host = System.getenv("PG_HOST");
String port = System.getenv().getOrDefault("PG_PORT", "5432");
String user = System.getenv().getOrDefault("PG_USER", "pgadmin");
String pass = System.getenv("PG_PASSWORD");
String db = System.getenv().getOrDefault("PG_DB", "postgres");
System.out.println("=== Corretto 27 PQ TLS Check (PostgreSQL) ===");
System.out.println("Target: " + host + ":" + port + "/" + db);
System.out.println("Java version: " + System.getProperty("java.version"));
System.out.println("Java vendor: " + System.getProperty("java.vendor"));
System.out.println();
String url = "jdbc:postgresql://" + host + ":" + port + "/" + db;
Properties props = new Properties();
props.setProperty("user", user);
props.setProperty("password", pass);
props.setProperty("ssl", "true");
props.setProperty("sslmode", "require");
try (Connection conn = DriverManager.getConnection(url, props);
Statement stmt = conn.createStatement()) {
System.out.println("Connected successfully!");
System.out.println();
System.out.println("--- TLS Session Status ---");
try (ResultSet rs = stmt.executeQuery(
"SELECT ssl, version, cipher, bits FROM pg_stat_ssl WHERE pid = pg_backend_pid()")) {
if (rs.next()) {
System.out.println("ssl: " + rs.getBoolean("ssl"));
System.out.println("version: " + rs.getString("version"));
System.out.println("cipher: " + rs.getString("cipher"));
System.out.println("bits: " + rs.getInt("bits"));
}
}
try (ResultSet rs = stmt.executeQuery("SELECT version()")) {
if (rs.next()) System.out.println("PostgreSQL: " + rs.getString(1));
}
}
}
}
Build and execution (on the source EC2)
# Build
docker build -t pg-pq-tls .
# Run (-Djavax.net.debug=ssl:handshake is included in the CMD in the Dockerfile)
docker run --rm \
-e PG_HOST=pq-tls-pg18.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com \
-e PG_PASSWORD="$PG_PASSWORD" \
pg-pq-tls
Summary
I was able to confirm that post-quantum key exchange is available with RDS for PostgreSQL.
If you need to comply with guidelines aimed at transitioning to post-quantum cryptography (PQC) by 2035, please adopt a version of RDS for PostgreSQL that supports post-quantum key exchange and configure a post-quantum group in the ssl_groups of the parameter group.
