How does GnuPG encrypt secret keys?

GnuPG 2.x has a separate gpg-agent that is the custodian of secret keys and that offers no control whatsoever over the encryption parameters of the secret keys within.

Furthermore, --s2k are accepted but ignored when setting a passphrase or exporting a secret key.

There is an issue on the bug tracker about this and, when followed up on the mailing list, the answer was

No, that is not possible. Right now the agent always uses AES and S2K paremeters which require on the running machine about 100ms for decryption.

So I believe the current situation is that keys are held by the agent as the agent decides is best to meet its speed target on the running machine and there is no way to control it or to export differently. All exports I have tried result in key attributes that you can see like this:

$ gpg --export-secret-key DEADBEEF | gpg --list-packets
iter+salt S2K, algo: 7, SHA1 protection, hash: 2, salt: ...

This reveals the symmetric-key (algo) and hashing (hash) algorithms in use, as defined in RFC4880. The above shows that algo: 7 is AES with 128-bit key and hash: 2 is SHA-1.

The iter+salt tag is the string-to-key mode that GnuPG uses to convert the passphrase (string) into a key. The default value iter+salt means that a salt is added to the passphrase and that string is hashed multiple times (a technique called key stretching which aims to increase difficulty of brute-forcing passphrases).


So that illustrates how GnuPG encrypts secret keys and highlights the fact that you have no control over it if you're using version 2.x due to its use of the gpg-agent.


Phase 1: Symmetric Key Derivation

First, the passphrase is used to derive a key for symmetric encryption using a string-to-key function. Several settings can be fine-tuned in GnuPG considering this, like the hashing algorithm and the number of repetitions. From the GnuPG manual:

--s2k-digest-algo name

Use name as the digest algorithm used to mangle the passphrases for symmetric encryption. The default is SHA-1.

--s2k-mode n

Selects how passphrases for symmetric encryption are mangled. If n is 0 a plain passphrase (which is in general not recommended) will be used, a 1 adds a salt (which should not be used) to the passphrase and a 3 (the default) iterates the whole process a number of times (see --s2k-count).

--s2k-count n

Specify how many times the passphrases mangling for symmetric encryption is repeated. This value may range between 1024 and 65011712 inclusive. The default is inquired from gpg-agent. Note that not all values in the 1024-65011712 range are legal and if an illegal value is selected, GnuPG will round up to the nearest legal value. This option is only meaningful if --s2k-mode is set to the default of 3.

Phase 2: Symmetric Encryption

Using this symmetric key, the private key is encrypted symmetrically. Also the encryption algorithm can be chosen, while AES-128 is the default in GnuPG.

--s2k-cipher-algo name

Use name as the cipher algorithm for symmetric encryption with a passphrase if --personal-cipher-preferences and --cipher-algo are not given. The default is AES-128.

Supported ciphers can be displayed by running gpg --version, for example:

$ gpg --version
[...]
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256

Unlike public-private-key algorithms (where GnuPG 2.1 added elliptic curves), the supported symmetric algorithms are the same among all somewhat current releases of GnuPG.

All those options can also be set up in ~/.gnupg/gpg.conf, where they're noted without the -- prefix.