Decode the string-to-key count for iterated and salted S2Ks
authorW. Trevor King <wking@tremily.us>
Sat, 21 Dec 2013 17:45:23 +0000 (09:45 -0800)
committerW. Trevor King <wking@tremily.us>
Mon, 23 Dec 2013 21:41:05 +0000 (13:41 -0800)
We need this for decryption.  From RFC 4880 [1]:

  The count is coded into a one-octet number using the following
  formula:

      #define EXPBIAS 6
          count = ((Int32)16 + (c & 15)) << ((c >> 4) + EXPBIAS);

  The above formula is in C, where "Int32" is a type for a 32-bit
  integer, and the variable "c" is the coded count, Octet 10.

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

gpg-migrate.py

index 9f775e1d1241f249a60460c2a4cc86129ad7523a..f8c1fd488934357da431f9f17940c46d0486ed6f 100755 (executable)
@@ -184,6 +184,8 @@ class PGPPacket (dict):
         110: 'private',
         }
 
+    _string_to_key_expbias = 6
+
     _signature_types = {
         0x00: 'binary document',
         0x01: 'canonical text document',
@@ -415,6 +417,15 @@ class PGPPacket (dict):
         offset += length
         return (offset, value)
 
+    @classmethod
+    def _decode_string_to_key_count(cls, data):
+        r"""Decode RFC 4880's string-to-key count
+
+        >>> PGPPacket._decode_string_to_key_count(b'\x97'[0])
+        753664
+        """
+        return (16 + (data & 15)) << ((data >> 4) + cls._string_to_key_expbias)
+
     def _parse_string_to_key_specifier(self, data):
         self['string-to-key-type'] = self._string_to_key_types[data[0]]
         offset = 1
@@ -434,7 +445,8 @@ class PGPPacket (dict):
             offset += 1
             self['string-to-key-salt'] = data[offset: offset + 8]
             offset += 8
-            self['string-to-key-coded-count'] = data[offset]
+            self['string-to-key-count'] = self._decode_string_to_key_count(
+                data=data[offset])
             offset += 1
         else:
             raise NotImplementedError(
@@ -736,6 +748,21 @@ class PGPPacket (dict):
             integer = integer >> 8
         return b''.join(chunks)
 
+    @classmethod
+    def _encode_string_to_key_count(cls, count):
+        r"""Encode RFC 4880's string-to-key count
+
+        >>> PGPPacket._encode_string_to_key_count(753664)
+        b'\x97'
+        """
+        coded_count = 0
+        count = count >> cls._string_to_key_expbias
+        while not count & 1:
+            count = count >> 1
+            coded_count += 1 << 4
+        coded_count += count & 15
+        return bytes([coded_count])
+
     def _serialize_string_to_key_specifier(self):
         string_to_key_type = bytes([
             self._reverse(
@@ -753,7 +780,8 @@ class PGPPacket (dict):
             chunks.append(bytes([self._reverse(
                 self._hash_algorithms, self['string-to-key-hash-algorithm'])]))
             chunks.append(self['string-to-key-salt'])
-            chunks.append(bytes([self['string-to-key-coded-count']]))
+            chunks.append(self._encode_string_to_key_count(
+                count=self['string-to-key-count']))
         else:
             raise NotImplementedError(
                 'string-to-key type {}'.format(self['string-to-key-type']))