4 import subprocess as _subprocess
5 import struct as _struct
8 def _get_stdout(args, stdin=None):
11 stdin_pipe = _subprocess.PIPE
12 p = _subprocess.Popen(args, stdin=stdin_pipe, stdout=_subprocess.PIPE)
13 stdout, stderr = p.communicate(stdin)
16 raise RuntimeError(status)
20 class PGPPacket (dict):
21 # http://tools.ietf.org/search/rfc4880
22 _old_format_packet_length_type = { # type: (bytes, struct type)
23 0: (1, 'B'), # 1-byte unsigned integer
24 1: (2, 'H'), # 2-byte unsigned integer
25 2: (4, 'I'), # 4-byte unsigned integer
31 1: 'public-key encrypted session key packet',
32 2: 'signature packet',
33 3: 'symmetric-key encrypted session key packet',
34 4: 'one-pass signature packet',
35 5: 'secret-key packet',
36 6: 'public-key packet',
37 7: 'secret-subkey packet',
38 8: 'compressed data packet',
39 9: 'symmetrically encrypted data packet',
41 11: 'literal data packet',
44 14: 'public-subkey packet',
45 17: 'user attribute packet',
46 18: 'sym. encrypted and integrity protected data packet',
47 19: 'modification detection code packet',
54 _public_key_algorithms = {
55 1: 'rsa (encrypt or sign)',
56 2: 'rsa encrypt-only',
58 16: 'elgamal (encrypt-only)',
59 17: 'dsa (digital signature algorithm)',
60 18: 'reserved for elliptic curve',
61 19: 'reserved for ecdsa',
62 20: 'reserved (formerly elgamal encrypt or sign)',
63 21: 'reserved for diffie-hellman',
77 _symmetric_key_algorithms = {
78 0: 'plaintext or unencrypted data',
85 7: 'aes with 128-bit key',
86 8: 'aes with 192-bit key',
87 9: 'aes with 256-bit key',
102 _cipher_block_size = { # in bits
103 'aes with 128-bit key': 128,
104 'aes with 192-bit key': 128,
105 'aes with 256-bit key': 128,
109 _compression_algorithms = {
152 _string_to_key_types = {
156 3: 'iterated and salted',
171 0x00: 'binary document',
172 0x01: 'canonical text document',
174 0x10: 'generic user id and public-key packet',
175 0x11: 'persona user id and public-key packet',
176 0x12: 'casual user id and public-key packet',
177 0x13: 'postitive user id and public-key packet',
178 0x18: 'subkey binding',
179 0x19: 'primary key binding',
181 0x20: 'key revocation',
182 0x28: 'subkey revocation',
183 0x30: 'certification revocation',
185 0x50: 'third-party confirmation',
188 _clean_type_regex = _re.compile('\W+')
190 def _clean_type(self):
191 return self._clean_type_regex.sub('_', self['type'])
193 def from_bytes(self, data):
194 offset = self._parse_header(data=data)
195 packet = data[offset:offset + self['length']]
196 if len(packet) < self['length']:
197 raise ValueError('packet too short ({} < {})'.format(
198 len(packet), self['length']))
199 offset += self['length']
200 method_name = '_parse_{}'.format(self._clean_type())
201 method = getattr(self, method_name, None)
203 raise NotImplementedError(
204 'cannot parse packet type {!r}'.format(self['type']))
208 def _parse_header(self, data):
211 always_one = packet_tag & 1 << 7
213 raise ValueError('most significant packet tag bit not set')
214 self['new-format'] = packet_tag & 1 << 6
215 if self['new-format']:
216 type_code = packet_tag & 0b111111
217 raise NotImplementedError('new-format packet length')
219 type_code = packet_tag >> 2 & 0b1111
220 self['length-type'] = packet_tag & 0b11
221 length_bytes, length_type = self._old_format_packet_length_type[
224 raise NotImplementedError(
225 'old-format packet of indeterminate length')
226 length_format = '>{}'.format(length_type)
227 length_data = data[offset: offset + length_bytes]
228 offset += length_bytes
229 self['length'] = _struct.unpack(length_format, length_data)[0]
230 self['type'] = self._packet_types[type_code]
234 def _parse_multiprecision_integer(data):
235 r"""Parse RFC 4880's multiprecision integers
237 >>> PGPPacket._parse_multiprecision_integer(b'\x00\x01\x01')
239 >>> PGPPacket._parse_multiprecision_integer(b'\x00\x09\x01\xff')
242 bits = _struct.unpack('>H', data[:2])[0]
244 length = (bits + 7) // 8
246 for i in range(length):
247 value += data[offset + i] * 1 << (8 * (length - i - 1))
249 return (offset, value)
251 def _parse_string_to_key_specifier(self, data):
252 self['string-to-key-type'] = self._string_to_key_types[data[0]]
254 if self['string-to-key-type'] == 'simple':
255 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
258 elif self['string-to-key-type'] == 'salted':
259 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
262 self['string-to-key-salt'] = data[offset: offset + 8]
264 elif self['string-to-key-type'] == 'iterated and salted':
265 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
268 self['string-to-key-salt'] = data[offset: offset + 8]
270 self['string-to-key-coded-count'] = data[offset]
273 raise NotImplementedError(
274 'string-to-key type {}'.format(self['string-to-key-type']))
277 def _parse_public_key_packet(self, data):
278 self._parse_generic_public_key_packet(data=data)
280 def _parse_public_subkey_packet(self, data):
281 self._parse_generic_public_key_packet(data=data)
283 def _parse_generic_public_key_packet(self, data):
284 self['key-version'] = data[0]
286 if self['key-version'] != 4:
287 raise NotImplementedError(
288 'public (sub)key packet version {}'.format(
289 self['key-version']))
291 self['creation-time'], algorithm = _struct.unpack(
292 '>IB', data[offset: offset + length])
294 self['public-key-algorithm'] = self._public_key_algorithms[algorithm]
295 if self['public-key-algorithm'].startswith('rsa '):
296 o, self['public-modulus'] = self._parse_multiprecision_integer(
299 o, self['public-exponent'] = self._parse_multiprecision_integer(
302 elif self['public-key-algorithm'].startswith('dsa '):
303 o, self['prime'] = self._parse_multiprecision_integer(
306 o, self['group-order'] = self._parse_multiprecision_integer(
309 o, self['group-generator'] = self._parse_multiprecision_integer(
312 o, self['public-key'] = self._parse_multiprecision_integer(
315 elif self['public-key-algorithm'].startswith('elgamal '):
316 o, self['prime'] = self._parse_multiprecision_integer(
319 o, self['group-generator'] = self._parse_multiprecision_integer(
322 o, self['public-key'] = self._parse_multiprecision_integer(
326 raise NotImplementedError(
327 'algorithm-specific key fields for {}'.format(
328 self['public-key-algorithm']))
331 def _parse_secret_key_packet(self, data):
332 self._parse_generic_secret_key_packet(data=data)
334 def _parse_secret_subkey_packet(self, data):
335 self._parse_generic_secret_key_packet(data=data)
337 def _parse_generic_secret_key_packet(self, data):
338 offset = self._parse_generic_public_key_packet(data=data)
339 string_to_key_usage = data[offset]
341 if string_to_key_usage in [255, 254]:
342 self['symmetric-encryption-algorithm'] = (
343 self._symmetric_key_algorithms[data[offset]])
345 offset += self._parse_string_to_key_specifier(data=data[offset:])
347 self['symmetric-encryption-algorithm'] = (
348 self._symmetric_key_algorithms[string_to_key_usage])
349 if string_to_key_usage:
350 block_size_bits = self._cipher_block_size.get(
351 self['symmetric-encryption-algorithm'], None)
352 if block_size_bits % 8:
353 raise NotImplementedError(
354 ('{}-bit block size for {} is not an integer number of bytes'
356 block_size_bits, self['symmetric-encryption-algorithm']))
357 block_size = block_size_bits // 8
359 raise NotImplementedError(
360 'unknown block size for {}'.format(
361 self['symmetric-encryption-algorithm']))
362 self['initial-vector'] = data[offset: offset + block_size]
364 if string_to_key_usage in [0, 255]:
368 self['secret-key'] = data[offset:key_end]
370 self['secret-key-checksum'] = data[key_end:]
372 def _parse_signature_packet(self, data):
373 self['signature-version'] = data[0]
375 if self['signature-version'] != 4:
376 raise NotImplementedError(
377 'signature packet version {}'.format(
378 self['signature-version']))
379 self['signature-type'] = self._signature_types[data[offset]]
381 self['public-key-algorithm'] = self._public_key_algorithms[
384 self['hash-algorithm'] = self._hash_algorithms[data[offset]]
386 hashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
388 self['hashed-subpackets'] = data[offset: offset + hashed_count]
389 offset += hashed_count
390 unhashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
392 self['unhashed-subpackets'] = data[offset: offset + unhashed_count]
393 offset += unhashed_count
394 self['signed-hash-word'] = data[offset: offset + 2]
396 self['signature'] = data[offset:]
402 def packets_from_bytes(data):
404 while offset < len(data):
406 offset += packet.from_bytes(data=data[offset:])
410 def migrate(old_key, new_key):
411 """Add the old key and sub-keys to the new key
413 For example, to upgrade your master key, while preserving old
414 signatures you'd made. You will lose signature *on* your old key
415 though, since sub-keys can't be signed (I don't think).
417 old_key_export = _get_stdout(
418 ['gpg', '--export', old_key])
419 old_key_packets = list(
420 packets_from_bytes(data=old_key_export))
421 if old_key_packets[0]['type'] != 'public-key packet':
423 '{} does not start with a public-key packet'.format(
425 old_key_secret_export = _get_stdout(
426 ['gpg', '--export-secret-keys', old_key])
427 old_key_secret_packets = list(
428 packets_from_bytes(data=old_key_secret_export))
431 pprint.pprint(old_key_packets)
432 pprint.pprint(old_key_secret_packets)
435 if __name__ == '__main__':
438 old_key, new_key = _sys.argv[1:3]
439 migrate(old_key=old_key, new_key=new_key)