2

Background: I'm writing a GPL Python OpenPGP to JSON parser which I'm testing on files generated with GPG 1.4.16.

If given a passphrase, the parser will generate keys using the string-to-key methods and ultimately decrypt messages.

I'm starting off with symmetric encryption messages:

echo "hello" | gpg --s2k-mode=0 --symmetric > symmetric.simples2k.gpg

... and using "foo" as the passphrase.

This generates a packet with a SymmetricKeyEncryptedSessionKeyPacket and a SymmetricEncryptedandIntegrityProtectedDataPacket packet, as expected.

The S2K paramters that GPG created are: Simple S2K (http://tools.ietf.org/html/rfc4880#section-3.7.1.1) with SHA1 hash and AES256 symmetric cipher.

Problem: When I derive the key from the passphrase foo using SimpleS2K then attempt to decrypt with AES256, it doesn't decrypt correctly. So part 1 of my investigation is verifying that I'm doing the S2K correctly.

Here's my understanding of how to generate the key from the passphrase foo using SimpleS2K

  1. Create two SHA1 hashers (because AES256 needs 32-byte key, SHA1 produces 20-byte hash)
  2. Don't preload hashers[0]
  3. Update hashers[1] with 0x00
  4. Update hashers[0] with UTF-8 encoded foo
  5. Update hashers[0] with UTF-8 encoded foo
  6. Concatenate hashers[0].digest plus hashers[1].digest
  7. Take first 32 bytes of result (ie drop last 8 bytes)

Here's a demonstration implementation in Python 3:

import hashlib
from Crypto.Cipher import AES  # using pycrypto==2.6.1


def format_octets(octets):
    return ' '.join(['{:02x}'.format(x) for x in octets])

hasher_0 = hashlib.sha1()
hasher_1 = hashlib.sha1()

hasher_1.update(bytes([0x0]))

hasher_0.update('foo'.encode('utf-8'))
hasher_1.update('foo'.encode('utf-8'))

key = (hasher_0.digest() + hasher_1.digest())[0:32]
print('s2k key: {}'.format(format_octets(key)))

encrypted_data = bytes([
    0x0b, 0x1e, 0xcf, 0x86, 0x33, 0x08, 0xfd, 0x66, 0x9a, 0xf0, 0xbe, 0x48,
    0x62, 0xa4, 0xa5, 0x42, 0x4f, 0xd8, 0x20, 0xfe, 0x16, 0xe4, 0x4c, 0xdb,
    0x80, 0x89, 0xee, 0x34, 0x72, 0xef, 0x52, 0x36, 0x70, 0x15, 0x01, 0x82,
    0xd5, 0x0e, 0xeb, 0x61, 0xba, 0xe7, 0x71, 0x4a, 0x8c, 0x22, 0x6a, 0x9c,
    0x79, 0x8f, 0xe3, 0xda, 0x31, 0xfc, 0xad, 0x14, 0xeb, 0x8a])

block_size = 16  # AES
iv = bytes([0x0] * block_size)  # all-zero initialisation vector

obj = AES.new(key, AES.MODE_CFB, iv)
decrypted = obj.decrypt(encrypted_data)

random_octets = decrypted[0: block_size]
repeated_octets = decrypted[block_size: block_size + 2]
plaintext_packets = decrypted[block_size + 2:-20]
sha1 = decrypted[-20:]

print('random octets: {}'.format(format_octets(random_octets)))
print('repeated octets: {}'.format(format_octets(repeated_octets)))
print('plaintext_packets: {}'.format(format_octets(plaintext_packets)))
print('sha1: {}'.format(format_octets(sha1)))

Which outputs

s2k key: 0b ee c7 b5 ea 3f 0f db c9 5d 0d d4 7f 3c 5b c2 75 da 8a 33 5a 8c aa 40 39 fd bc 02 c0 1a 64 9c
random octets: 48 9b 65 84 01 f7 33 23 0a 6e e4 32 9d c4 ef 99
repeated octets: 56 a8
plaintext_packets: b9 fd 5a 05 6e d2 83 22 22 fe e5 d7 77 dd 93 49 4f 03 fe 13
sha1: ce 7d 93 34 59 f6 71 70 c3 bd 06 94 ad 97 f3 0a 59 72 1d 93

The full JSON output is here: https://gist.github.com/paulfurley/5285a6ebce4a30dca12b#file-bad_simples2k-json

Hopefully we can rule out the S2K part and get onto the AES part :)

Thanks!

  • Was the null character included in one, but not the other? – RoraΖ Mar 27 '15 at 14:45
  • Good question - hopefully it wouldn't be included in either but worth a check – Paul M Furley Mar 27 '15 at 14:55
  • Also, I believe that GPG compresses data by default. You might want to see if you have a compressed packet type as your result. Also make sure that you're using CFB mode with an IV of all zeroes specified. I'm basing this off section 5.13 – RoraΖ Mar 27 '15 at 15:08
  • @raz Thanks - however unfortunately I can tell that I'm not decrypting successfully because bytes 15,16 should be equal to 17,18 and they aren't :( – Paul M Furley Mar 27 '15 at 15:22
  • If you're feeling brave I found the sGPG source line where the magic happens: github.com/paulfurley/gnupg-mirror/blob/… It doesn't look like it writes the NULL byte but the code's hardly clear :( – Paul M Furley Mar 27 '15 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.