Merge pull request #416 from dreid/improved-aead-examples

Try making the AEAD examples less dense.
diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst
index a1a3ec0..a683bb9 100644
--- a/docs/hazmat/primitives/symmetric-encryption.rst
+++ b/docs/hazmat/primitives/symmetric-encryption.rst
@@ -356,20 +356,80 @@
     :param bytes tag: The tag bytes to verify during decryption. When encrypting
                       this must be None.
 
-    .. doctest::
+    .. testcode::
 
-        >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
-        >>> from cryptography.hazmat.backends import default_backend
-        >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend())
-        >>> encryptor = cipher.encryptor()
-        >>> encryptor.authenticate_additional_data(b"authenticated but not encrypted payload")
-        >>> ct = encryptor.update(b"a secret message") + encryptor.finalize()
-        >>> tag = encryptor.tag
-        >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend)
-        >>> decryptor = cipher.decryptor()
-        >>> decryptor.authenticate_additional_data(b"authenticated but not encrypted payload")
-        >>> decryptor.update(ct) + decryptor.finalize()
-        'a secret message'
+        import os
+
+        from cryptography.hazmat.primitives.ciphers import (
+            Cipher, algorithms, modes
+        )
+
+        from cryptography.hazmat.primitives.padding import PKCS7
+
+        def encrypt(key, plaintext, associated_data):
+            # Generate a random 96-bit IV.
+            iv = os.urandom(12)
+
+            # Construct a AES-GCM Cipher object with the given and our randomly
+            # generated IV.
+            encryptor = Cipher(
+                algorithms.AES(key),
+                modes.GCM(iv),
+                backend=default_backend()
+            ).encryptor()
+
+            # We have to pad our plaintext because it may not be a
+            # multiple of the block size.
+            padder = PKCS7(algorithms.AES.block_size).padder()
+            padded_plaintext = padder.update(plaintext) + padder.finalize()
+
+            # associated_data will be authenticated but not encrypted,
+            # it must also be passed in on decryption.
+            encryptor.authenticate_additional_data(associated_data)
+
+            # Encrypt the plaintext and get the associated ciphertext.
+            ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
+
+            return (iv, ciphertext, encryptor.tag)
+
+        def decrypt(key, associated_data, iv, ciphertext, tag):
+            # Construct a Cipher object, with the key, iv, and additionally the
+            # GCM tag used for authenticating the message.
+            decryptor = Cipher(
+                algorithms.AES(key),
+                modes.GCM(iv, tag),
+                backend=default_backend()
+            ).decryptor()
+
+            # We will need to unpad the plaintext.
+            unpadder = PKCS7(algorithms.AES.block_size).unpadder()
+
+            # We put associated_data back in or the tag will fail to verify
+            # when we finalize the decryptor.
+            decryptor.authenticate_additional_data(associated_data)
+
+            # Decryption gets us the authenticated padded plaintext.
+            padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
+
+            return unpadder.update(padded_plaintext) + unpadder.finalize()
+
+        iv, ciphertext, tag = encrypt(
+            key,
+            b"a secret message!",
+            b"authenticated but not encrypted payload"
+        )
+
+        print(decrypt(
+            key,
+            b"authenticated but not encrypted payload",
+            iv,
+            ciphertext,
+            tag
+        ))
+
+    .. testoutput::
+
+        a secret message!
 
 
 Insecure Modes