Why RSA cannot work¶
This is the constraint that shapes the whole module, and it is worth understanding rather than taking on trust — because the failure it causes is silent, delayed, and unrecoverable.
The mismatch¶
OpenPGP encrypts a message by generating a random session key, encrypting the message with it, and then encrypting that session key to the recipient's public key. The result is a Public-Key Encrypted Session Key (PKESK) packet.
For an RSA recipient key, RFC 9580 fixes the padding:
the above values are encoded using the PKCS#1 block encoding EME-PKCS1-v1_5, as described in Step 2 in Section 7.2.1 of RFC 8017
AWS KMS supports exactly two encryption algorithms for RSA key specs:
RSAES_OAEP_SHA_1 and RSAES_OAEP_SHA_256. Both are OAEP. There is no
PKCS#1 v1.5 decryption path, and no parameter that adds one.
So a KMS RSA key cannot unwrap an OpenPGP session key. Not "inefficiently" or "with extra work" — the operation does not exist.
Why this is dangerous rather than merely limiting¶
An ENCRYPT_DECRYPT RSA key in KMS provisions perfectly. It produces a valid
public key, which you can publish, and which any OpenPGP client will happily
encrypt to. Everything looks correct.
The failure surfaces the first time you try to decrypt something real — which, for a contact address, means the first time someone sends you something that matters. By then you may have been publishing an unusable key for months, and every message sent to it is unreadable forever.
That is why the module rejects RSA in encryption_key_spec at plan time rather
than documenting the hazard. The dead end is made unrepresentable, so it cannot
be discovered at the worst possible moment.
Why ECDH escapes it¶
ECDH involves no padding scheme at all, which is precisely the point.
The sender generates an ephemeral key pair, does an ECDH agreement against your published subkey, runs a key derivation function over the resulting shared point, and uses the derived key to AES-key-wrap the session key (RFC 3394). The PKESK packet carries the ephemeral public point.
To decrypt, you need the same shared point — and that is exactly what KMS
DeriveSharedSecret returns:
The raw secret derived from the specified key agreement algorithm, private key in the asymmetric KMS key, and your peer's public key.
The raw shared secret is the KDF's input. Everything after it — the KDF, the key unwrap, the message decryption — happens outside KMS with public information. The private key never leaves.
Which ECDH, precisely¶
This matters, because getting it wrong looks like a key fault rather than a code fault.
The module provisions a NIST curve, so the relevant construction is public-key
algorithm ID 18 (ECDH), whose KDF is the RFC 6637-derived one retained by
RFC 9580: a hash over the shared point's x-coordinate concatenated with a
Param block. It is not the X25519 / X448 construction (IDs 25 and 26),
which uses HKDF with a different info string.
One consequence is easy to miss: that Param block binds the recipient key's
fingerprint and the curve OID. Decryption therefore needs the certificate's
fingerprint, not just the KMS key — which couples certificate assembly and
decryption more tightly than "call DeriveSharedSecret and unwrap" suggests.
What is ruled out, and why¶
| Key spec | Verdict |
|---|---|
RSA_2048 / 3072 / 4096 |
No PKCS#1 v1.5 decrypt path in KMS. Impossible. |
ECC_NIST_EDWARDS25519 |
AWS documents it "signing and verification only". Cannot derive a shared secret. |
ECC_SECG_P256K1 |
Sign/verify only; excluded by AWS from the key-agreement specs. |
ECC_NIST_P256 / P384 / P521 |
Supported for KEY_AGREEMENT. These are the only three. |
NIST P-curves have cofactor h=1, so the cofactor ECDH primitive KMS implements (per SP 800-56A) is plain ECDH here — no adjustment needed.
The status of this claim¶
The primitives compose, and provider support is verified: the hashicorp/aws
6.58.0 enum for key_usage is exactly SIGN_VERIFY, ENCRYPT_DECRYPT,
GENERATE_VERIFY_MAC, KEY_AGREEMENT.
What has not been demonstrated is an end-to-end decrypt of a real armoured
message through DeriveSharedSecret. That is tracked as the open question on
the spec, and it is why you should not publish an Encryption: field pointing
at a certificate built on this module until it is answered.