3 import getpass as _getpass
4 import hashlib as _hashlib
7 import subprocess as _subprocess
8 import struct as _struct
10 import Crypto.Cipher.AES as _crypto_cipher_aes
11 import Crypto.Cipher.Blowfish as _crypto_cipher_blowfish
12 import Crypto.Cipher.CAST as _crypto_cipher_cast
13 import Crypto.Cipher.DES3 as _crypto_cipher_des3
16 def _get_stdout(args, stdin=None):
19 stdin_pipe = _subprocess.PIPE
20 p = _subprocess.Popen(args, stdin=stdin_pipe, stdout=_subprocess.PIPE)
21 stdout, stderr = p.communicate(stdin)
24 raise RuntimeError(status)
28 class PGPPacket (dict):
29 # http://tools.ietf.org/search/rfc4880
30 _old_format_packet_length_type = { # type: (bytes, struct type)
31 0: (1, 'B'), # 1-byte unsigned integer
32 1: (2, 'H'), # 2-byte unsigned integer
33 2: (4, 'I'), # 4-byte unsigned integer
39 1: 'public-key encrypted session key packet',
40 2: 'signature packet',
41 3: 'symmetric-key encrypted session key packet',
42 4: 'one-pass signature packet',
43 5: 'secret-key packet',
44 6: 'public-key packet',
45 7: 'secret-subkey packet',
46 8: 'compressed data packet',
47 9: 'symmetrically encrypted data packet',
49 11: 'literal data packet',
52 14: 'public-subkey packet',
53 17: 'user attribute packet',
54 18: 'sym. encrypted and integrity protected data packet',
55 19: 'modification detection code packet',
62 _public_key_algorithms = {
63 1: 'rsa (encrypt or sign)',
64 2: 'rsa encrypt-only',
66 16: 'elgamal (encrypt-only)',
67 17: 'dsa (digital signature algorithm)',
68 18: 'reserved for elliptic curve',
69 19: 'reserved for ecdsa',
70 20: 'reserved (formerly elgamal encrypt or sign)',
71 21: 'reserved for diffie-hellman',
85 _symmetric_key_algorithms = {
86 0: 'plaintext or unencrypted data',
93 7: 'aes with 128-bit key',
94 8: 'aes with 192-bit key',
95 9: 'aes with 256-bit key',
110 _cipher_block_size = { # in bits
111 'aes with 128-bit key': 128,
112 'aes with 192-bit key': 128,
113 'aes with 256-bit key': 128,
118 'aes with 128-bit key': _crypto_cipher_aes,
119 'aes with 192-bit key': _crypto_cipher_aes,
120 'aes with 256-bit key': _crypto_cipher_aes,
121 'blowfish': _crypto_cipher_blowfish,
122 'cast5': _crypto_cipher_cast,
123 'tripledes': _crypto_cipher_des3,
126 _compression_algorithms = {
169 _string_to_key_types = {
173 3: 'iterated and salted',
187 _string_to_key_expbias = 6
190 0x00: 'binary document',
191 0x01: 'canonical text document',
193 0x10: 'generic user id and public-key packet',
194 0x11: 'persona user id and public-key packet',
195 0x12: 'casual user id and public-key packet',
196 0x13: 'postitive user id and public-key packet',
197 0x18: 'subkey binding',
198 0x19: 'primary key binding',
200 0x20: 'key revocation',
201 0x28: 'subkey revocation',
202 0x30: 'certification revocation',
204 0x50: 'third-party confirmation',
207 _signature_subpacket_types = {
210 2: 'signature creation time',
211 3: 'signature expiration time',
212 4: 'exportable certification',
213 5: 'trust signature',
214 6: 'regular expression',
217 9: 'key expiration time',
218 10: 'placeholder for backward compatibility',
219 11: 'preferred symmetric algorithms',
220 12: 'revocation key',
229 21: 'preferred hash algorithms',
230 22: 'preferred compression algorithms',
231 23: 'key server preferences',
232 24: 'preferred key server',
233 25: 'primary user id',
236 28: 'signer user id',
237 29: 'reason for revocation',
239 31: 'signature target',
240 32: 'embedded signature',
254 _clean_type_regex = _re.compile('\W+')
256 def _clean_type(self, type=None):
259 return self._clean_type_regex.sub('_', type)
262 def _reverse(dict, value):
263 """Reverse lookups in dictionaries
265 >>> PGPPacket._reverse(PGPPacket._packet_types, 'public-key packet')
268 return [k for k,v in dict.items() if v == value][0]
271 method_name = '_str_{}'.format(self._clean_type())
272 method = getattr(self, method_name, None)
276 return '{}: {}'.format(self['type'], details)
278 def _str_public_key_packet(self):
279 return self._str_generic_key_packet()
281 def _str_public_subkey_packet(self):
282 return self._str_generic_key_packet()
284 def _str_secret_key_packet(self):
285 return self._str_generic_key_packet()
287 def _str_secret_subkey_packet(self):
288 return self._str_generic_key_packet()
290 def _str_generic_key_packet(self):
291 return self['fingerprint'][-8:].upper()
293 def _str_signature_packet(self):
294 lines = [self['signature-type']]
295 if self['hashed-subpackets']:
296 lines.append(' hashed subpackets:')
297 lines.extend(self._str_signature_subpackets(
298 self['hashed-subpackets'], prefix=' '))
299 if self['unhashed-subpackets']:
300 lines.append(' unhashed subpackets:')
301 lines.extend(self._str_signature_subpackets(
302 self['unhashed-subpackets'], prefix=' '))
303 return '\n'.join(lines)
305 def _str_signature_subpackets(self, subpackets, prefix):
307 for subpacket in subpackets:
308 method_name = '_str_{}_signature_subpacket'.format(
309 self._clean_type(type=subpacket['type']))
310 method = getattr(self, method_name, None)
312 lines.append(' {}: {}'.format(
314 method(subpacket=subpacket)))
316 lines.append(' {}'.format(subpacket['type']))
319 def _str_signature_creation_time_signature_subpacket(self, subpacket):
320 return str(subpacket['signature-creation-time'])
322 def _str_issuer_signature_subpacket(self, subpacket):
323 return subpacket['issuer'][-8:].upper()
325 def _str_key_expiration_time_signature_subpacket(self, subpacket):
326 return str(subpacket['key-expiration-time'])
328 def _str_preferred_symmetric_algorithms_signature_subpacket(
331 algo for algo in subpacket['preferred-symmetric-algorithms'])
333 def _str_preferred_hash_algorithms_signature_subpacket(
336 algo for algo in subpacket['preferred-hash-algorithms'])
338 def _str_preferred_compression_algorithms_signature_subpacket(
341 algo for algo in subpacket['preferred-compression-algorithms'])
343 def _str_key_server_preferences_signature_subpacket(self, subpacket):
345 x for x in sorted(subpacket['key-server-preferences']))
347 def _str_primary_user_id_signature_subpacket(self, subpacket):
348 return str(subpacket['primary-user-id'])
350 def _str_key_flags_signature_subpacket(self, subpacket):
351 return ', '.join(x for x in sorted(subpacket['key-flags']))
353 def _str_features_signature_subpacket(self, subpacket):
354 return ', '.join(x for x in sorted(subpacket['features']))
356 def _str_embedded_signature_signature_subpacket(self, subpacket):
357 return subpacket['embedded']['signature-type']
359 def _str_user_id_packet(self):
362 def from_bytes(self, data):
363 offset = self._parse_header(data=data)
364 packet = data[offset:offset + self['length']]
365 if len(packet) < self['length']:
366 raise ValueError('packet too short ({} < {})'.format(
367 len(packet), self['length']))
368 offset += self['length']
369 method_name = '_parse_{}'.format(self._clean_type())
370 method = getattr(self, method_name, None)
372 raise NotImplementedError(
373 'cannot parse packet type {!r}'.format(self['type']))
377 def _parse_header(self, data):
380 always_one = packet_tag & 1 << 7
382 raise ValueError('most significant packet tag bit not set')
383 self['new-format'] = packet_tag & 1 << 6
384 if self['new-format']:
385 type_code = packet_tag & 0b111111
386 raise NotImplementedError('new-format packet length')
388 type_code = packet_tag >> 2 & 0b1111
389 self['length-type'] = packet_tag & 0b11
390 length_bytes, length_type = self._old_format_packet_length_type[
393 raise NotImplementedError(
394 'old-format packet of indeterminate length')
395 length_format = '>{}'.format(length_type)
396 length_data = data[offset: offset + length_bytes]
397 offset += length_bytes
398 self['length'] = _struct.unpack(length_format, length_data)[0]
399 self['type'] = self._packet_types[type_code]
403 def _parse_multiprecision_integer(data):
404 r"""Parse RFC 4880's multiprecision integers
406 >>> PGPPacket._parse_multiprecision_integer(b'\x00\x01\x01')
408 >>> PGPPacket._parse_multiprecision_integer(b'\x00\x09\x01\xff')
411 bits = _struct.unpack('>H', data[:2])[0]
413 length = (bits + 7) // 8
415 for i in range(length):
416 value += data[offset + i] * 1 << (8 * (length - i - 1))
418 return (offset, value)
421 def _decode_string_to_key_count(cls, data):
422 r"""Decode RFC 4880's string-to-key count
424 >>> PGPPacket._decode_string_to_key_count(b'\x97'[0])
427 return (16 + (data & 15)) << ((data >> 4) + cls._string_to_key_expbias)
429 def _parse_string_to_key_specifier(self, data):
430 self['string-to-key-type'] = self._string_to_key_types[data[0]]
432 if self['string-to-key-type'] == 'simple':
433 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
436 elif self['string-to-key-type'] == 'salted':
437 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
440 self['string-to-key-salt'] = data[offset: offset + 8]
442 elif self['string-to-key-type'] == 'iterated and salted':
443 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
446 self['string-to-key-salt'] = data[offset: offset + 8]
448 self['string-to-key-count'] = self._decode_string_to_key_count(
452 raise NotImplementedError(
453 'string-to-key type {}'.format(self['string-to-key-type']))
456 def _parse_public_key_packet(self, data):
457 self._parse_generic_public_key_packet(data=data)
459 def _parse_public_subkey_packet(self, data):
460 self._parse_generic_public_key_packet(data=data)
462 def _parse_generic_public_key_packet(self, data):
463 self['key-version'] = data[0]
465 if self['key-version'] != 4:
466 raise NotImplementedError(
467 'public (sub)key packet version {}'.format(
468 self['key-version']))
470 self['creation-time'], algorithm = _struct.unpack(
471 '>IB', data[offset: offset + length])
473 self['public-key-algorithm'] = self._public_key_algorithms[algorithm]
474 if self['public-key-algorithm'].startswith('rsa '):
475 o, self['public-modulus'] = self._parse_multiprecision_integer(
478 o, self['public-exponent'] = self._parse_multiprecision_integer(
481 elif self['public-key-algorithm'].startswith('dsa '):
482 o, self['prime'] = self._parse_multiprecision_integer(
485 o, self['group-order'] = self._parse_multiprecision_integer(
488 o, self['group-generator'] = self._parse_multiprecision_integer(
491 o, self['public-key'] = self._parse_multiprecision_integer(
494 elif self['public-key-algorithm'].startswith('elgamal '):
495 o, self['prime'] = self._parse_multiprecision_integer(
498 o, self['group-generator'] = self._parse_multiprecision_integer(
501 o, self['public-key'] = self._parse_multiprecision_integer(
505 raise NotImplementedError(
506 'algorithm-specific key fields for {}'.format(
507 self['public-key-algorithm']))
508 fingerprint = _hashlib.sha1()
509 fingerprint.update(b'\x99')
510 fingerprint.update(_struct.pack('>H', len(data)))
511 fingerprint.update(data)
512 self['fingerprint'] = fingerprint.hexdigest()
515 def _parse_secret_key_packet(self, data):
516 self._parse_generic_secret_key_packet(data=data)
518 def _parse_secret_subkey_packet(self, data):
519 self._parse_generic_secret_key_packet(data=data)
521 def _parse_generic_secret_key_packet(self, data):
522 offset = self._parse_generic_public_key_packet(data=data)
523 string_to_key_usage = data[offset]
525 if string_to_key_usage in [255, 254]:
526 self['symmetric-encryption-algorithm'] = (
527 self._symmetric_key_algorithms[data[offset]])
529 offset += self._parse_string_to_key_specifier(data=data[offset:])
531 self['symmetric-encryption-algorithm'] = (
532 self._symmetric_key_algorithms[string_to_key_usage])
533 if string_to_key_usage:
534 block_size_bits = self._cipher_block_size.get(
535 self['symmetric-encryption-algorithm'], None)
536 if block_size_bits % 8:
537 raise NotImplementedError(
538 ('{}-bit block size for {} is not an integer number of bytes'
540 block_size_bits, self['symmetric-encryption-algorithm']))
541 block_size = block_size_bits // 8
543 raise NotImplementedError(
544 'unknown block size for {}'.format(
545 self['symmetric-encryption-algorithm']))
546 self['initial-vector'] = data[offset: offset + block_size]
548 ciphertext = data[offset:]
549 offset += len(ciphertext)
550 decrypted_data = self.decrypt_symmetric_encryption(data=ciphertext)
552 decrypted_data = data[offset:key_end]
553 if string_to_key_usage in [0, 255]:
555 elif string_to_key_usage == 254:
559 secret_key = decrypted_data[:key_end]
561 secret_key_checksum = decrypted_data[key_end:]
563 calculated_checksum = sum(secret_key) % 65536
565 checksum_hash = _hashlib.sha1()
566 checksum_hash.update(secret_key)
567 calculated_checksum = checksum_hash.digest()
568 if secret_key_checksum != calculated_checksum:
570 'corrupt secret key (checksum {} != expected {})'.format(
571 secret_key_checksum, calculated_checksum))
572 self['secret-key'] = secret_key
574 def _parse_signature_subpackets(self, data):
576 while offset < len(data):
577 o, subpacket = self._parse_signature_subpacket(data=data[offset:])
581 def _parse_signature_subpacket(self, data):
587 elif first >= 192 and first < 255:
588 second = data[offset]
590 length = ((first - 192) << 8) + second + 192
592 length = _struct.unpack(
593 '>I', data[offset: offset + 4])[0]
595 subpacket['type'] = self._signature_subpacket_types[data[offset]]
597 subpacket_data = data[offset: offset + length - 1]
598 offset += len(subpacket_data)
599 method_name = '_parse_{}_signature_subpacket'.format(
600 self._clean_type(type=subpacket['type']))
601 method = getattr(self, method_name, None)
603 raise NotImplementedError(
604 'cannot parse signature subpacket type {!r}'.format(
606 method(data=subpacket_data, subpacket=subpacket)
607 return (offset, subpacket)
609 def _parse_signature_packet(self, data):
610 self['signature-version'] = data[0]
612 if self['signature-version'] != 4:
613 raise NotImplementedError(
614 'signature packet version {}'.format(
615 self['signature-version']))
616 self['signature-type'] = self._signature_types[data[offset]]
618 self['public-key-algorithm'] = self._public_key_algorithms[
621 self['hash-algorithm'] = self._hash_algorithms[data[offset]]
623 hashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
625 self['hashed-subpackets'] = list(self._parse_signature_subpackets(
626 data[offset: offset + hashed_count]))
627 offset += hashed_count
628 unhashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
630 self['unhashed-subpackets'] = list(self._parse_signature_subpackets(
631 data=data[offset: offset + unhashed_count]))
632 offset += unhashed_count
633 self['signed-hash-word'] = data[offset: offset + 2]
635 self['signature'] = data[offset:]
637 def _parse_signature_creation_time_signature_subpacket(
638 self, data, subpacket):
639 subpacket['signature-creation-time'] = _struct.unpack('>I', data)[0]
641 def _parse_issuer_signature_subpacket(self, data, subpacket):
642 subpacket['issuer'] = ''.join('{:02x}'.format(byte) for byte in data)
644 def _parse_key_expiration_time_signature_subpacket(
645 self, data, subpacket):
646 subpacket['key-expiration-time'] = _struct.unpack('>I', data)[0]
648 def _parse_preferred_symmetric_algorithms_signature_subpacket(
649 self, data, subpacket):
650 subpacket['preferred-symmetric-algorithms'] = [
651 self._symmetric_key_algorithms[d] for d in data]
653 def _parse_preferred_hash_algorithms_signature_subpacket(
654 self, data, subpacket):
655 subpacket['preferred-hash-algorithms'] = [
656 self._hash_algorithms[d] for d in data]
658 def _parse_preferred_compression_algorithms_signature_subpacket(
659 self, data, subpacket):
660 subpacket['preferred-compression-algorithms'] = [
661 self._compression_algorithms[d] for d in data]
663 def _parse_key_server_preferences_signature_subpacket(
664 self, data, subpacket):
665 subpacket['key-server-preferences'] = set()
667 subpacket['key-server-preferences'].add('no-modify')
669 def _parse_primary_user_id_signature_subpacket(self, data, subpacket):
670 subpacket['primary-user-id'] = bool(data[0])
672 def _parse_key_flags_signature_subpacket(self, data, subpacket):
673 subpacket['key-flags'] = set()
675 subpacket['key-flags'].add('can certify')
677 subpacket['key-flags'].add('can sign')
679 subpacket['key-flags'].add('can encrypt communications')
681 subpacket['key-flags'].add('can encrypt storage')
683 subpacket['key-flags'].add('private split')
685 subpacket['key-flags'].add('can authenticate')
687 subpacket['key-flags'].add('private shared')
689 def _parse_features_signature_subpacket(self, data, subpacket):
690 subpacket['features'] = set()
692 subpacket['features'].add('modification detection')
694 def _parse_embedded_signature_signature_subpacket(self, data, subpacket):
695 subpacket['embedded'] = PGPPacket()
696 subpacket['embedded']._parse_signature_packet(data=data)
698 def _parse_user_id_packet(self, data):
699 self['user'] = str(data, 'utf-8')
702 method_name = '_serialize_{}'.format(self._clean_type())
703 method = getattr(self, method_name, None)
705 raise NotImplementedError(
706 'cannot serialize packet type {!r}'.format(self['type']))
708 self['length'] = len(body)
710 self._serialize_header(),
714 def _serialize_header(self):
717 type_code = self._reverse(self._packet_types, self['type'])
719 always_one * (1 << 7) |
720 new_format * (1 << 6) |
721 type_code * (1 << 2) |
724 length_bytes, length_type = self._old_format_packet_length_type[
726 length_format = '>{}'.format(length_type)
727 length_data = _struct.pack(length_format, self['length'])
734 def _serialize_multiprecision_integer(integer):
735 r"""Serialize RFC 4880's multipricision integers
737 >>> PGPPacket._serialize_multiprecision_integer(1)
739 >>> PGPPacket._serialize_multiprecision_integer(511)
742 bit_length = int(_math.log(integer, 2)) + 1
744 _struct.pack('>H', bit_length),
747 chunks.insert(1, bytes([integer & 0xff]))
748 integer = integer >> 8
749 return b''.join(chunks)
752 def _encode_string_to_key_count(cls, count):
753 r"""Encode RFC 4880's string-to-key count
755 >>> PGPPacket._encode_string_to_key_count(753664)
759 count = count >> cls._string_to_key_expbias
762 coded_count += 1 << 4
763 coded_count += count & 15
764 return bytes([coded_count])
766 def _serialize_string_to_key_specifier(self):
767 string_to_key_type = bytes([
769 self._string_to_key_types, self['string-to-key-type']),
771 chunks = [string_to_key_type]
772 if self['string-to-key-type'] == 'simple':
773 chunks.append(bytes([self._reverse(
774 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
775 elif self['string-to-key-type'] == 'salted':
776 chunks.append(bytes([self._reverse(
777 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
778 chunks.append(self['string-to-key-salt'])
779 elif self['string-to-key-type'] == 'iterated and salted':
780 chunks.append(bytes([self._reverse(
781 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
782 chunks.append(self['string-to-key-salt'])
783 chunks.append(self._encode_string_to_key_count(
784 count=self['string-to-key-count']))
786 raise NotImplementedError(
787 'string-to-key type {}'.format(self['string-to-key-type']))
789 return b''.join(chunks)
791 def _serialize_public_key_packet(self):
792 return self._serialize_generic_public_key_packet()
794 def _serialize_public_subkey_packet(self):
795 return self._serialize_generic_public_key_packet()
797 def _serialize_generic_public_key_packet(self):
798 key_version = bytes([self['key-version']])
799 chunks = [key_version]
800 if self['key-version'] != 4:
801 raise NotImplementedError(
802 'public (sub)key packet version {}'.format(
803 self['key-version']))
804 chunks.append(_struct.pack('>I', self['creation-time']))
805 chunks.append(bytes([self._reverse(
806 self._public_key_algorithms, self['public-key-algorithm'])]))
807 if self['public-key-algorithm'].startswith('rsa '):
808 chunks.append(self._serialize_multiprecision_integer(
809 self['public-modulus']))
810 chunks.append(self._serialize_multiprecision_integer(
811 self['public-exponent']))
812 elif self['public-key-algorithm'].startswith('dsa '):
813 chunks.append(self._serialize_multiprecision_integer(
815 chunks.append(self._serialize_multiprecision_integer(
816 self['group-order']))
817 chunks.append(self._serialize_multiprecision_integer(
818 self['group-generator']))
819 chunks.append(self._serialize_multiprecision_integer(
821 elif self['public-key-algorithm'].startswith('elgamal '):
822 chunks.append(self._serialize_multiprecision_integer(
824 chunks.append(self._serialize_multiprecision_integer(
825 self['group-generator']))
826 chunks.append(self._serialize_multiprecision_integer(
829 raise NotImplementedError(
830 'algorithm-specific key fields for {}'.format(
831 self['public-key-algorithm']))
832 return b''.join(chunks)
834 def decrypt_symmetric_encryption(self, data):
835 """Decrypt OpenPGP's Cipher Feedback mode"""
836 algorithm = self['symmetric-encryption-algorithm']
837 module = self._crypto_module[algorithm]
838 key_size = self._key_size[algorithm]
839 segment_size_bits = self._cipher_block_size[algorithm]
840 if segment_size_bits % 8:
841 raise NotImplementedError(
842 ('{}-bit segment size for {} is not an integer number of bytes'
843 ).format(segment_size_bits, algorithm))
844 segment_size_bytes = segment_size_bits // 8
845 padding = segment_size_bytes - len(data) % segment_size_bytes
847 data += b'\x00' * padding
848 passphrase = _getpass.getpass(
849 'passphrase for {}: '.format(self['fingerprint'][-8:]))
850 passphrase = passphrase.encode('ascii')
853 mode=module.MODE_CFB,
854 IV=self['initial-vector'],
855 segment_size=segment_size_bits)
856 plaintext = cipher.decrypt(data)
858 plaintext = plaintext[:-padding]
862 def packets_from_bytes(data):
864 while offset < len(data):
866 offset += packet.from_bytes(data=data[offset:])
870 class PGPKey (object):
871 """An OpenPGP key with public and private parts.
875 OpenPGP users may transfer public keys. The essential elements
876 of a transferable public key are as follows:
878 - One Public-Key packet
879 - Zero or more revocation signatures
880 - One or more User ID packets
881 - After each User ID packet, zero or more Signature packets
883 - Zero or more User Attribute packets
884 - After each User Attribute packet, zero or more Signature
885 packets (certifications)
886 - Zero or more Subkey packets
887 - After each Subkey packet, one Signature packet, plus
888 optionally a revocation
890 Secret keys have a similar packet stream [2]:
892 OpenPGP users may transfer secret keys. The format of a
893 transferable secret key is the same as a transferable public key
894 except that secret-key and secret-subkey packets are used
895 instead of the public key and public-subkey packets.
896 Implementations SHOULD include self-signatures on any user IDs
897 and subkeys, as this allows for a complete public key to be
898 automatically extracted from the transferable secret key.
899 Implementations MAY choose to omit the self-signatures,
900 especially if a transferable public key accompanies the
901 transferable secret key.
903 [1]: http://tools.ietf.org/search/rfc4880#section-11.1
904 [2]: http://tools.ietf.org/search/rfc4880#section-11.2
906 def __init__(self, fingerprint):
907 self.fingerprint = fingerprint
908 self.public_packets = None
909 self.secret_packets = None
912 lines = ['key: {}'.format(self.fingerprint)]
913 if self.public_packets:
914 lines.append(' public:')
915 for packet in self.public_packets:
916 lines.extend(self._str_packet(packet=packet, prefix=' '))
917 if self.secret_packets:
918 lines.append(' secret:')
919 for packet in self.secret_packets:
920 lines.extend(self._str_packet(packet=packet, prefix=' '))
921 return '\n'.join(lines)
923 def _str_packet(self, packet, prefix):
924 lines = str(packet).split('\n')
925 return [prefix + line for line in lines]
927 def import_from_gpg(self):
928 key_export = _get_stdout(
929 ['gpg', '--export', self.fingerprint])
930 self.public_packets = list(
931 packets_from_bytes(data=key_export))
932 if self.public_packets[0]['type'] != 'public-key packet':
934 '{} does not start with a public-key packet'.format(
936 key_secret_export = _get_stdout(
937 ['gpg', '--export-secret-keys', self.fingerprint])
938 self.secret_packets = list(
939 packets_from_bytes(data=key_secret_export))
940 if self.secret_packets[0]['type'] != 'secret-key packet':
942 '{} does not start with a secret-key packet'.format(
945 def export_to_gpg(self):
946 raise NotImplemetedError('export to gpg')
948 def import_from_key(self, key):
949 """Migrate the (sub)keys into this key"""
953 def migrate(old_key, new_key):
954 """Add the old key and sub-keys to the new key
956 For example, to upgrade your master key, while preserving old
957 signatures you'd made. You will lose signature *on* your old key
958 though, since sub-keys can't be signed (I don't think).
960 old_key = PGPKey(fingerprint=old_key)
961 old_key.import_from_gpg()
962 new_key = PGPKey(fingerprint=new_key)
963 new_key.import_from_gpg()
964 new_key.import_from_key(key=old_key)
970 if __name__ == '__main__':
973 old_key, new_key = _sys.argv[1:3]
974 migrate(old_key=old_key, new_key=new_key)