openpgp A. Gallagher, Ed. Internet-Draft PGPKeys.EU Intended status: Informational 5 October 2026 Expires: 8 April 2027 Padding in OpenPGP draft-gallagher-openpgp-padding-00 Abstract This document provides updated guidance around the generation of padding in OpenPGP data streams. It does not specify any new OpenPGP wire formats, but does propose some higher-level best practices. About This Document This note is to be removed before publishing as an RFC. The latest revision of this draft can be found at https://andrewgdotcom.gitlab.io/padding. Status information for this document may be found at https://datatracker.ietf.org/doc/draft- gallagher-openpgp-padding/. Discussion of this document takes place on the OpenPGP Working Group mailing list (mailto:openpgp@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/openpgp/. Subscribe at https://www.ietf.org/mailman/listinfo/openpgp/. Source for this draft and an issue tracker can be found at https://gitlab.com/andrewgdotcom/padding. Status of This Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet- Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 8 April 2027. Gallagher Expires 8 April 2027 [Page 1] Internet-Draft Padding in OpenPGP October 2026 Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 2. Conventions and Definitions . . . . . . . . . . . . . . . . . 3 3. Clarification of Existing Guidance . . . . . . . . . . . . . 3 3.1. Non-Random Padding . . . . . . . . . . . . . . . . . . . 3 3.2. Multiple Padding Packets . . . . . . . . . . . . . . . . 4 4. New Guidance . . . . . . . . . . . . . . . . . . . . . . . . 4 4.1. API Design . . . . . . . . . . . . . . . . . . . . . . . 5 4.2. Construction of Padding Packets . . . . . . . . . . . . . 5 4.2.1. Overlong Encoding . . . . . . . . . . . . . . . . . . 6 4.2.2. Multi-Packet Encoding . . . . . . . . . . . . . . . . 6 5. Conformal Padding . . . . . . . . . . . . . . . . . . . . . . 7 5.1. Outline of the Conformal Padding Problem . . . . . . . . 8 5.2. Derivation of the Conformal Padding Formula . . . . . . . 8 5.3. Construction of Conformal Padding Strings . . . . . . . . 9 6. Bucketing Strategies . . . . . . . . . . . . . . . . . . . . 10 7. Security Considerations . . . . . . . . . . . . . . . . . . . 11 8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 11 9. Normative References . . . . . . . . . . . . . . . . . . . . 11 Appendix A. Conformal Padding Visual Demonstration . . . . . . . 11 Appendix B. Acknowledgments . . . . . . . . . . . . . . . . . . 13 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 13 1. Introduction Section 5.14 of [RFC9580] specifies a Padding packet, but provides little guidance beyond recommending that its contents SHOULD be random octets. This document provides the following additional guidance: * Random padding is overkill in certain contexts, and padding with zeros can be a viable, cheaper alternative Gallagher Expires 8 April 2027 [Page 2] Internet-Draft Padding in OpenPGP October 2026 * Some padding lengths are not possible to generate without special- casing, so a method is proposed to generate these "irregular" lengths safely * Padding may be added either before or after ASCII-armoring, so a "conformal padding" scheme is proposed to ensure these have equivalent properties 2. Conventions and Definitions The term "OpenPGP Certificate" is used in this document interchangeably with "OpenPGP Transferable Public Key", as defined in Section 10.1 of [RFC9580]. The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. 3. Clarification of Existing Guidance We draw attention to the fact that the following two scenarios are implicitly permitted by [RFC9580]. 3.1. Non-Random Padding Section 5.14 of [RFC9580] states that random padding is recommended to mitigate the effects of compression: Its contents SHOULD be random octets to make the length obfuscation it provides more robust even when compressed. It also states that one of the use cases is: As the last packet of an Optionally Padded Message within a version 2 Symmetrically Encrypted and Integrity Protected Data packet There is no advantage gained by using random padding directly inside an encrypted data packet. The packet grammar is clear that such padding will never be compressed, since any (optional) compression is applied before padding. On the other hand, random padding data comes at a (small) calculation cost. Gallagher Expires 8 April 2027 [Page 3] Internet-Draft Padding in OpenPGP October 2026 The same argument applies to any context where the generating implementation knows that its output will not be compressed before encryption. Padding MAY consist of non-random data, such as a string of zeros, when used in such a context. 3.2. Multiple Padding Packets Section 10.3 of [RFC9580] defines an Optionally Padded Message as: OpenPGP Message | OpenPGP Message, Padding Packet. But Section 5.14 of [RFC9580] permits additional padding packets: An implementation MUST be able to process Padding packets anywhere else in an OpenPGP stream We interpret these statements inclusively, to mean that an Optionally Padded Message MAY contain any number of Padding packets. It is RECOMMENDED however that any Padding packets come after the message data and not before, to minimise variance. 4. New Guidance The crucial property of padding is its overall length, however an OpenPGP packet consists not only of the packet body but also the packet framing. In OpenPGP format (Section 4.2.1 of [RFC9580]), this framing consists of a single initial octet followed by a Body Length header. The Body Length header is itself of variable length (the "length-of-length"): * A 1-octet Body Length header encodes packet lengths of up to 191 octets. * A 2-octet Body Length header encodes packet lengths of 192 to 8383 octets. * A 5-octet Body Length header encodes packet lengths of up to 4,294,967,295 (0xFFFFFFFF) octets in length. The minimum size for a framed OpenPGP packet is therefore two octets. While it is possible to pad by zero octets by not including any Padding packet, it is not possible to pad a data stream by one octet. The largest possible Padding packet has a body length of 4,294,967,295 and a total framed length of 4,294,967,301. Gallagher Expires 8 April 2027 [Page 4] Internet-Draft Padding in OpenPGP October 2026 To ensure that an uninterrupted range of padding lengths can be generated, an implementation that supports the generation of padding must pad by an excess of E octets, where E >= 2. For consistency, all padded data streams must be over-padded by the same excess - the caller must anticipate this and allocate sufficient space. 4.1. API Design It is RECOMMENDED that OpenPGP libraries expose a low-level padding- generation API to higher layers that is constrained within safe limits: * The API should accept an unsigned integer of no more than 32 bits as its length parameter * The API should generate padding of total length length+E octets, where the excess E >= 2 is a constant This ensures that all valid inputs produce valid output of length between E and 4,294,967,295 + E inclusive. If E <= 6, the maximum padding length can be represented by a single Padding packet. It is RECOMMENDED to fix E = 3 to ensure compatibility with conformal padding (see Section 5). In the (highly unlikely) event that a caller requires more than 4GB of padding it would have to call the API multiple times. Each additional API call will introduce an additional excess that must be compensated for by the caller. Generating more than 4GB of padding is therefore NOT RECOMMENDED. 4.2. Construction of Padding Packets An OpenPGP ipmlementation will generally construct a framed packet using a deterministic process: * determine the packet body length B * determine the shortest length-of-length L capable of encoding B * construct a framed packet of total length F = 1 + L + B However, when constructing padding we must work backwards from a known framed packet length F to find B. If the shortest length-of- length is always used, then there are values of F that cannot be obtained by any choice of B. Gallagher Expires 8 April 2027 [Page 5] Internet-Draft Padding in OpenPGP October 2026 For example, if B = 191, then L = 1 and F = 1 + 1 + 191 = 193 - but increment B by one and L increases to 2, giving F = 1 + 2 + 192 = 195. Similarly, if B = 8383, then F = 1 + 2 + 8383 = 8386, while B = 8384 gives F = 1 + 5 + 8384 = 8390. There is no choice of body length that results in a framed packet length of 194, 8387, 8388, or 8389 if the "shortest length-of-length" method is used. A generating implementation must therefore handle the generation of Padding packets as a special case, in order to permit the construction of such "irregular" framed packet lengths. The optimal method will vary between implementations, but some suggestions are listed below. 4.2.1. Overlong Encoding Most receiving implementations will accept overlong encodings of packet body lengths. An example of an overlong encoding would be a 5-octet Body Length header indicating a body length of 184, giving F = 1 + 5 + 188 = 194. If an implementation can override the length-of-length L in its own framing layer, it can construct all framed packet lengths F > 2 (including "irregular" ones) as follows: * if F < 194: - B := F - 2 - L := 1 * else: - B = F - 6 - L := 5 Two-octet length encodings are not necessary if overlong encodings are acceptable. This simplifies the algorithm significantly. 4.2.2. Multi-Packet Encoding A generating implementation MAY output multiple Padding packets, and a receiving implementation MUST accept them (see above). An implementation can therefore split the padding into two packets to avoid the "irregular" lengths: * if F is one of the "irregular" framed lengths (194, 8387, 8388, or 8389): Gallagher Expires 8 April 2027 [Page 6] Internet-Draft Padding in OpenPGP October 2026 - Output an initial Padding packet of framed length N, where 2 < N < 192 - Followed by a second Padding packet of framed length F - N * else, output a single packet of framed length F Every non-"irregular" framed length F can then be constructed using "shortest length-of-length" as normal: * if F < 194, then B := F - 2 * else if F < 8387, then B := F - 3 * else B := F - 6 5. Conformal Padding A generating implementation MAY pad a binary OpenPGP data stream using Padding packets before ASCII-armoring. If this is not possible or appropriate, armored data can be padded using printable characters. One scenario where Padding packets may not be appropriate is when transferring a certificate bundle containing only v4 certificates, since the Padding packet is not backwards-compatible with legacy clients (see Section 10.1.5 of [RFC9580]). Conformal padding is a method of padding OpenPGP data after ASCII- armoring so that no additional information is leaked to an eavesdropper if: * the same ASCII data stream is transferred using two different line-ending formats * the same data is transferred in both armored and binary formats In the general case, armored data MAY consist of lines up to 76 printable characters and MAY also contain extra headers and a checksum. If conformal padding is enabled, extra headers and checksums MUST NOT be added, and the Base64 data MUST be line-broken at exactly 64 characters. This ensures consistency of padded data length after newline characters are included. Gallagher Expires 8 April 2027 [Page 7] Internet-Draft Padding in OpenPGP October 2026 5.1. Outline of the Conformal Padding Problem We recall that ASCII armored data always comes in a whole number of padded "chunks", each containing four printable characters and representing up to three bytes of data. We also recall that binary padding introduces an excess of E octets to all data streams, where 2 <= E <= 6. IFF E is divisible by 3, this consistently adds e = E/3 chunks of Base64 characters, regardless of unpadded data length. Let us assume that E = 3, for reasons that will be explained in Section 5.2. Binary-padded data is then represented as an armored block of J = M/3 + e = (L-1)*16 + 1 chunks and L newlines, where * L = M/48 + 1 is the number of lines, * e = 1 is the number of excess chunks due to binary over-padding, * M is the desired padded data length in bytes (NOT including excess). Unpadded data is represented as an armored block of j = ceil(m/3) = (l-1)*16 + i chunks and l newlines, where * l = ceil(m/48) is the number of lines, * i = ceil(m/3) - (l-1)*16 is the number of Base64 chunks on the last line (1 <= i <= 16), * m is the unpadded data length in bytes. The shortfall of chunks "missing" from an unpadded armored block due to lack of binary padding is J - j = M/3 + e - ceil(m/3), and the corresponding shortfall of newlines is L - l = M/48 + 1 - ceil(m/48). 5.2. Derivation of the Conformal Padding Formula We wish to ensure that a padding string of length equal to the shortfall of chunks also compensates for the shortfall of newlines. For a newline-terminated string of length c split across lines of length C, the total number of newlines required is ceil(c/C). Let us split a padding string of M/3 + e - ceil(m/3) chunks across lines of length 64 characters (=16 chunks), where e is the number of excess chunks in our over-padding. The number of newlines k automatically added to the string is therefore: k = ceil( (M/3 + e - ceil(m/3)) / 16 ) IFF M is divisible by 48, we can bring it outside: k = M/48 + ceil( (e - ceil(m/3)) / 16 ) Since ceil(-n) = -floor(n): Gallagher Expires 8 April 2027 [Page 8] Internet-Draft Padding in OpenPGP October 2026 k = M/48 - floor( (ceil(m/3) - e) / 16 ) Now IFF the binary excess is E = 3, then e = 1 and we can apply the identity floor( (n-1) / m ) = ceil(n/m) - 1 to get: k = M/48 + 1 - ceil( ceil(m/3) / 16 ) k = M/48 + 1 - ceil(m/48) which equals the shortfall of newlines L - l identified above. This means that IFF: * the binary excess E = 3, * the target padded data length M (without excess) is divisible by 48, * ASCII armor is always line-wrapped at 64 characters, * and we over-pad by exactly one excess chunk (corresponding to three excess bytes of binary padding), the act of splitting a padding string of the required length into 64-character lines exactly compensates for the shortfall of newlines. 5.3. Construction of Conformal Padding Strings A padding string MUST always be present when padding is enabled, regardless of the original data length. A conformal padding string consists of: * a fixed 4-character prefix P: = to represent the excess, * followed by (M/3 - ceil(m/3))*4 additional printable characters, * line-wrapped at the 64th column, * with the first four characters of each subsequent line also being P: =, * and newline-terminated. An example of a conformal padding string is: P: =2/b1DpDX1pBa+VjLcJfVh0fOpFLe3BGaiBCqouqc0wlw2pjyBnpxZFfwnss5rCbA P: =Z6y02No1 Gallagher Expires 8 April 2027 [Page 9] Internet-Draft Padding in OpenPGP October 2026 It is RECOMMENDED to place the padding string before the message data, to force the receiver to consume all of the padding and thereby prevent early connection drops. Depending on context, the padding string MAY be located in one of several locations: * Inside the ASCII armor, as armor headers * Before the ASCII armor, as plain text * Within an enclosing protocol, for example as HTTP headers The entire string MUST be included verbatim, without indentation, charset transformation, escape sequences or any other modification. It MAY be enclosed in another structure such as quotes or a block comment, so long as it is internally unchanged. The conformal padding string is designed to be valid in a broad variety of contexts, however it may cause warnings such as "unknown header" to be generated by a receiving implementation. It is the generating implementation's responsibility to ensure that padding is placed in a way that minimises harmful consequences. The prefix and line-wrapping requirements ensure that the combined padding and armor always contains both a constant number of printable characters, and a constant number of newlines. As a consequence, the anonymity cohort survives transformation between newline formats. Further, IFF binary padding uses a fixed excess of E = 3, the anonymity cohort also survives transformation between binary-padded and ASCII-padded format (assuming the same bucketing model is used for both). A visual demonstration of this can be found in Appendix A. 6. Bucketing Strategies A complete discussion of bucketing strategies is beyond the scope of this document. Many common bucketing strategies such as "next power of two" are not compatible with conformal padding, because they do not allocate bucket widths in multiples of of 48. Determination of an optimal bucketing strategy must be informed by the statistical properties of the data being padded. A bucketing strategy that works well for arbitrary internet messages may not be suitable for data sets with less well-behaved statistics, such as certificates. A general-purpose library API MAY encourage multiples of 48 to enable conformal padding, but SHOULD refrain from making any other assumptions about bucketing strategies. Gallagher Expires 8 April 2027 [Page 10] Internet-Draft Padding in OpenPGP October 2026 7. Security Considerations ((TO BE COMPLETED)) 8. IANA Considerations IANA is requested to add a reference to this document from the "Padding Packet" entry in the OpenPGP Packet Types Registry. 9. Normative References [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC9580] Wouters, P., Ed., Huigens, D., Winter, J., and Y. Niibe, "OpenPGP", RFC 9580, DOI 10.17487/RFC9580, July 2024, . Appendix A. Conformal Padding Visual Demonstration Let us consider a toy bucketing model, where the bucket width is 96 bytes (48*2), and the byte values of the "data" and "padding" are faked for visual clarity. Consider an unpadded certificate of binary length 86 bytes, armored: -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddec= -----END PGP PUBLIC KEY BLOCK----- When binary-padded to 96+3 bytes before armoring, it is 214 bytes long over 6 lines. Note that the three bytes of binary over-padding cause the encoded data to wrap onto an additional line: -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecAAAAAAAAAAAAA AAAA -----END PGP PUBLIC KEY BLOCK----- Gallagher Expires 8 April 2027 [Page 11] Internet-Draft Padding in OpenPGP October 2026 When first armored and then ASCII-padded with a (96/3 - ceil(86/3) + 1)*4 = 16 character padding string, it is also 214 bytes long over 6 lines: P: =DECAFBADDECA -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddec= -----END PGP PUBLIC KEY BLOCK----- Now consider certificates of other lengths - for comparison we show first the binary-padded and then the ASCII-padded version of each. If the certificate is exactly 96 bytes, we still add the minimal (96/3 - ceil(96/3) + 1)*4 = 4 character padding string P: / together with its CRLF: -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad AAAA -----END PGP PUBLIC KEY BLOCK----- or P: = -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad -----END PGP PUBLIC KEY BLOCK----- If the certificate is exactly N lines shorter than the bucket, the padding string wraps onto the (N+1)th line, compensating for the "missing" CRLF. This 48-byte certificate requires (96/3 - ceil(48/3) + 1)*4 = 68 characters of padding: -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAA -----END PGP PUBLIC KEY BLOCK----- or Gallagher Expires 8 April 2027 [Page 12] Internet-Draft Padding in OpenPGP October 2026 P: =DECAFBADDECAFBADDECAFBADDECAFBADDECAFBADDECAFBADDECAFBADDECA P: = -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad -----END PGP PUBLIC KEY BLOCK----- If the armored certificate is one chunk longer than an integer number of lines, the padding string doesn't wrap, avoiding an off-by-one error. This 50-byte certificate requires (96/3 - ceil(50/3) + 1)*4 = 64 characters of padding: -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad decAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAA -----END PGP PUBLIC KEY BLOCK----- or P: =DECAFBADDECAFBADDECAFBADDECAFBADDECAFBADDECAFBADDECAFBADDECA -----BEGIN PGP PUBLIC KEY BLOCK----- decafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbaddecafbad dec= -----END PGP PUBLIC KEY BLOCK----- Note that in each padded example above there are always 214 bytes over 6 lines. This is a conserved property of a 96-byte bucket. Appendix B. Acknowledgments The author would like to thank Heiko Schäfer for discussions. Author's Address Andrew Gallagher (editor) PGPKeys.EU Email: andrewg@andrewg.com Gallagher Expires 8 April 2027 [Page 13]