Some time ago, I saw news that “Proton Mail now supports post-quantum encryption”.

Post-quantum protection is now available in Proton Mail, on all plans including free. Once enabled, Proton Mail will generate and use post-quantum-ready keys for new encrypted emails to protect your personal messages and business communications against today’s threats and a future where current public-key cryptography may no longer be enough.

I use GNU Privacy Guard (GnuPG), which implements the OpenPGP standard1, to encrypt my data. Even though the standard aims to provide email encryption, which I think is still useful to me, I use it more as a way to encrypt personal data that would be uploaded to cloud providers I do not fully trust.

It has been about five years since I started using elliptic-curve cryptography for my OpenPGP key (which was itself a result of migration from RSA), and I thought it was now time for another change. Given that I currently have no one contacting me with the existing key, it seemed like a good opportunity to prevent “harvest now, decrypt later” attacks in advance, without having to worry about building another web of trust.

Enabling post-quantum protection

To generate a new set of keys, I went to my Proton account’s settings and enabled post-quantum protection.

Proton account settings for "Encryption and keys". In a section named "Post-quantum protection", a button to "Enable post-quantum protection" is present.

By doing so, a dialogue showed up and asked me if I am sure. I proceeded after acknowledging the consequences.

A dialogue box asking for confirmation before enabling post-quantum protection. Two warnings are shown as follows: "Your recovery methods will be invalidated: you can generate new recovery data later." and "You must update all Proton mobile apps: your new keys won't work on older app versions.". A checkbox with a message that says "I understand that I will no longer be able to sign in to older versions of Proton mobile apps." is checked.

Some new keys were generated, with old keys still being active as fallbacks. To add them to my device’s keyring, from the same account settings page, I exported some private keys.

A list of email encryption keys. On top of existing key with type "ECC (Curve25519)", a new one with type "PQC_V6 (MLDSA_ED25519), PQC_V6 (MKLEM_X25519)" is present.

However, adding them with gpg was unsuccessful.

[lyuk98@framework:~]$ gpg --import privatekey-dadda93b070d0ba3a01371ec6bd6ec7b61b2859a0dcbb639193130ceb7b1609e.asc
gpg: packet(5) with unknown version 6
gpg: read_block: read error: Invalid packet
gpg: no valid OpenPGP data found.
gpg: import from 'privatekey-dadda93b070d0ba3a01371ec6bd6ec7b61b2859a0dcbb639193130ceb7b1609e.asc' failed: Invalid keyring
gpg: Total number processed: 0

OpenPGP v6 and LibrePGP

The reason the command failed was that GnuPG does not support OpenPGP v6 (or RFC 9580), which is necessary to support newer keys that I have just generated. I then became aware of the “schism in the OpenPGP world”, where some “major implementers” (including GnuPG) opposing the new email encryption standard created their own one named LibrePGP.

Without knowing much about disagreements between two camps, it looked like another X/Wayland or SysV/systemd debate. I seemed to have already taken sides though, albeit unknowingly, by creating OpenPGP v6 keys that are incompatible with GnuPG.

After doing some research and personally concluding that the new OpenPGP standard is the way forward, I stuck with the plan; I can use my existing OpenPGP v4 (RFC 4880) keys with other people that took a different path, anyway. The next step to the migration was therefore to replace GnuPG with a compatible client, where I chose Sequoia PGP.

Migrating to Sequoia PGP

Before making sq (the command-line interface for Sequoia) available to my NixOS environment, I decided to first give it a try.

[lyuk98@framework:~]$ nix shell nixpkgs#sequoia-sq

Importing the new key still failed, however, with messages that seemed cryptic at first.

[lyuk98@framework:~]$ sq key import privatekey-dadda93b070d0ba3a01371ec6bd6ec7b61b2859a0dcbb639193130ceb7b1609e.asc
Error reading privatekey-dadda93b070d0ba3a01371ec6bd6ec7b61b2859a0dcbb639193130ceb7b1609e.asc: Unsupported
Cert: Unsupported primary key: Malformed packet: public_mpis: length 1984 but consumed 2099 bytes
Imported 0 new certificates, updated 0 certificates, 0 certificates unchanged, 0 errors.

  Error: Unsupported Cert: Unsupported primary key: Malformed packet: public_mpis: length 1984 but consumed
         2099 bytes

