Use PyCrypto for decrypting symmetric encryption
authorW. Trevor King <wking@tremily.us>
Sat, 21 Dec 2013 17:06:40 +0000 (09:06 -0800)
committerW. Trevor King <wking@tremily.us>
Mon, 23 Dec 2013 21:41:05 +0000 (13:41 -0800)
Maintained by Dwayne C. Litzenberger [1,2,3].  Although there is a
MODE_OPENGP in PyCrypto since v2.6 [4], it seems like v4 secret key
bodies are actually encrypted using plain-vanilla MODE_CFB.

[1]: http://www.pycrypto.org/
[2]: https://www.dlitz.net/software/pycrypto/
[3]: https://github.com/dlitz/pycrypto/
[4]: https://github.com/dlitz/pycrypto/blob/master/ChangeLog

gpg-migrate.py

index 785f78eeec4dea5a40068f8efbd90be566e3d914..9f775e1d1241f249a60460c2a4cc86129ad7523a 100755 (executable)
@@ -7,6 +7,11 @@ import re as _re
 import subprocess as _subprocess
 import struct as _struct
 
+import Crypto.Cipher.AES as _crypto_cipher_aes
+import Crypto.Cipher.Blowfish as _crypto_cipher_blowfish
+import Crypto.Cipher.CAST as _crypto_cipher_cast
+import Crypto.Cipher.DES3 as _crypto_cipher_des3
+
 
 def _get_stdout(args, stdin=None):
     stdin_pipe = None
@@ -109,6 +114,15 @@ class PGPPacket (dict):
         'cast5': 64,
         }
 
+    _crypto_module = {
+        'aes with 128-bit key': _crypto_cipher_aes,
+        'aes with 192-bit key': _crypto_cipher_aes,
+        'aes with 256-bit key': _crypto_cipher_aes,
+        'blowfish': _crypto_cipher_blowfish,
+        'cast5': _crypto_cipher_cast,
+        'tripledes': _crypto_cipher_des3,
+        }
+
     _compression_algorithms = {
         0: 'uncompressed',
         1: 'zip',
@@ -790,7 +804,31 @@ class PGPPacket (dict):
         return b''.join(chunks)
 
     def decrypt_symmetric_encryption(self, data):
-        raise NotImplementedError('decrypt symmetric encryption')
+        """Decrypt OpenPGP's Cipher Feedback mode"""
+        algorithm = self['symmetric-encryption-algorithm']
+        module = self._crypto_module[algorithm]
+        key_size = self._key_size[algorithm]
+        segment_size_bits = self._cipher_block_size[algorithm]
+        if segment_size_bits % 8:
+            raise NotImplementedError(
+                ('{}-bit segment size for {} is not an integer number of bytes'
+                 ).format(segment_size_bits, algorithm))
+        segment_size_bytes = segment_size_bits // 8
+        padding = segment_size_bytes - len(data) % segment_size_bytes
+        if padding:
+            data += b'\x00' * padding
+        passphrase = _getpass.getpass(
+            'passphrase for {}: '.format(self['fingerprint'][-8:]))
+        passphrase = passphrase.encode('ascii')
+        cipher = module.new(
+            key=passphrase,
+            mode=module.MODE_CFB,
+            IV=self['initial-vector'],
+            segment_size=segment_size_bits)
+        plaintext = cipher.decrypt(data)
+        if padding:
+            plaintext = plaintext[:-padding]
+        return plaintext
 
 
 def packets_from_bytes(data):