Upgrading a certification signature from SHA1 to SHA256

A certification signature is a way to store the assertion that a binding between a User ID (like an email address) and a public key is authentic. The certification signature is added to the certificate containing the binding. There are two types of certifications: self-signed ones, which signify that the certification aligns with the intents of the keyholder, and certifications made by others, signifying their confidence in the authenticity of the binding.

The first certification signature is added to a key when it's generated. It self-signs the key, creating a binding between its public key and its initial User IDs. Revocation certificates also contain a certification signature for verifying the authenticity of the revocation.

The certification signature is created by hashing the public key and the User ID, and then signing the resulting hash value. Ideally, the hash value for the combination of public key and User ID should be unique. Hash values, however, are of limited size, which implies information loss, and also that other input values could produce the same hash value. The consequence of these potential collisions is that a certification signature asserts the authenticity of an unlimited number of public key – User ID pairs. Obviously, this is not desired behavior.

One design criterion for a hash function used for the creation of certification signatures is that these collisions must be next to impossible to construct, making the abuse of such collisions infeasible in practice.

SHA1 is one such hash function. Over time, careful research has concluded that SHA1 is less robust against collision construction than initially thought. When combined with the inexorable expansion and availability of computing power, SHA1 became vulnerable, meaning the construction of a matching public key – User ID pair for a hash value became a realistic scenario. The remedy is to add a new signature (over the same content), using a hash calculated using a stronger hash function: SHA256. The old signature will not be removed, but its intention will now be confirmed in a way which is resistant to collisions.

In the following, we will show you how to use sq cert lint and, due to a lack of awareness of outdated standards, how to use it with GnuPG.

Lint certificates of Sequoia's certificate store

Check for SHA1 signatures in certificates

sq cert lint checks for certificates that uses SHA1 signatures. sq cert lint takes the certificate(s) to examine as either a file parameter or from STDIN. It also operates directly on the certificate store—so no need for a keyring, unless you would like to check the whole cert store, which we show later on. To check a specific certificate, use the command like this:

$ sq cert lint --cert $FINGERPRINT
Examined 1 certificate.
  0 certificates are invalid and were not linted. (GOOD)
  1 certificate was linted.
  1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
  0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
  1 has at least one User ID protected by SHA-1. (BAD)
  1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
  1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

If you have access to the key, you can fix this now. Therefore, use the command sq cert lint and add the --fix option. This causes sq to generate new certifications, using the SHA256 hash function, and update the certificate in the cert store:

$ sq cert lint --fix --cert $FINGERPRINT
Certificate B371C0977CFE0EBC is not valid under the standard policy: No binding signature at time 2025-03-27T08:25:00Z
Certificate B371C0977CFE0EBC contains a User ID (Alice Example <alice@example.com>) protected by SHA-1
Please enter the password to decrypt B371C0977CFE0EBC/B371C0977CFE0EBC, Alice Example <alice@example.com> (UNAUTHENTICATED): 
Certificate B371C0977CFE0EBC, key 395B0E508A299D30 uses a SHA-1-protected binding signature.
Examined 1 certificate.
  0 certificates are invalid and were not linted. (GOOD)
  1 certificate was linted.
  1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
  0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
  1 has at least one User ID protected by SHA-1. (BAD)
  1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
  1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

To check whether the certificate has been linted, we have to repeat the sq cert lint command for this specific certificate, which you can see no longer shows an SHA1 signature:

$ sq cert lint --cert $FINGERPRINT
Examined 1 certificate.
  0 certificates are invalid and were not linted. (GOOD)
  1 certificate was linted.
  0 of the 1 certificates (0%) have at least one issue. (GOOD)
