From: W. Trevor King Date: Fri, 20 Dec 2013 18:25:59 +0000 (-0800) Subject: Add secret key parsing to PGPPacket X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=ac2696b9c7688e1c7efef7ad13455d732d596fbb;p=gpg-migrate.git Add secret key parsing to PGPPacket Use the same parser for public-key and public-subkey packets. From RFC 4880 [1]: A Secret-Subkey packet (tag 7) is the subkey analog of the Secret Key packet and has exactly the same format. The generic (sub)key parsing is specified in section 5.5.3 [2]: The Secret-Key and Secret-Subkey packets contain all the data of the Public-Key and Public-Subkey packets, with additional algorithm- specific secret-key data appended, usually in encrypted form. The packet contains: - A Public-Key or Public-Subkey packet, as described above. - One octet indicating string-to-key usage conventions. Zero indicates that the secret-key data is not encrypted. 255 or 254 indicates that a string-to-key specifier is being given. Any other value is a symmetric-key encryption algorithm identifier. - [Optional] If string-to-key usage octet was 255 or 254, a one- octet symmetric encryption algorithm. - [Optional] If string-to-key usage octet was 255 or 254, a string-to-key specifier. The length of the string-to-key specifier is implied by its type, as described above. - [Optional] If secret data is encrypted (string-to-key usage octet not zero), an Initial Vector (IV) of the same length as the cipher's block size. - Plain or encrypted multiprecision integers comprising the secret key data. These algorithm-specific fields are as described below. - If the string-to-key usage octet is zero or 255, then a two-octet checksum of the plaintext of the algorithm-specific portion (sum of all octets, mod 65536). If the string-to-key usage octet was 254, then a 20-octet SHA-1 hash of the plaintext of the algorithm-specific portion. This checksum or hash is encrypted together with the algorithm-specific fields (if string-to-key usage octet is not zero). Note that for all other values, a two-octet checksum is required. RFC 4880 claims to list block sizes (needed for the IV length) [3]: OpenPGP specifies a number of symmetric-key algorithms. This specification creates a registry of symmetric-key algorithm identifiers. The registry includes the algorithm name, its key sizes and block size, and a reference to the defining specification. The initial values for this registry can be found in Section 9. But in section 9.2 [4], they just list key size. It looks like the block size is usually equal to the key size, but not always. From section one of the AES spec [5]: This standard specifies the Rijndael algorithm (...), a symmetric block cipher that can process data blocks of 128 bits, using cipher keys with lengths of 128, 192, and 256 bits. So it looks like the block size for each cipher needs research beyond RFC 4880. [1]: http://tools.ietf.org/search/rfc4880#section-5.5.1.4 [2]: http://tools.ietf.org/search/rfc4880#section-5.5.3 [3]: http://tools.ietf.org/search/rfc4880#section-10.3.2 [4]: http://tools.ietf.org/search/rfc4880#section-9.2 [5]: http://csrc.nist.gov/publications/fips/fips197/fips-197.{ps,pdf} --- diff --git a/gpg-migrate.py b/gpg-migrate.py index 7fea58d..27054a0 100755 --- a/gpg-migrate.py +++ b/gpg-migrate.py @@ -99,6 +99,9 @@ class PGPPacket (dict): 110: 'private', } + _cipher_block_size = { # in bits + } + _compression_algorithms = { 0: 'uncompressed', 1: 'zip', @@ -303,6 +306,47 @@ class PGPPacket (dict): self['public-key-algorithm'])) return offset + def _parse_secret_key_packet(self, data): + self._parse_generic_secret_key_packet(data=data) + + def _parse_secret_subkey_packet(self, data): + self._parse_generic_secret_key_packet(data=data) + + def _parse_generic_secret_key_packet(self, data): + offset = self._parse_generic_public_key_packet(data=data) + string_to_key_usage = data[offset] + offset += 1 + if string_to_key_usage in [255, 254]: + self['symmetric-encryption-algorithm'] = ( + self._symmetric_key_algorithms[data[offset]]) + offset += 1 + offset += self._parse_string_to_key_specifier(data=data[offset:]) + else: + self['symmetric-encryption-algorithm'] = ( + self._symmetric_key_algorithms[string_to_key_usage]) + if string_to_key_usage: + block_size_bits = self._cipher_block_size.get( + self['symmetric-encryption-algorithm'], None) + if block_size_bits % 8: + raise NotImplementedError( + ('{}-bit block size for {} is not an integer number of bytes' + ).format( + block_size_bits, self['symmetric-encryption-algorithm'])) + block_size = block_size_bits // 8 + if not block_size: + raise NotImplementedError( + 'unknown block size for {}'.format( + self['symmetric-encryption-algorithm'])) + self['initial-vector'] = data[offset: offset + block_size] + offset += block_size + if string_to_key_usage in [0, 255]: + key_end = -2 + else: + key_end = 0 + self['secret-key'] = data[offset:key_end] + if key_end: + self['secret-key-checksum'] = data[key_end:] + def to_bytes(self): pass