After some searching, I found out that the current stable version of sq (v1.3.1) does not support post-quantum cryptography (PQC). Fortunately, though, a more recent pre-release version (v1.4.0-pqc.1) apparently does.

To use the new version of sq in my NixOS (Home Manager, to be precise) configuration, I overrode some parts of the package definition.
{ pkgs, ... }:
{
  nixpkgs.overlays = [
    # Use pre-release version of sq to enable support for post-quantum cryptography
    (final: prev: {
      sequoia-sq = prev.sequoia-sq.overrideAttrs (rec {
        version = "1.4.0-pqc.1";
        src = prev.fetchFromGitLab {
          owner = "sequoia-pgp";
          repo = "sequoia-sq";
          tag = "v${version}";
          hash = "sha256-ep3il5In0ecyNWHvCo0yh4yL92VTy3/FligzKkY+SJQ=";
        };

        # Fetch latest Cargo dependencies
        cargoDeps = prev.rustPlatform.fetchCargoVendor {
          inherit src;
          hash = "sha256-NYUYQCKG4XWchvuEzzAD+R25Wk0YrHN4ISVtQnhPkcM=";
        };
      });
    })
  ];
}

However, I later realised that using the OpenSSL backend for sq is required for post-quantum cryptography (instead of the default Nettle backend). As such, I added overrides for that, too.

{ pkgs, ... }:
{
  nixpkgs.overlays = [
    # Use pre-release version of sq to enable support for post-quantum cryptography
    (final: prev: {
      sequoia-sq = prev.sequoia-sq.overrideAttrs (oldAttrs: rec {
        version = "1.4.0-pqc.1";
        src = prev.fetchFromGitLab {
          owner = "sequoia-pgp";
          repo = "sequoia-sq";
          tag = "v${version}";
          hash = "sha256-ep3il5In0ecyNWHvCo0yh4yL92VTy3/FligzKkY+SJQ=";
        };

        # Fetch latest Cargo dependencies
        cargoDeps = prev.rustPlatform.fetchCargoVendor {
          inherit src;
          hash = "sha256-NYUYQCKG4XWchvuEzzAD+R25Wk0YrHN4ISVtQnhPkcM=";
        };

        # Use OpenSSL cryptography backend as it is currently the only one supporting PQC
        buildInputs = oldAttrs.buildInputs ++ [ prev.openssl ];
        cargoBuildNoDefaultFeatures = true;
        cargoBuildFeatures = [ "crypto-openssl" ];
      });
    })
  ];
}

I then made the tools available to my NixOS environment.

# Add packages for Sequoia
home.packages = with pkgs; [
  sequoia-sq # Command-line interface for Sequoia
  sequoia-git # Authenticate changes to VCS repositories
  sequoia-sop # Stateless OpenPGP implementation using Sequoia
  sequoia-sqv # OpenPGP signature verification tool
  sequoia-wot # Sequoia web of trust
  sequoia-chameleon-gnupg # GnuPG reimplementation using Sequoia
];

The configuration for GnuPG was removed, but I kept gpg-agent for anything that relies on it.

services.gpg-agent = {
  # Enable GnuPG private key agent
  enable = true;

  # Set default pinentry interface
  pinentry.package = lib.mkDefault pkgs.pinentry-gnome3;
};

After applying the configuration and verifying that I am using the correct version, I tried importing the key again, which succeeded this time.

[lyuk98@framework:~]$ sq version
sq 1.4.0-pqc.1
using sequoia-openpgp 2.2.0-pqc.1
with cryptographic backend 3.6.1

