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 _key_size = { # in bits
127 'aes with 128-bit key': 128,
128 'aes with 192-bit key': 192,
129 'aes with 256-bit key': 256,
132 _compression_algorithms = {
175 _string_to_key_types = {
179 3: 'iterated and salted',
193 _string_to_key_expbias = 6
196 0x00: 'binary document',
197 0x01: 'canonical text document',
199 0x10: 'generic user id and public-key packet',
200 0x11: 'persona user id and public-key packet',
201 0x12: 'casual user id and public-key packet',
202 0x13: 'postitive user id and public-key packet',
203 0x18: 'subkey binding',
204 0x19: 'primary key binding',
206 0x20: 'key revocation',
207 0x28: 'subkey revocation',
208 0x30: 'certification revocation',
210 0x50: 'third-party confirmation',
213 _signature_subpacket_types = {
216 2: 'signature creation time',
217 3: 'signature expiration time',
218 4: 'exportable certification',
219 5: 'trust signature',
220 6: 'regular expression',
223 9: 'key expiration time',
224 10: 'placeholder for backward compatibility',
225 11: 'preferred symmetric algorithms',
226 12: 'revocation key',
235 21: 'preferred hash algorithms',
236 22: 'preferred compression algorithms',
237 23: 'key server preferences',
238 24: 'preferred key server',
239 25: 'primary user id',
242 28: 'signer user id',
243 29: 'reason for revocation',
245 31: 'signature target',
246 32: 'embedded signature',
260 _clean_type_regex = _re.compile('\W+')
262 def _clean_type(self, type=None):
265 return self._clean_type_regex.sub('_', type)
268 def _reverse(dict, value):
269 """Reverse lookups in dictionaries
271 >>> PGPPacket._reverse(PGPPacket._packet_types, 'public-key packet')
274 return [k for k,v in dict.items() if v == value][0]
277 method_name = '_str_{}'.format(self._clean_type())
278 method = getattr(self, method_name, None)
282 return '{}: {}'.format(self['type'], details)
284 def _str_public_key_packet(self):
285 return self._str_generic_key_packet()
287 def _str_public_subkey_packet(self):
288 return self._str_generic_key_packet()
290 def _str_secret_key_packet(self):
291 return self._str_generic_key_packet()
293 def _str_secret_subkey_packet(self):
294 return self._str_generic_key_packet()
296 def _str_generic_key_packet(self):
297 return self['fingerprint'][-8:].upper()
299 def _str_signature_packet(self):
300 lines = [self['signature-type']]
301 if self['hashed-subpackets']:
302 lines.append(' hashed subpackets:')
303 lines.extend(self._str_signature_subpackets(
304 self['hashed-subpackets'], prefix=' '))
305 if self['unhashed-subpackets']:
306 lines.append(' unhashed subpackets:')
307 lines.extend(self._str_signature_subpackets(
308 self['unhashed-subpackets'], prefix=' '))
309 return '\n'.join(lines)
311 def _str_signature_subpackets(self, subpackets, prefix):
313 for subpacket in subpackets:
314 method_name = '_str_{}_signature_subpacket'.format(
315 self._clean_type(type=subpacket['type']))
316 method = getattr(self, method_name, None)
318 lines.append(' {}: {}'.format(
320 method(subpacket=subpacket)))
322 lines.append(' {}'.format(subpacket['type']))
325 def _str_signature_creation_time_signature_subpacket(self, subpacket):
326 return str(subpacket['signature-creation-time'])
328 def _str_issuer_signature_subpacket(self, subpacket):
329 return subpacket['issuer'][-8:].upper()
331 def _str_key_expiration_time_signature_subpacket(self, subpacket):
332 return str(subpacket['key-expiration-time'])
334 def _str_preferred_symmetric_algorithms_signature_subpacket(
337 algo for algo in subpacket['preferred-symmetric-algorithms'])
339 def _str_preferred_hash_algorithms_signature_subpacket(
342 algo for algo in subpacket['preferred-hash-algorithms'])
344 def _str_preferred_compression_algorithms_signature_subpacket(
347 algo for algo in subpacket['preferred-compression-algorithms'])
349 def _str_key_server_preferences_signature_subpacket(self, subpacket):
351 x for x in sorted(subpacket['key-server-preferences']))
353 def _str_primary_user_id_signature_subpacket(self, subpacket):
354 return str(subpacket['primary-user-id'])
356 def _str_key_flags_signature_subpacket(self, subpacket):
357 return ', '.join(x for x in sorted(subpacket['key-flags']))
359 def _str_features_signature_subpacket(self, subpacket):
360 return ', '.join(x for x in sorted(subpacket['features']))
362 def _str_embedded_signature_signature_subpacket(self, subpacket):
363 return subpacket['embedded']['signature-type']
365 def _str_user_id_packet(self):
368 def from_bytes(self, data):
369 offset = self._parse_header(data=data)
370 packet = data[offset:offset + self['length']]
371 if len(packet) < self['length']:
372 raise ValueError('packet too short ({} < {})'.format(
373 len(packet), self['length']))
374 offset += self['length']
375 method_name = '_parse_{}'.format(self._clean_type())
376 method = getattr(self, method_name, None)
378 raise NotImplementedError(
379 'cannot parse packet type {!r}'.format(self['type']))
383 def _parse_header(self, data):
386 always_one = packet_tag & 1 << 7
388 raise ValueError('most significant packet tag bit not set')
389 self['new-format'] = packet_tag & 1 << 6
390 if self['new-format']:
391 type_code = packet_tag & 0b111111
392 raise NotImplementedError('new-format packet length')
394 type_code = packet_tag >> 2 & 0b1111
395 self['length-type'] = packet_tag & 0b11
396 length_bytes, length_type = self._old_format_packet_length_type[
399 raise NotImplementedError(
400 'old-format packet of indeterminate length')
401 length_format = '>{}'.format(length_type)
402 length_data = data[offset: offset + length_bytes]
403 offset += length_bytes
404 self['length'] = _struct.unpack(length_format, length_data)[0]
405 self['type'] = self._packet_types[type_code]
409 def _parse_multiprecision_integer(data):
410 r"""Parse RFC 4880's multiprecision integers
412 >>> PGPPacket._parse_multiprecision_integer(b'\x00\x01\x01')
414 >>> PGPPacket._parse_multiprecision_integer(b'\x00\x09\x01\xff')
417 bits = _struct.unpack('>H', data[:2])[0]
419 length = (bits + 7) // 8
421 for i in range(length):
422 value += data[offset + i] * 1 << (8 * (length - i - 1))
424 return (offset, value)
427 def _decode_string_to_key_count(cls, data):
428 r"""Decode RFC 4880's string-to-key count
430 >>> PGPPacket._decode_string_to_key_count(b'\x97'[0])
433 return (16 + (data & 15)) << ((data >> 4) + cls._string_to_key_expbias)
435 def _parse_string_to_key_specifier(self, data):
436 self['string-to-key-type'] = self._string_to_key_types[data[0]]
438 if self['string-to-key-type'] == 'simple':
439 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
442 elif self['string-to-key-type'] == 'salted':
443 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
446 self['string-to-key-salt'] = data[offset: offset + 8]
448 elif self['string-to-key-type'] == 'iterated and salted':
449 self['string-to-key-hash-algorithm'] = self._hash_algorithms[
452 self['string-to-key-salt'] = data[offset: offset + 8]
454 self['string-to-key-count'] = self._decode_string_to_key_count(
458 raise NotImplementedError(
459 'string-to-key type {}'.format(self['string-to-key-type']))
462 def _parse_public_key_packet(self, data):
463 self._parse_generic_public_key_packet(data=data)
465 def _parse_public_subkey_packet(self, data):
466 self._parse_generic_public_key_packet(data=data)
468 def _parse_generic_public_key_packet(self, data):
469 self['key-version'] = data[0]
471 if self['key-version'] != 4:
472 raise NotImplementedError(
473 'public (sub)key packet version {}'.format(
474 self['key-version']))
476 self['creation-time'], algorithm = _struct.unpack(
477 '>IB', data[offset: offset + length])
479 self['public-key-algorithm'] = self._public_key_algorithms[algorithm]
480 if self['public-key-algorithm'].startswith('rsa '):
481 o, self['public-modulus'] = self._parse_multiprecision_integer(
484 o, self['public-exponent'] = self._parse_multiprecision_integer(
487 elif self['public-key-algorithm'].startswith('dsa '):
488 o, self['prime'] = self._parse_multiprecision_integer(
491 o, self['group-order'] = self._parse_multiprecision_integer(
494 o, self['group-generator'] = self._parse_multiprecision_integer(
497 o, self['public-key'] = self._parse_multiprecision_integer(
500 elif self['public-key-algorithm'].startswith('elgamal '):
501 o, self['prime'] = self._parse_multiprecision_integer(
504 o, self['group-generator'] = self._parse_multiprecision_integer(
507 o, self['public-key'] = self._parse_multiprecision_integer(
511 raise NotImplementedError(
512 'algorithm-specific key fields for {}'.format(
513 self['public-key-algorithm']))
514 fingerprint = _hashlib.sha1()
515 fingerprint.update(b'\x99')
516 fingerprint.update(_struct.pack('>H', len(data)))
517 fingerprint.update(data)
518 self['fingerprint'] = fingerprint.hexdigest()
521 def _parse_secret_key_packet(self, data):
522 self._parse_generic_secret_key_packet(data=data)
524 def _parse_secret_subkey_packet(self, data):
525 self._parse_generic_secret_key_packet(data=data)
527 def _parse_generic_secret_key_packet(self, data):
528 offset = self._parse_generic_public_key_packet(data=data)
529 string_to_key_usage = data[offset]
531 if string_to_key_usage in [255, 254]:
532 self['symmetric-encryption-algorithm'] = (
533 self._symmetric_key_algorithms[data[offset]])
535 offset += self._parse_string_to_key_specifier(data=data[offset:])
537 self['symmetric-encryption-algorithm'] = (
538 self._symmetric_key_algorithms[string_to_key_usage])
539 if string_to_key_usage:
540 block_size_bits = self._cipher_block_size.get(
541 self['symmetric-encryption-algorithm'], None)
542 if block_size_bits % 8:
543 raise NotImplementedError(
544 ('{}-bit block size for {} is not an integer number of bytes'
546 block_size_bits, self['symmetric-encryption-algorithm']))
547 block_size = block_size_bits // 8
549 raise NotImplementedError(
550 'unknown block size for {}'.format(
551 self['symmetric-encryption-algorithm']))
552 self['initial-vector'] = data[offset: offset + block_size]
554 ciphertext = data[offset:]
555 offset += len(ciphertext)
556 decrypted_data = self.decrypt_symmetric_encryption(data=ciphertext)
558 decrypted_data = data[offset:key_end]
559 if string_to_key_usage in [0, 255]:
561 elif string_to_key_usage == 254:
565 secret_key = decrypted_data[:key_end]
567 secret_key_checksum = decrypted_data[key_end:]
569 calculated_checksum = sum(secret_key) % 65536
571 checksum_hash = _hashlib.sha1()
572 checksum_hash.update(secret_key)
573 calculated_checksum = checksum_hash.digest()
574 if secret_key_checksum != calculated_checksum:
576 'corrupt secret key (checksum {} != expected {})'.format(
577 secret_key_checksum, calculated_checksum))
578 self['secret-key'] = secret_key
580 def _parse_signature_subpackets(self, data):
582 while offset < len(data):
583 o, subpacket = self._parse_signature_subpacket(data=data[offset:])
587 def _parse_signature_subpacket(self, data):
593 elif first >= 192 and first < 255:
594 second = data[offset]
596 length = ((first - 192) << 8) + second + 192
598 length = _struct.unpack(
599 '>I', data[offset: offset + 4])[0]
601 subpacket['type'] = self._signature_subpacket_types[data[offset]]
603 subpacket_data = data[offset: offset + length - 1]
604 offset += len(subpacket_data)
605 method_name = '_parse_{}_signature_subpacket'.format(
606 self._clean_type(type=subpacket['type']))
607 method = getattr(self, method_name, None)
609 raise NotImplementedError(
610 'cannot parse signature subpacket type {!r}'.format(
612 method(data=subpacket_data, subpacket=subpacket)
613 return (offset, subpacket)
615 def _parse_signature_packet(self, data):
616 self['signature-version'] = data[0]
618 if self['signature-version'] != 4:
619 raise NotImplementedError(
620 'signature packet version {}'.format(
621 self['signature-version']))
622 self['signature-type'] = self._signature_types[data[offset]]
624 self['public-key-algorithm'] = self._public_key_algorithms[
627 self['hash-algorithm'] = self._hash_algorithms[data[offset]]
629 hashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
631 self['hashed-subpackets'] = list(self._parse_signature_subpackets(
632 data[offset: offset + hashed_count]))
633 offset += hashed_count
634 unhashed_count = _struct.unpack('>H', data[offset: offset + 2])[0]
636 self['unhashed-subpackets'] = list(self._parse_signature_subpackets(
637 data=data[offset: offset + unhashed_count]))
638 offset += unhashed_count
639 self['signed-hash-word'] = data[offset: offset + 2]
641 self['signature'] = data[offset:]
643 def _parse_signature_creation_time_signature_subpacket(
644 self, data, subpacket):
645 subpacket['signature-creation-time'] = _struct.unpack('>I', data)[0]
647 def _parse_issuer_signature_subpacket(self, data, subpacket):
648 subpacket['issuer'] = ''.join('{:02x}'.format(byte) for byte in data)
650 def _parse_key_expiration_time_signature_subpacket(
651 self, data, subpacket):
652 subpacket['key-expiration-time'] = _struct.unpack('>I', data)[0]
654 def _parse_preferred_symmetric_algorithms_signature_subpacket(
655 self, data, subpacket):
656 subpacket['preferred-symmetric-algorithms'] = [
657 self._symmetric_key_algorithms[d] for d in data]
659 def _parse_preferred_hash_algorithms_signature_subpacket(
660 self, data, subpacket):
661 subpacket['preferred-hash-algorithms'] = [
662 self._hash_algorithms[d] for d in data]
664 def _parse_preferred_compression_algorithms_signature_subpacket(
665 self, data, subpacket):
666 subpacket['preferred-compression-algorithms'] = [
667 self._compression_algorithms[d] for d in data]
669 def _parse_key_server_preferences_signature_subpacket(
670 self, data, subpacket):
671 subpacket['key-server-preferences'] = set()
673 subpacket['key-server-preferences'].add('no-modify')
675 def _parse_primary_user_id_signature_subpacket(self, data, subpacket):
676 subpacket['primary-user-id'] = bool(data[0])
678 def _parse_key_flags_signature_subpacket(self, data, subpacket):
679 subpacket['key-flags'] = set()
681 subpacket['key-flags'].add('can certify')
683 subpacket['key-flags'].add('can sign')
685 subpacket['key-flags'].add('can encrypt communications')
687 subpacket['key-flags'].add('can encrypt storage')
689 subpacket['key-flags'].add('private split')
691 subpacket['key-flags'].add('can authenticate')
693 subpacket['key-flags'].add('private shared')
695 def _parse_features_signature_subpacket(self, data, subpacket):
696 subpacket['features'] = set()
698 subpacket['features'].add('modification detection')
700 def _parse_embedded_signature_signature_subpacket(self, data, subpacket):
701 subpacket['embedded'] = PGPPacket()
702 subpacket['embedded']._parse_signature_packet(data=data)
704 def _parse_user_id_packet(self, data):
705 self['user'] = str(data, 'utf-8')
708 method_name = '_serialize_{}'.format(self._clean_type())
709 method = getattr(self, method_name, None)
711 raise NotImplementedError(
712 'cannot serialize packet type {!r}'.format(self['type']))
714 self['length'] = len(body)
716 self._serialize_header(),
720 def _serialize_header(self):
723 type_code = self._reverse(self._packet_types, self['type'])
725 always_one * (1 << 7) |
726 new_format * (1 << 6) |
727 type_code * (1 << 2) |
730 length_bytes, length_type = self._old_format_packet_length_type[
732 length_format = '>{}'.format(length_type)
733 length_data = _struct.pack(length_format, self['length'])
740 def _serialize_multiprecision_integer(integer):
741 r"""Serialize RFC 4880's multipricision integers
743 >>> PGPPacket._serialize_multiprecision_integer(1)
745 >>> PGPPacket._serialize_multiprecision_integer(511)
748 bit_length = int(_math.log(integer, 2)) + 1
750 _struct.pack('>H', bit_length),
753 chunks.insert(1, bytes([integer & 0xff]))
754 integer = integer >> 8
755 return b''.join(chunks)
758 def _encode_string_to_key_count(cls, count):
759 r"""Encode RFC 4880's string-to-key count
761 >>> PGPPacket._encode_string_to_key_count(753664)
765 count = count >> cls._string_to_key_expbias
768 coded_count += 1 << 4
769 coded_count += count & 15
770 return bytes([coded_count])
772 def _serialize_string_to_key_specifier(self):
773 string_to_key_type = bytes([
775 self._string_to_key_types, self['string-to-key-type']),
777 chunks = [string_to_key_type]
778 if self['string-to-key-type'] == 'simple':
779 chunks.append(bytes([self._reverse(
780 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
781 elif self['string-to-key-type'] == 'salted':
782 chunks.append(bytes([self._reverse(
783 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
784 chunks.append(self['string-to-key-salt'])
785 elif self['string-to-key-type'] == 'iterated and salted':
786 chunks.append(bytes([self._reverse(
787 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
788 chunks.append(self['string-to-key-salt'])
789 chunks.append(self._encode_string_to_key_count(
790 count=self['string-to-key-count']))
792 raise NotImplementedError(
793 'string-to-key type {}'.format(self['string-to-key-type']))
795 return b''.join(chunks)
797 def _serialize_public_key_packet(self):
798 return self._serialize_generic_public_key_packet()
800 def _serialize_public_subkey_packet(self):
801 return self._serialize_generic_public_key_packet()
803 def _serialize_generic_public_key_packet(self):
804 key_version = bytes([self['key-version']])
805 chunks = [key_version]
806 if self['key-version'] != 4:
807 raise NotImplementedError(
808 'public (sub)key packet version {}'.format(
809 self['key-version']))
810 chunks.append(_struct.pack('>I', self['creation-time']))
811 chunks.append(bytes([self._reverse(
812 self._public_key_algorithms, self['public-key-algorithm'])]))
813 if self['public-key-algorithm'].startswith('rsa '):
814 chunks.append(self._serialize_multiprecision_integer(
815 self['public-modulus']))
816 chunks.append(self._serialize_multiprecision_integer(
817 self['public-exponent']))
818 elif self['public-key-algorithm'].startswith('dsa '):
819 chunks.append(self._serialize_multiprecision_integer(
821 chunks.append(self._serialize_multiprecision_integer(
822 self['group-order']))
823 chunks.append(self._serialize_multiprecision_integer(
824 self['group-generator']))
825 chunks.append(self._serialize_multiprecision_integer(
827 elif self['public-key-algorithm'].startswith('elgamal '):
828 chunks.append(self._serialize_multiprecision_integer(
830 chunks.append(self._serialize_multiprecision_integer(
831 self['group-generator']))
832 chunks.append(self._serialize_multiprecision_integer(
835 raise NotImplementedError(
836 'algorithm-specific key fields for {}'.format(
837 self['public-key-algorithm']))
838 return b''.join(chunks)
840 def decrypt_symmetric_encryption(self, data):
841 """Decrypt OpenPGP's Cipher Feedback mode"""
842 algorithm = self['symmetric-encryption-algorithm']
843 module = self._crypto_module[algorithm]
844 key_size = self._key_size[algorithm]
845 segment_size_bits = self._cipher_block_size[algorithm]
846 if segment_size_bits % 8:
847 raise NotImplementedError(
848 ('{}-bit segment size for {} is not an integer number of bytes'
849 ).format(segment_size_bits, algorithm))
850 segment_size_bytes = segment_size_bits // 8
851 padding = segment_size_bytes - len(data) % segment_size_bytes
853 data += b'\x00' * padding
854 passphrase = _getpass.getpass(
855 'passphrase for {}: '.format(self['fingerprint'][-8:]))
856 passphrase = passphrase.encode('ascii')
859 mode=module.MODE_CFB,
860 IV=self['initial-vector'],
861 segment_size=segment_size_bits)
862 plaintext = cipher.decrypt(data)
864 plaintext = plaintext[:-padding]
868 def packets_from_bytes(data):
870 while offset < len(data):
872 offset += packet.from_bytes(data=data[offset:])
876 class PGPKey (object):
877 """An OpenPGP key with public and private parts.
881 OpenPGP users may transfer public keys. The essential elements
882 of a transferable public key are as follows:
884 - One Public-Key packet
885 - Zero or more revocation signatures
886 - One or more User ID packets
887 - After each User ID packet, zero or more Signature packets
889 - Zero or more User Attribute packets
890 - After each User Attribute packet, zero or more Signature
891 packets (certifications)
892 - Zero or more Subkey packets
893 - After each Subkey packet, one Signature packet, plus
894 optionally a revocation
896 Secret keys have a similar packet stream [2]:
898 OpenPGP users may transfer secret keys. The format of a
899 transferable secret key is the same as a transferable public key
900 except that secret-key and secret-subkey packets are used
901 instead of the public key and public-subkey packets.
902 Implementations SHOULD include self-signatures on any user IDs
903 and subkeys, as this allows for a complete public key to be
904 automatically extracted from the transferable secret key.
905 Implementations MAY choose to omit the self-signatures,
906 especially if a transferable public key accompanies the
907 transferable secret key.
909 [1]: http://tools.ietf.org/search/rfc4880#section-11.1
910 [2]: http://tools.ietf.org/search/rfc4880#section-11.2
912 def __init__(self, fingerprint):
913 self.fingerprint = fingerprint
914 self.public_packets = None
915 self.secret_packets = None
918 lines = ['key: {}'.format(self.fingerprint)]
919 if self.public_packets:
920 lines.append(' public:')
921 for packet in self.public_packets:
922 lines.extend(self._str_packet(packet=packet, prefix=' '))
923 if self.secret_packets:
924 lines.append(' secret:')
925 for packet in self.secret_packets:
926 lines.extend(self._str_packet(packet=packet, prefix=' '))
927 return '\n'.join(lines)
929 def _str_packet(self, packet, prefix):
930 lines = str(packet).split('\n')
931 return [prefix + line for line in lines]
933 def import_from_gpg(self):
934 key_export = _get_stdout(
935 ['gpg', '--export', self.fingerprint])
936 self.public_packets = list(
937 packets_from_bytes(data=key_export))
938 if self.public_packets[0]['type'] != 'public-key packet':
940 '{} does not start with a public-key packet'.format(
942 key_secret_export = _get_stdout(
943 ['gpg', '--export-secret-keys', self.fingerprint])
944 self.secret_packets = list(
945 packets_from_bytes(data=key_secret_export))
946 if self.secret_packets[0]['type'] != 'secret-key packet':
948 '{} does not start with a secret-key packet'.format(
951 def export_to_gpg(self):
952 raise NotImplemetedError('export to gpg')
954 def import_from_key(self, key):
955 """Migrate the (sub)keys into this key"""
959 def migrate(old_key, new_key):
960 """Add the old key and sub-keys to the new key
962 For example, to upgrade your master key, while preserving old
963 signatures you'd made. You will lose signature *on* your old key
964 though, since sub-keys can't be signed (I don't think).
966 old_key = PGPKey(fingerprint=old_key)
967 old_key.import_from_gpg()
968 new_key = PGPKey(fingerprint=new_key)
969 new_key.import_from_gpg()
970 new_key.import_from_key(key=old_key)
976 if __name__ == '__main__':
979 old_key, new_key = _sys.argv[1:3]
980 migrate(old_key=old_key, new_key=new_key)