37 Generate a fresh RSA key pair.
39 The generated key includes all components needed for PGP operations:
40 - n: public modulus (p * q)
41 - e: public exponent (typically 65537)
42 - d: private exponent (e^-1 mod phi(n))
43 - p, q: prime factors of n
44 - u: coefficient (p^-1 mod q) for CRT optimization
46 The caller can pass the wanted key size in input, for a default of 2048
47 bytes. This function returns the RSA key components, after performing
48 some validation on them.
73 Validate a generated RSA key.
75 This function performs basic validation to ensure the RSA key is properly
76 constructed and all components are consistent, at least mathematically.
78 Validations performed:
79 1. n = p * q (modulus is product of primes)
80 2. gcd(e, phi(n)) = 1 (public exponent is coprime to phi(n))
81 3. (d * e) mod(phi(n)) = 1 (private exponent is multiplicative inverse)
82 4. (u * p) (mod q) = 1 (coefficient is correct for CRT)
85 n, e, d, p, q, u = rsa[
'n'], rsa[
'e'], rsa[
'd'], rsa[
'p'], rsa[
'q'], rsa[
'u']
89 raise ValueError(
"RSA validation failed: n <> p * q")
93 raise ValueError(
"RSA validation failed: p = q (not allowed)")
96 phi_n = (p - 1) * (q - 1)
104 if gcd(e, phi_n) != 1:
105 raise ValueError(
"RSA validation failed: gcd(e, phi(n)) <> 1")
108 if (d * e) % phi_n != 1:
109 raise ValueError(
"RSA validation failed: d * e <> 1 (mod phi(n))")
113 raise ValueError(
"RSA validation failed: u * p <> 1 (mod q)")
117 Encode an integer as an OpenPGP Multi-Precision Integer (MPI).
119 Format (RFC 4880, Section 3.2):
120 - 2 bytes: bit length of the integer (big-endian)
121 - N bytes: the integer in big-endian format
123 This is used to encode RSA key components (n, e, d, p, q, u) in PGP
126 The integer to encode is given in input, returning an MPI-encoded
130 mpi_encode(65537) -> b'\x00\x11\x01\x00\x01'
131 (17 bits, value 0x010001)
134 raise ValueError(
"MPI cannot encode negative integers")
150 Create a new OpenPGP packet with a proper header.
152 OpenPGP packet format (RFC 4880, Section 4.2):
153 - New packet format: 0xC0 | tag
154 - Length encoding depends on payload size:
156 * 192-8383: two bytes (192 + ((length - 192) >> 8), (length - 192) & 0xFF)
157 * 8384+: five bytes (0xFF + 4-byte big-endian length)
159 The packet is built from a "tag" (1-63) and some "payload" data. The
160 result generated is a complete OpenPGP packet.
163 new_packet(1, b'data') -> b'\xC1\x04data'
164 (Tag 1, length 4, payload 'data')
167 first = 0xC0 | (tag & 0x3F)
177 llen = bytes([192 + (ln2 >> 8), ln2 & 0xFF])
182 return bytes([first]) + llen + payload
186 Build the key data, containing an RSA private key.
188 The RSA contents should have been generated previously.
190 Format (see RFC 4880, Section 5.5.3):
191 - 1 byte: version (4)
192 - 4 bytes: creation time (current Unix timestamp)
193 - 1 byte: public key algorithm (2 = RSA encrypt)
194 - MPI: RSA public modulus n
195 - MPI: RSA public exponent e
196 - 1 byte: string-to-key usage (0 = no encryption)
197 - MPI: RSA private exponent d
200 - MPI: RSA coefficient u = p^-1 mod q
201 - 2 bytes: checksum of private key material
203 This function takes a set of RSA key components in input (n, e, d, p, q, u)
204 and returns a secret key packet.
213 pub = ver + ctime + algo + n_mpi + e_mpi
216 hide_type = bytes([0])
223 private_data = d_mpi + p_mpi + q_mpi + u_mpi
224 cksum = sum(private_data) & 0xFFFF
226 secret = hide_type + private_data +
struct.pack(
'>H', cksum)
227 payload = pub + secret
233 Implement OpenPGP CFB mode with resync.
235 OpenPGP CFB mode is a variant of standard CFB with a resync operation
236 after the first two blocks.
238 Algorithm (RFC 4880, Section 13.9):
239 1. Block 1: FR=zeros, encrypt full block_size bytes
240 2. Block 2: FR=block1, encrypt only 2 bytes
241 3. Resync: FR = block1[2:] + block2
242 4. Remaining blocks: standard CFB mode
244 This function uses the following arguments:
245 - key: AES encryption key (16 bytes for AES-128)
246 - plaintext: Data to encrypt
253 FR = b
'\x00' * block_size
255 block1 = bytes(a ^ b
for a, b
in zip(FRE, plaintext[0:16]))
261 block2 = bytes(a ^ b
for a, b
in zip(FRE[0:2], plaintext[16:18]))
266 FR = block1[2:] + block2
270 while pos <
len(plaintext):
272 chunk_len = min(block_size,
len(plaintext) - pos)
273 chunk = plaintext[pos:pos+chunk_len]
274 enc_chunk = bytes(a ^ b
for a, b
in zip(FRE[:chunk_len], chunk))
275 ciphertext += enc_chunk
278 if chunk_len == block_size:
282 FR = enc_chunk + FR[chunk_len:]
289 Build a literal data packet containing a message.
291 Format (RFC 4880, Section 5.9):
292 - 1 byte: data format ('b' = binary, 't' = text, 'u' = UTF-8 text)
293 - 1 byte: filename length (0 = no filename)
294 - N bytes: filename (empty in this case)
295 - 4 bytes: date (current Unix timestamp)
296 - M bytes: literal data
298 The data used to build the packet is given in input, with the generated
310 Build a symmetrically-encrypted data packet using AES-128-CFB.
312 This packet contains encrypted data using the session key. The format
313 includes a random prefix, for security (see RFC 4880, Section 5.7).
316 - Random prefix (block_size bytes)
317 - Prefix repeat (last 2 bytes of prefix repeated)
318 - Encrypted literal data packet
320 This function uses the following set of arguments:
321 - sess_key: Session key for encryption
322 - cipher_algo: Cipher algorithm identifier (7 = AES-128)
323 - payload: Data to encrypt (wrapped in literal data packet)
331 prefix = prefix_random + prefix_random[-2:]
337 plaintext = prefix + literal_pkt
346 Build a public-key encrypted key.
348 This is a very important function, as it is able to create the packet
349 triggering the overflow check. This function can also be used to create
352 Format (RFC 4880, Section 5.1):
353 - 1 byte: version (3)
354 - 8 bytes: key ID (0 = any key accepted)
355 - 1 byte: public key algorithm (2 = RSA encrypt)
356 - MPI: RSA-encrypted session key
358 This uses in arguments the generated RSA key pair, and the session key
359 to encrypt. The latter is manipulated to trigger the overflow.
361 This function returns a complete packet encrypted by a session key.
371 algo_byte = bytes([7])
372 cksum = sum(sess_key) & 0xFFFF
373 M = algo_byte + sess_key +
struct.pack(
'>H', cksum)
379 ps_len = total_len -
len(M) - 2
382 raise ValueError(f
"Padding string too short ({ps_len} bytes); need at least 8 bytes. "
383 f
"Message length: {len(M)}, Modulus size: {n_bytes} bytes")
386 PS = bytes([0xFF]) * ps_len
390 padded = bytes([0x02]) + PS + bytes([0x00]) + M
393 if len(padded) != n_bytes:
394 raise ValueError(f
"Padded message length ({len(padded)}) doesn't match RSA modulus size ({n_bytes})")
400 if m_int >= rsa[
'n']:
401 raise ValueError(
"Padded message is larger than RSA modulus")
404 c_int =
pow(m_int, rsa[
'e'], rsa[
'n'])
413 payload = ver + key_id + algo + c_mpi
419 This function creates a crafted message, with a long session key
422 This takes in input the RSA key components generated previously,
423 returning a concatenated set of PGP packets crafted for the purpose
429 prefix = AES_KEY + b
"\x00" * 16 +
p32(0x10)
451 return b
"".join(packets)
482 print(f
'''-- Test for overflow with session key at decrypt.
483-- Data automatically generated by scripts/{file_basename}.
484-- See this file for details explaining how this data is generated.
485SELECT pgp_pub_decrypt_bytea(
486'\\x{message_data}'::bytea,
487'\\x{key_data}'::bytea);''',
490if __name__ ==
"__main__":
void print(const void *obj)
bytes build_tag1_packet(dict rsa, bytes sess_key)
pgp_cfb_encrypt_resync(key, plaintext)
bytes build_key_data(dict rsa)
dict generate_rsa_keypair(int key_size=2048)
bytes build_literal_data_packet(bytes data)
bytes build_message_data(dict rsa)
bytes new_packet(int tag, bytes payload)
bytes build_symenc_data_packet(bytes sess_key, int cipher_algo, bytes payload)
None validate_rsa_key(dict rsa)
static uint32 gcd(uint32 a, uint32 b)