Add PGPPacket._parse_multiprecision_integer
authorW. Trevor King <wking@tremily.us>
Fri, 20 Dec 2013 17:16:45 +0000 (09:16 -0800)
committerW. Trevor King <wking@tremily.us>
Fri, 20 Dec 2013 19:46:30 +0000 (11:46 -0800)
From RFC 4880 [1]:

  An MPI consists of two pieces: a two-octet scalar that is the length
  of the MPI in bits followed by a string of octets that contain the
  actual integer.

  These octets form a big-endian number; a big-endian number can be
  made into an MPI by prefixing it with the appropriate length.

  Examples:

  (all numbers are in hexadecimal)

  The string of octets [00 01 01] forms an MPI with the value 1.  The
  string [00 09 01 FF] forms an MPI with the value of 511.

  Additional rules:

  The size of an MPI is ((MPI.length + 7) / 8) + 2 octets.

  The length field of an MPI describes the length starting from its
  most significant non-zero bit.  Thus, the MPI [00 02 01] is not
  formed correctly.  It should be [00 01 01].

  Unused bits of an MPI MUST be zero.

  Also note that when an MPI is encrypted, the length refers to the
  plaintext MPI.  It may be ill-formed in its ciphertext.

[1]: http://tools.ietf.org/search/rfc4880#section-3.2

gpg-migrate.py

index d2049c256815f8b977317bcc6c47effe517a5646..3a893522fbc69b7999a6c858c2b5f1eba95d2c5d 100755 (executable)
@@ -187,6 +187,24 @@ class PGPPacket (dict):
         self['type'] = self._packet_types[type_code]
         return offset
 
+    @staticmethod
+    def _parse_multiprecision_integer(data):
+        r"""Parse RFC 4880's multiprecision integers
+
+        >>> PGPPacket._parse_multiprecision_integer(b'\x00\x01\x01')
+        (3, 1)
+        >>> PGPPacket._parse_multiprecision_integer(b'\x00\x09\x01\xff')
+        (4, 511)
+        """
+        bits = _struct.unpack('>H', data[:2])[0]
+        offset = 2
+        length = (bits + 7) // 8
+        value = 0
+        for i in range(length):
+            value += data[offset + i] * 1 << (8 * (length - i - 1))
+        offset += length
+        return (offset, value)
+
     def _parse_public_key_packet(self, data):
         self._parse_generic_public_key_packet(data=data)