0 of the linted certificates were revoked.
  0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
  0 have at least one User ID protected by SHA-1. (GOOD)
  0 have all User IDs protected by SHA-1. (GOOD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
  0 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (GOOD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

To show more information, use sq packet dump:

$ sq packet dump --cert $FINGERPRINT
Public-Key Packet, new CTB, 269 bytes
    Version: 4
    Creation time: 2025-02-17 14:32:54 UTC
    Pk algo: RSA
    Pk size: 2048 bits
    Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
    KeyID: B371C0977CFE0EBC
  
User ID Packet, new CTB, 33 bytes
    Value: Alice Example <alice@example.com>
  
Signature Packet, new CTB, 402 bytes
    Version: 4
    Type: PositiveCertification
    Pk algo: RSA
    Hash algo: SHA256
    Hashed area:
      Signature creation time: 2025-03-26 08:46:07 UTC (critical)
      Symmetric algo preferences: AES256, AES192, AES128
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Notation: salt@notations.sequoia-pgp.org
        00000000  5f 9e ba 5e 79 73 80 3c  34 69 18 6c a7 b4 9c 4f
        00000010  e6 da c2 d8 f1 31 70 d7  0f a2 8d 09 a2 36 e9 b8
      Hash preferences: SHA256, SHA512
      Compression preferences: Zlib, BZip2, Zip
      Keyserver preferences: no modify
      Key flags: CS
      Features: SEIPDv1
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: B3DB
    Level: 0 (signature over data)
  
Signature Packet, new CTB, 335 bytes
    Version: 4
    Type: PositiveCertification
    Pk algo: RSA
    Hash algo: SHA1
    Hashed area:
      Signature creation time: 2025-02-17 14:32:54 UTC
      Key flags: CS
      Symmetric algo preferences: AES256, AES192, AES128, CAST5, TripleDES
      Hash preferences: SHA256, SHA1, SHA384, SHA512, SHA224
      Compression preferences: Zlib, BZip2, Zip
      Features: SEIPDv1
      Keyserver preferences: no modify
    Unhashed area:
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: A68C
    Level: 0 (signature over data)
  
Public-Subkey Packet, new CTB, 269 bytes
    Version: 4
    Creation time: 2025-02-17 14:32:54 UTC
    Pk algo: RSA
    Pk size: 2048 bits
    Fingerprint: 8CE181686CF93037B5971F22395B0E508A299D30
    KeyID: 395B0E508A299D30
  
Signature Packet, new CTB, 382 bytes
    Version: 4
    Type: SubkeyBinding
    Pk algo: RSA
    Hash algo: SHA256
    Hashed area:
      Signature creation time: 2025-03-26 08:46:07 UTC (critical)
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Notation: salt@notations.sequoia-pgp.org
        00000000  09 87 55 24 27 e4 93 20  d6 e3 94 e9 06 b8 31 a8
        00000010  5a 5b 4f cb 5e 2a aa fe  57 37 d7 eb 48 50 58 0e
      Key flags: EtEr
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: 6BC9
    Level: 0 (signature over data)
  
Signature Packet, new CTB, 310 bytes
    Version: 4
    Type: SubkeyBinding
    Pk algo: RSA
    Hash algo: SHA1
    Hashed area:
      Signature creation time: 2025-02-17 14:32:54 UTC
      Key flags: EtEr
    Unhashed area:
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: 40E8
    Level: 0 (signature over data) 

Checking all certificates

If you want to check all your certificates, even the ones you have received from others, to just know which ones are vulnerable (of course you can't update them without their secret key) they have to be exported and a keyring has to be created for sq lint to handle them. First, here is how to export the certificates from the cert store:

$ sq cert export --all > keyring.pgp

Then you can check the exported keyring using the sq lint command, which will not change anything:

$ sq cert lint --cert-file keyring.pgp
Certificate 9C2437DF50A1F904 is not valid under the standard policy: No binding signature at time 2025-03-27T08:11:04Z
Certificate 9C2437DF50A1F904 contains a User ID (Alice Example (GnuPG 1.4.18 Testkey) <alice@example.com>) protected by SHA-1
Certificate 9C2437DF50A1F904, key 6FBFA7A845431489 uses a SHA-1-protected binding signature.
[...]
Examined 644 certificates.
  82 certificates are invalid and were not linted. (BAD)
  562 certificates were linted.
  175 of the 644 certificates (27%) have at least one issue. (BAD)
36 of the linted certificates were revoked.
  1 of the 36 certificates has revocation certificates that are weaker than the certificate and should be recreated. (BAD)
276 of the linted certificates were expired.
249 of the non-revoked linted certificates have at least one non-revoked User ID:
  91 have at least one User ID protected by SHA-1. (BAD)
  89 have all User IDs protected by SHA-1. (BAD)
236 of the non-revoked linted certificates have at least one non-revoked, live subkey:
  83 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
74 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

  Error: 175 certificates have at least one issue

As you can see in this example, many invalid certificates may have accumulated over time. To resolve that, you could either ask the respective owners to update their certificates, or if the corresponding secret key material is available to you, fix the certificates yourself using the --fix option described above.

sq cert lint with certificates from gpg keyrings

Check for SHA1 signatures in certificates

Again, we use sq cert lint to check for certificates that use SHA1 signatures. sq cert lint takes the certificate(s) to examine either a file parameter or from STDIN.

This example takes the certificates from gpg, using a keyring which contains just one vulnerable certificate.

$ gpg --export | sq cert lint
Certificate B371C0977CFE0EBC is not valid under the standard policy: No binding signature at time 2025-02-17T14:52:16Z
Certificate B371C0977CFE0EBC contains a User ID (Alice Example <alice@example.com>) protected by SHA-1
Certificate B371C0977CFE0EBC, key 395B0E508A299D30 uses a SHA-1-protected binding signature.
Examined 1 certificate.
  0 certificates are invalid and were not linted. (GOOD)
  1 certificate was linted.
  1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
  0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
  1 has at least one User ID protected by SHA-1. (BAD)
  1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
  1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

  Error: 1 certificate have at least one issue

To fix a certification signature, a secret key is needed.

Exporting a "secret key" actually exports the complete keypair—both the secret key material and the public certificate.

Passing --fix to sq cert lint causes sq to generate new certifications using the SHA256 hash function. The result is written to stdout and can be passed to sq or, or back into gpg for import.

$ gpg --export-secret-keys alice@example.com | sq cert lint --fix
Certificate B371C0977CFE0EBC is not valid under the standard policy: No binding signature at time 2025-02-17T14:56:10Z
Certificate B371C0977CFE0EBC contains a User ID (Alice Example <alice@example.com>) protected by SHA-1
Please enter the password to decrypt B371C0977CFE0EBC/B371C0977CFE0EBC Alice Example <alice@example.com> (UNAUTHENTICATED): 
Certificate B371C0977CFE0EBC, key 395B0E508A299D30 uses a SHA-1-protected binding signature.
-----BEGIN PGP PRIVATE KEY BLOCK-----

xcMGBGezSJYBCACv2FM2TzZtv5LmV+CmKjRqrcuaexasLg6GxpoTHVN7gwSXDNGq
[...]
2DIgwY1O
=hUqr
-----END PGP PRIVATE KEY BLOCK-----
Examined 1 certificate.
  0 certificates are invalid and were not linted. (GOOD)
  1 certificate was linted.
  1 of the 1 certificates (100%) has at least one issue. (BAD)
0 of the linted certificates were revoked.
  0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
  1 has at least one User ID protected by SHA-1. (BAD)
  1 has all User IDs protected by SHA-1. (BAD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
  1 has at least one non-revoked, live subkey with a binding signature that uses SHA-1. (BAD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

The output of sq cert lint --fix can be piped to sq key import, or gpg --import if you're using gpg:

$ gpg --export-secret-keys alice@example.com | sq cert lint --fix | gpg --import
[...]
gpg: Total number processed: 1
gpg:         new signatures: 2
gpg:       secret keys read: 1
gpg:  secret keys unchanged: 1

In this example, two new signatures were detected, and both used the improved hash algorithm.

We can now have a look at the cert again in sq, expecting it to be linted:

$ gpg --export alice@example.com | sq cert lint
Examined 1 certificate.
  0 certificates are invalid and were not linted. (GOOD)
  1 certificate was linted.
  0 of the 1 certificates (0%) have at least one issue. (GOOD)
0 of the linted certificates were revoked.
  0 of the 0 certificates has revocation certificates that are weaker than the certificate and should be recreated. (GOOD)
0 of the linted certificates were expired.
1 of the non-revoked linted certificate has at least one non-revoked User ID:
  0 have at least one User ID protected by SHA-1. (GOOD)
  0 have all User IDs protected by SHA-1. (GOOD)
1 of the non-revoked linted certificates has at least one non-revoked, live subkey:
  0 have at least one non-revoked, live subkey with a binding signature that uses SHA-1. (GOOD)
0 of the non-revoked linted certificates have at least one non-revoked, live, signing-capable subkey:
  0 certificates have at least one non-revoked, live, signing-capable subkey with a strong binding signature, but a backsig that uses SHA-1. (GOOD)

All checks are good.

For a closer look, use sq packet dump:

$ gpg --export alice@example.com | sq packet dump
Public-Key Packet, old CTB, 269 bytes
    Version: 4
    Creation time: 2025-02-17 14:32:54 UTC
    Pk algo: RSA
    Pk size: 2048 bits
    Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
    KeyID: B371C0977CFE0EBC
  
User ID Packet, old CTB, 33 bytes
    Value: Alice Example <alice@example.com>
  
Signature Packet, old CTB, 312 bytes
    Version: 4
    Type: PositiveCertification
    Pk algo: RSA
    Hash algo: SHA1
    Hashed area:
      Signature creation time: 2025-02-17 14:32:54 UTC
      Key flags: CS
      Symmetric algo preferences: AES256, AES192, AES128, CAST5, TripleDES
      Hash preferences: SHA256, SHA1, SHA384, SHA512, SHA224
      Compression preferences: Zlib, BZip2, Zip
      Features: SEIPDv1
      Keyserver preferences: no modify
    Unhashed area:
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: A68C
    Level: 0 (signature over data)
  
Signature Packet, old CTB, 402 bytes
    Version: 4
    Type: PositiveCertification
    Pk algo: RSA
    Hash algo: SHA256
    Hashed area:
      Signature creation time: 2025-02-17 14:59:11 UTC (critical)
      Symmetric algo preferences: AES256, AES192, AES128
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Notation: salt@notations.sequoia-pgp.org
        00000000  77 d1 f6 d5 be 0b 50 60  c2 02 e9 db 20 0e 38 37
        00000010  04 7a 3d 06 58 fc 7c 16  de 66 86 d2 e1 b5 29 37
      Hash preferences: SHA256, SHA512
      Compression preferences: Zlib, BZip2, Zip
      Keyserver preferences: no modify
      Key flags: CS
      Features: SEIPDv1
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: 7F53
    Level: 0 (signature over data)
  
Public-Subkey Packet, old CTB, 269 bytes
    Version: 4
    Creation time: 2025-02-17 14:32:54 UTC
    Pk algo: RSA
    Pk size: 2048 bits
    Fingerprint: 8CE181686CF93037B5971F22395B0E508A299D30
    KeyID: 395B0E508A299D30
  
Signature Packet, old CTB, 287 bytes
    Version: 4
    Type: SubkeyBinding
    Pk algo: RSA
    Hash algo: SHA1
    Hashed area:
      Signature creation time: 2025-02-17 14:32:54 UTC
      Key flags: EtEr
    Unhashed area:
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: 40E8
    Level: 0 (signature over data)
  
Signature Packet, old CTB, 382 bytes
    Version: 4
    Type: SubkeyBinding
    Pk algo: RSA
    Hash algo: SHA256
    Hashed area:
      Signature creation time: 2025-02-17 14:59:11 UTC (critical)
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Notation: salt@notations.sequoia-pgp.org
        00000000  1d f1 d2 67 0e 98 6a 8c  f9 ab 62 c0 44 fc cf 17
        00000010  41 5e 64 9d 63 5a 75 d2  13 82 73 3b 08 7d 5c ec
      Key flags: EtEr
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: 94F9
    Level: 0 (signature over data)

Note that the old SHA1 certifications are still present within the certificate, but the new SHA256 certifications of the same entities will be used in preference to them.

Fixing a revocation certificate

First, let's look into a revocation certificate to examine the hash algorithm used. In this example alice.rev contains the revocation certificate. Use sq packet dump to inspect it more closely:

$ sq packet dump alice.rev
Signature Packet, old CTB, 293 bytes
    Version: 4
    Type: KeyRevocation
    Pk algo: RSA
    Hash algo: SHA1
    Hashed area:
      Signature creation time: 2025-02-17 15:10:37 UTC
      Reason for revocation: Key is retired and no longer used, Retire
    Unhashed area:
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: E7F3
    Level: 0 (signature over data)

Hash algo tells us the hash algorithm used, SHA1 in this case.

sq cert lint is not meant to fix this issue; Instead, create a new revocation certificate.

The generation of a revocation certificate only works with keys containing SHA256 certifications. See above for how to fix keys in case they are missing.

$ gpg --export-secret-key alice@example.com | sq key userid revoke --cert-file - --userid "Alice Example <alice@example.com>" --reason "retired" --message "This key is retired" --output alice_new.rev
Waiting for OpenPGP certificates on stdin...
Please enter the password to decrypt B371C0977CFE0EBC/B371C0977CFE0EBC Alice Example <alice@example.com> (UNAUTHENTICATED): 
$ sq packet dump alice_new.rev
Public-Key Packet, new CTB, 269 bytes
    Version: 4
    Creation time: 2025-02-17 14:32:54 UTC
    Pk algo: RSA
    Pk size: 2048 bits
    Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
    KeyID: B371C0977CFE0EBC

User ID Packet, new CTB, 33 bytes
    Value: Alice Example <alice@example.com>

Signature Packet, new CTB, 401 bytes
    Version: 4
    Type: CertificationRevocation
    Pk algo: RSA
    Hash algo: SHA512
    Hashed area:
      Signature creation time: 2025-02-17 15:15:34 UTC (critical)
      Issuer: B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
      Notation: salt@notations.sequoia-pgp.org
        00000000  3f 86 99 1e cb 7a 33 0d  39 f0 da 7c 99 00 be 22
        00000010  42 04 f9 d5 83 a9 b0 36  33 1c 88 07 34 2d 0d 18
      Reason for revocation: User ID information is no longer valid, This key is retired
      Issuer Fingerprint: 17D714014EE9789B1152A408B371C0977CFE0EBC
        Alice Example <alice@example.com> (UNAUTHENTICATED)
    Digest prefix: D216
    Level: 0 (signature over data)