[lyuk98@framework:~]$ sq key import privatekey-dadda93b070d0ba3a01371ec6bd6ec7b61b2859a0dcbb639193130ceb7b1609e.asc
Imported DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E pr@lyuk98.com <pr@lyuk98.com>
(UNAUTHENTICATED) from privatekey-dadda93b070d0ba3a01371ec6bd6ec7b61b2859a0dcbb639193130ceb7b1609e.asc: new

Hint: If this is your key, you should  mark it as a fully trusted introducer:

  $ sq pki link authorize --unconstrained \
    --cert=DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E --all

Hint: Otherwise, consider marking it as authenticated:

  $ sq pki link add --cert=DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E --all
Imported 1 new key, updated 0 keys, 0 keys unchanged, 0 errors.
Imported 1 new certificate, updated 0 certificates, 0 certificates unchanged, 0 errors.

As the output suggested, I ran sq pki link authorize to “mark it as a fully trusted introducer”.

[lyuk98@framework:~]$ sq pki link authorize --unconstrained \
  --cert=DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E --all
 - ┌ DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E
   └ pr@lyuk98.com <pr@lyuk98.com>
   - certification created

Using Sequoia PGP

Encrypting data with sq

The key was ready, and I wanted to try encryption next. I wrote a small file and performed an encryption with sq encrypt.
[lyuk98@framework:~]$ systemd-ask-password > document.txt
🔐 Password: ••••••••                

[lyuk98@framework:~]$ sq encrypt \
  --output document.txt.pgp \
  --for DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E \
  --signer DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E \
  document.txt
Please enter the password to decrypt DADDA93B070D0BA3/DADDA93B070D0BA3, pr@lyuk98.com <pr@lyuk98.com> (authenticated): 
Composing a message...

 - encrypted for pr@lyuk98.com <pr@lyuk98.com> (authenticated)
   - using DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E

 - signed by pr@lyuk98.com <pr@lyuk98.com> (authenticated)
   - using DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E


[lyuk98@framework:~]$ cat document.txt.pgp
-----BEGIN PGP MESSAGE-----

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
sevR0mfyUA==
=QefM
-----END PGP MESSAGE-----

I was initially surprised by the size of what appeared to be an encryption overhead. Following the previous command, the decryption was next, revealing the plaintext password as a result.

[lyuk98@framework:~]$ sq decrypt --output decrypted.txt document.txt.pgp
Please enter the password to decrypt 85ADC438AE94676B, pr@lyuk98.com <pr@lyuk98.com> (authenticated) (blank to skip): 
Encrypted using AES-256
Authenticating DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E (pr@lyuk98.com
<pr@lyuk98.com>) using the web of trust:
  Fully authenticated (120 of 120) DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E,
  pr@lyuk98.com <pr@lyuk98.com>
    ◯─┬ 6887A438706BAE97C40556E7C3F234E3B569B03A
    │ └ (Local Trust Root)
    │  certified the following binding on 2026‑05‑21 as a meta-introducer (depth: unconstrained)
    └─┬ DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E
      └ pr@lyuk98.com <pr@lyuk98.com>

  Authenticated signature made by DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E
  (pr@lyuk98.com <pr@lyuk98.com>)

Decrypted by DADDA93B070D0BA3A01371EC6BD6EC7B61B2859A0DCBB639193130CEB7B1609E, pr@lyuk98.com
<pr@lyuk98.com> (authenticated)
1 authenticated signature.

[lyuk98@framework:~]$ cat decrypted.txt
password

Authenticating commits with Sequoia git

I have always signed my Git commits ever since I created my first OpenPGP key. Nobody forced me to do so, but I felt it was important to be able to verify that the commits were not tampered with in any way. As I applied the practice to all my personal projects, some Git configuration settings were set in advance for convenience.

[lyuk98@framework:~]$ git config list --global | grep sign
commit.gpgsign=true
tag.gpgsign=true
user.signingkey=270CB11B1189E79A17DCB7831BDAFDC5D60E735C

And while being introduced to Sequoia, I found an interesting project: Sequoia git.

