Repository: pyca/cryptography

Issue:
GCM on OpenSSL without calling authenticate_additional_data does not produce ciphertext.
atula on IRC pointed out that when using AES-GCM mode without calling `authenticate_additional_data` he always got ciphertext consisting entirely of null bytes.

I've reproduced the problem here:

``` python

import os

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

backend = default_backend()

key = os.urandom(16)
plaintext = b'I am a secret.'

initialization_vector = os.urandom(12)

encryptor1 = Cipher(
    algorithms.AES(key),
    modes.GCM(initialization_vector, min_tag_length=32),
    backend=backend).encryptor()

print 'ciphertext:   ', repr(encryptor1.update(plaintext) +
                             encryptor1.finalize())

encryptor2 = Cipher(
    algorithms.AES(key),
    modes.GCM(initialization_vector, min_tag_length=32),
    backend=backend).encryptor()

encryptor2.authenticate_additional_data(b'')

print 'ciphertext 2: ', repr(encryptor2.update(plaintext) +
                             encryptor2.finalize())
```

The first ciphertext is with an `AEADCipherContext` that has never had authenticate_additional_data called.

``` console

↪︎ python t.py
ciphertext:    '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
ciphertext 2:  'F\xf4\xc9\x8f!\xf0\xe8\xbc["\xba|\x12\x8b'
```



Discussion:
There's a parallel problem here where if you `authenticate_additional_data` but don't call `update` (basically only using the GHASH function of GCM) then on `finalize()` the tag will be all null bytes. These bugs are both in the CommonCrypto backend but not the OpenSSL backend.

I will fix both these bugs in a PR momentarily.


Resolving patch:
diff --git a/cryptography/hazmat/backends/commoncrypto/ciphers.py b/cryptography/hazmat/backends/commoncrypto/ciphers.py
--- a/cryptography/hazmat/backends/commoncrypto/ciphers.py
+++ b/cryptography/hazmat/backends/commoncrypto/ciphers.py
@@ -151,6 +151,12 @@ def __init__(self, backend, cipher, mode, operation):
             len(mode.initialization_vector)
         )
         self._backend._check_cipher_response(res)
+        # CommonCrypto has a bug where calling update without at least one
+        # call to authenticate_additional_data will result in null byte output
+        # for ciphertext. The following empty byte string call prevents the
+        # issue, which is present in at least 10.8 and 10.9.
+        # Filed as rdar://18314544
+        self.authenticate_additional_data(b"")
 
     def update(self, data):
         buf = self._backend._ffi.new("unsigned char[]", len(data))
@@ -164,6 +170,12 @@ def update(self, data):
         return self._backend._ffi.buffer(buf)[:]
 
     def finalize(self):
+        # CommonCrypto has a yet another bug where you must make at least one
+        # call to update. If you pass just AAD and call finalize without a call
+        # to update you'll get null bytes for tag. The following update call
+        # prevents this issue, which is present in at least 10.8 and 10.9.
+        # Filed as rdar://18314580
+        self.update(b"")
         tag_size = self._cipher.block_size // 8
         tag_buf = self._backend._ffi.new("unsigned char[]", tag_size)
         tag_len = self._backend._ffi.new("size_t *", tag_size)
diff --git a/cryptography/hazmat/backends/openssl/ciphers.py b/cryptography/hazmat/backends/openssl/ciphers.py
--- a/cryptography/hazmat/backends/openssl/ciphers.py
+++ b/cryptography/hazmat/backends/openssl/ciphers.py
@@ -128,6 +128,14 @@ def update(self, data):
         return self._backend._ffi.buffer(buf)[:outlen[0]]
 
     def finalize(self):
+        # OpenSSL 1.0.1 on Ubuntu 12.04 (and possibly other distributions)
+        # appears to have a bug where you must make at least one call to update
+        # even if you are only using authenticate_additional_data or the
+        # GCM tag will be wrong. An (empty) call to update resolves this
+        # and is harmless for all other versions of OpenSSL.
+        if isinstance(self._mode, GCM):
+            self.update(b"")
+
         buf = self._backend._ffi.new("unsigned char[]", self._block_size)
         outlen = self._backend._ffi.new("int *")
         res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)


Test patch:
diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py
--- a/tests/hazmat/primitives/test_aes.py
+++ b/tests/hazmat/primitives/test_aes.py
@@ -18,7 +18,7 @@
 
 import pytest
 
-from cryptography.hazmat.primitives.ciphers import algorithms, modes
+from cryptography.hazmat.primitives.ciphers import algorithms, base, modes
 
 from .utils import generate_aead_test, generate_encrypt_test
 from ...utils import load_nist_vectors
@@ -228,3 +228,36 @@ class TestAESModeGCM(object):
         algorithms.AES,
         modes.GCM,
     )
+
+    def test_gcm_tag_with_only_aad(self, backend):
+        key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3")
+        iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d")
+        aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193")
+        tag = binascii.unhexlify(b"0f247e7f9c2505de374006738018493b")
+
+        cipher = base.Cipher(
+            algorithms.AES(key),
+            modes.GCM(iv),
+            backend=backend
+        )
+        encryptor = cipher.encryptor()
+        encryptor.authenticate_additional_data(aad)
+        encryptor.finalize()
+        assert encryptor.tag == tag
+
+    def test_gcm_ciphertext_with_no_aad(self, backend):
+        key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a")
+        iv = binascii.unhexlify(b"8b23299fde174053f3d652ba")
+        ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42")
+        tag = binascii.unhexlify(b"23c7ab0f952b7091cd324835043b5eb5")
+        pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032")
+
+        cipher = base.Cipher(
+            algorithms.AES(key),
+            modes.GCM(iv),
+            backend=backend
+        )
+        encryptor = cipher.encryptor()
+        computed_ct = encryptor.update(pt) + encryptor.finalize()
+        assert computed_ct == ct
+        assert encryptor.tag == tag