I tried post-quantum key exchange with Amazon Corretto 27, which became GA

I tried post-quantum key exchange with Amazon Corretto 27, which became GA

Amazon Corretto 27, which became GA on September 17, 2026, supports post-quantum hybrid key exchange for TLS 1.3 via JEP 527. I connected a Corretto 27 client and an OpenSSL 3.5.7 server on local Docker and confirmed from the JDK debug logs that X25519MLKEM768 was selected.
2026.09.18

This page has been translated by machine translation. View original

Introduction

On September 17, 2026, Amazon Corretto 27 was released.

https://aws.amazon.com/jp/about-aws/whats-new/2026/09/amazon-corretto-27-generally-available/

OpenJDK 27 became GA on September 15, 2026, with the release following two days later.

In this article, I confirmed that post-quantum hybrid key exchange is selected in TLS 1.3 with Corretto 27, using JDK debug logs on a local Docker environment.

Main Features of Corretto 27

The announcement lists the following as main features.

  • Change to make G1 the default GC in all environments (JEP 523)
  • Post-quantum hybrid key exchange for TLS 1.3 (JEP 527)
  • Default enablement of compact object headers (JEP 534)
  • In-process data masking for JFR (JEP 536)
  • Continued preview of extended pattern matching, structured concurrency, and lazy constants (JEP 532, JEP 533, JEP 531)
  • Continued incubation of Vector API (JEP 537)

Verification Environment

The host architecture used for verification is aarch64. Since both JDK and OpenSSL are contained within the container, no installation on the host is required. This measurement was performed on an aarch64 host.

The client is an image based on debian:bookworm-slim with the latest version of Corretto 27 downloaded and extracted. The version at the time of retrieval (September 17, 2026) was as follows.

openjdk version "27" 2026-09-15
OpenJDK Runtime Environment Corretto-27.0.0.35.1 (build 27+35-FR)
OpenJDK 64-Bit Server VM Corretto-27.0.0.35.1 (build 27+35-FR, mixed mode, sharing)

The server is OpenSSL 3.5.7 on debian:trixie-slim. The official changelog for OpenSSL 3.5.0 states that TLS hybrid key sharing methods including X25519MLKEM768 were added.

Verification Steps

Place compose.yml, Dockerfile.server, Dockerfile.client, and PqTlsCheck.java in the same directory and run them (listed below in that order).

Overall Configuration (compose.yml)

services:
  tls-server:
    build:
      context: .
      dockerfile: Dockerfile.server
    container_name: pq-tls-server
    networks:
      - pq-net
    logging:
      driver: "json-file"

  tls-client:
    build:
      context: .
      dockerfile: Dockerfile.client
    container_name: pq-tls-client
    depends_on:
      - tls-server
    networks:
      - pq-net
    # Wait for server startup + run with JDK handshake debug enabled
    entrypoint:
      - sh
      - -c
      - |
        sleep 2
        # Settings recommended to keep enabled in production as well
        exec java \
          -Djavax.net.debug=ssl:handshake \
          -Dcom.sun.jndi.ldap.object.trustURLCodebase=false \
          -cp /app \
          PqTlsCheck tls-server 4433
    logging:
      driver: "json-file"

networks:
  pq-net:
    driver: bridge

Starting the OpenSSL Server

On the server side, a self-signed certificate is generated during the image build, and openssl s_server is started with TLS 1.3 only and the -trace flag.

Dockerfile.server
FROM debian:trixie-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    openssl \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /certs

# Generate a self-signed server certificate (no CA, single certificate)
RUN openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
    -keyout server.key -out server.crt -days 1 -nodes \
    -subj "/CN=tls-server" \
    -addext "subjectAltName=DNS:tls-server,IP:127.0.0.1"

EXPOSE 4433

# -trace: outputs handshake details (including named group) to stderr
CMD ["openssl", "s_server", \
     "-key", "/certs/server.key", \
     "-cert", "/certs/server.crt", \
     "-accept", "4433", \
     "-tls1_3", \
     "-trace", \
     "-www"]

Running the Corretto 27 Client

The client is an image that retrieves Corretto 27 and runs javac at build time.

Dockerfile.client
FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Corretto 27 - automatic architecture detection
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

COPY PqTlsCheck.java .
RUN javac PqTlsCheck.java

# -Djavax.net.debug=ssl:handshake outputs full handshake log
# Server certificate validation is disabled (due to self-signed certificate)
CMD ["java", \
     "-Djavax.net.debug=ssl:handshake", \
     "-Dcom.sun.jndi.ldap.object.trustURLCodebase=false", \
     "PqTlsCheck", "tls-server", "4433"]

The client application is a short program that establishes a TLS 1.3 connection using SSLSocket and outputs session information.

PqTlsCheck.java

This is a verification implementation for self-signed certificates. Do not use this in production, as certificate validation is disabled.