Sequoia git is a system to authenticate changes to a VCS repository. A project embeds a signing policy in their git repository, which says who is allowed to add commits, make releases, and modify the policy. sq-git log can then authenticate a range of commits using the embedded policy.

It was interesting, as I could apparently verify not only that the commits were signed, but also that they were signed by trusted contributors.

Firstly, though, I configured what Git uses for signing commits and tags. Instead of GnuPG, its reimplementation named Chameleon was used.
programs.git = {
  # Use GnuPG-compatible Sequoia Chameleon for signing commits
  signing.signer = lib.getExe pkgs.sequoia-chameleon-gnupg;
};
[lyuk98@framework:~]$ git config list --global | grep openpgp
gpg.format=openpgp
gpg.openpgp.program=/nix/store/wd6m7najxjxfchqh0d3m9mbcykczhsj7-sequoia-chameleon-gnupg-0.13.1/bin/gpg-sq

With that taken care of, starting with my repository for NixOS configuration, I ran sq-git policy authorize to add myself as a contributor. The fingerprint of the existing key (that was used to sign all of my commits there) was supplied with the --cert option.

[lyuk98@framework:~/nixos-config]$ sq-git policy authorize \
  --project-maintainer \
  --cert 270CB11B1189E79A17DCB7831BDAFDC5D60E735C \
  lyuk98
  - User lyuk98 was added.
  - User lyuk98 was granted the right sign-commit.
  - User lyuk98 was granted the right sign-tag.
  - User lyuk98 was granted the right sign-archive.
  - User lyuk98 was granted the right add-user.
  - User lyuk98 was granted the right retire-user.
  - User lyuk98 was granted the right audit.
  - User lyuk98: new certificate 270CB11B1189E79A17DCB7831BDAFDC5D60E735C.

[lyuk98@framework:~/nixos-config]$ git add openpgp-policy.toml

[lyuk98@framework:~/nixos-config]$ git commit --message "Add OpenPGP signing policy"

After committing the policy document, I first set the trust root, which is the start of a range of commits that I wish to verify, to the first commit made to the repository.

[lyuk98@framework:~/nixos-config]$ git config set sequoia.trustRoot a1d2d2fae42ae6155955422cf937b6442430cb44

By running sq-git log, I could then verify that everything from the trust root was signed with the specified certificate.

[lyuk98@framework:~/nixos-config]$ sq-git log --policy-file openpgp-policy.toml
Verified that there is an authenticated path from the trust root
a1d2d2fae42ae6155955422cf937b6442430cb44 to 45fd0dd46007157638b3dd394e1b894e089c9464.

Omitting --policy-file did not work, surprisingly, even though the default policy document it should look for is openpgp-policy.toml.

Closing thoughts

After learning about the “schism” that I have previously mentioned, the new post-quantum key did not yet look like a perfect replacement for my existing keys, at least not in the foreseeable future. It was troubling, as fragmented and competing standards would not help end-to-end encrypted email communication reach a wider audience.

As a result, I decided to continue using my previous set of keys for the time being. The reasons were:

  • Elliptic-curve cryptography is currently not considered insecure
  • The aforementioned “harvest now, decrypt later” attack is not the most relevant threat to me
  • Inconveniences introduced by OpenPGP v6 keys currently outweigh the potential security benefits in my case

It was unfortunate, but I do not think everything I have done so far was for nothing. Before I worked on this, GnuPG was pretty much synonymous with OpenPGP to me; with Sequoia PGP, I realised the importance of recognising different implementations of the encryption standard. Furthermore, I now have a way to easily verify that my commits are signed according to the set rules.

My primary OpenPGP key can be discovered via Web Key Directory (WKD), which in turn directs to the public key hosted at my server. Its fingerprint is as follows:

270C B11B 1189 E79A 17DC  B783 1BDA FDC5 D60E 735C

  1. Technically, GnuPG implements the now-obsolete standard, RFC 4880. More on that is discussed in the later part of this post. ↩︎