import javax.net.ssl.*;
import java.io.*;
import java.security.cert.X509Certificate;

/**
 * Corretto 27 PQ TLS handshake verification tool.
 * Performs TLS 1.3 negotiation with the target server and
 * logs the selected cipher suite / named group.
 *
 * Uses a TrustManager with certificate validation disabled (for self-signed certificates).
 * In production, do not use this TrustManager; instead, enable the JDK default TrustManager
 * (with certificate validation) and hostname verification.
 */
public class PqTlsCheck {

    public static void main(String[] args) throws Exception {
        String host = args.length > 0 ? args[0] : "localhost";
        int port    = args.length > 1 ? Integer.parseInt(args[1]) : 4433;

        System.out.println("=== Corretto 27 PQ TLS Check ===");
        System.out.println("Target: " + host + ":" + port);
        System.out.println("Java version: " + System.getProperty("java.version"));
        System.out.println("Java vendor:  " + System.getProperty("java.vendor"));
        System.out.println();

        // TrustManager that accepts self-signed certificates (for verification use only)
        TrustManager[] trustAll = new TrustManager[]{
            new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] c, String a) {}
                public void checkServerTrusted(X509Certificate[] c, String a) {}
                public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
            }
        };

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, trustAll, null);

        SSLSocketFactory factory = ctx.getSocketFactory();
        try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {
            socket.setEnabledProtocols(new String[]{"TLSv1.3"});

            // Returns null since no explicit setting is made (JDK default applies)
            SSLParameters params = socket.getSSLParameters();
            String[] namedGroups = params.getNamedGroups();
            System.out.println("--- Named Groups (client preference order) ---");
            if (namedGroups != null) {
                for (int i = 0; i < namedGroups.length; i++) {
                    System.out.printf("  [%d] %s%n", i, namedGroups[i]);
                }
            } else {
                System.out.println("  (not set explicitly — JDK default applies)");
            }
            System.out.println();

            // Execute handshake (ssl:handshake debug log is output by JVM)
            socket.startHandshake();

            SSLSession session = socket.getSession();
            System.out.println("--- TLS Session ---");
            System.out.println("Protocol:     " + session.getProtocol());
            System.out.println("CipherSuite:  " + session.getCipherSuite());
            System.out.println("PeerHost:     " + session.getPeerHost());
            System.out.println();
            System.out.println("SUCCESS: TLS handshake completed.");
        }
    }
}

Start the server and then run the client.

docker compose up -d tls-server && docker compose run --rm tls-client

When verification is complete, also remove the locally built images.

docker compose down --rmi local

Handshake Verification

Since -Djavax.net.debug=ssl:handshake is specified, the contents of ClientHello and ServerHello are output to the client's standard error. First, check the supported_groups extension in ClientHello.

"supported_groups (10)": {
  "named groups": [X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096]
},

Since no named groups are set in PqTlsCheck.java, this is the default priority order of Corretto 27. The behavior described in JEP 527, placing X25519MLKEM768 at the top, was confirmed without modifying any code.

Next, the key_share in ServerHello.

"key_share (51)": {
  "server_share": {
    "named group": X25519MLKEM768
    ...

The OpenSSL 3.5.7 server selected X25519MLKEM768, and the handshake was completed with hybrid key exchange. The JDK debug log records the completion of negotiation in TLS 1.3.

Negotiated protocol version: TLSv1.3

The application's standard output is as follows.

--- TLS Session ---
Protocol:     TLSv1.3
CipherSuite:  TLS_AES_256_GCM_SHA384
PeerHost:     tls-server

SUCCESS: TLS handshake completed.

Summary

In conjunction with the general availability of Amazon Corretto 27, I confirmed that the post-quantum hybrid key exchange X25519MLKEM768 is selected for TLS 1.3 in a local Docker environment.

Major AWS services such as Amazon S3 and Elastic Load Balancing (ALB/NLB) have supported PQC since 2025.

https://dev.classmethod.jp/articles/amazon-s3-post-quantum-support/
https://dev.classmethod.jp/articles/alb-nlb-post-quantum-key-exchange-tls/

Additionally, Aurora MySQL 8.4.8, released in September 2026, also added support for PQC.

https://dev.classmethod.jp/articles/aurora-mysql-pqtls-jdk27/

With the improving performance of quantum computers, there are concerns that widely used public-key cryptography such as RSA and elliptic curve cryptography (ECC) could be broken by the mid-2030s (NISC Interim Summary, November 20, 2025).

As a preparation for the so-called "2035 problem," please consider evaluating Corretto 27, a PQC-compatible JDK.

However, Corretto 27 is a Feature Release version, and support ends in April 2027. Please use it with the assumption of future updates and migration to an LTS version.

Share this article

AWSのお困り事はクラスメソッドへ