PKCS#8 serialized key loading
diff --git a/cryptography/hazmat/backends/interfaces.py b/cryptography/hazmat/backends/interfaces.py
index 97a7a4f..55d5cd7 100644
--- a/cryptography/hazmat/backends/interfaces.py
+++ b/cryptography/hazmat/backends/interfaces.py
@@ -196,6 +196,16 @@
 
 
 @six.add_metaclass(abc.ABCMeta)
+class PKCS8SerializationBackend(object):
+    @abc.abstractmethod
+    def load_pkcs8_pem_private_key(self, data, password):
+        """
+        Load a private key from PEM encoded data, using password if the data
+        is encrypted.
+        """
+
+
+@six.add_metaclass(abc.ABCMeta)
 class CMACBackend(object):
     @abc.abstractmethod
     def cmac_algorithm_supported(self, algorithm):
diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py
index 8d76160..ce4963b 100644
--- a/cryptography/hazmat/backends/openssl/backend.py
+++ b/cryptography/hazmat/backends/openssl/backend.py
@@ -26,7 +26,8 @@
 )
 from cryptography.hazmat.backends.interfaces import (
     CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
-    PBKDF2HMACBackend, RSABackend, TraditionalOpenSSLSerializationBackend
+    PBKDF2HMACBackend, PKCS8SerializationBackend, RSABackend,
+    TraditionalOpenSSLSerializationBackend
 )
 from cryptography.hazmat.bindings.openssl.binding import Binding
 from cryptography.hazmat.primitives import hashes, interfaces
@@ -55,6 +56,7 @@
 @utils.register_interface(PBKDF2HMACBackend)
 @utils.register_interface(RSABackend)
 @utils.register_interface(TraditionalOpenSSLSerializationBackend)
+@utils.register_interface(PKCS8SerializationBackend)
 class Backend(object):
     """
     OpenSSL API binding interfaces.
@@ -777,6 +779,12 @@
         return _CMACContext(self, algorithm)
 
     def load_traditional_openssl_pem_private_key(self, data, password):
+        # OpenSSLs API for loading PKCS#8 certs can also load the traditional
+        # format so we just use that for both of them.
+
+        return self.load_pkcs8_pem_private_key(data, password)
+
+    def load_pkcs8_pem_private_key(self, data, password):
         mem_bio = self._bytes_to_bio(data)
 
         password_callback, password_func = self._pem_password_cb(password)
@@ -793,10 +801,18 @@
             if not errors:
                 raise ValueError("Could not unserialize key data.")
 
-            if errors[0][1:] == (
-                self._lib.ERR_LIB_PEM,
-                self._lib.PEM_F_PEM_DO_HEADER,
-                self._lib.PEM_R_BAD_PASSWORD_READ
+            if (
+                errors[0][1:] == (
+                    self._lib.ERR_LIB_PEM,
+                    self._lib.PEM_F_PEM_DO_HEADER,
+                    self._lib.PEM_R_BAD_PASSWORD_READ
+                )
+            ) or (
+                errors[0][1:] == (
+                    self._lib.ERR_LIB_PEM,
+                    self._lib.PEM_F_PEM_READ_BIO_PRIVATEKEY,
+                    self._lib.PEM_R_BAD_PASSWORD_READ
+                )
             ):
                 assert not password
                 raise TypeError(
diff --git a/cryptography/hazmat/primitives/serialization.py b/cryptography/hazmat/primitives/serialization.py
index 3893750..ed73c4c 100644
--- a/cryptography/hazmat/primitives/serialization.py
+++ b/cryptography/hazmat/primitives/serialization.py
@@ -18,3 +18,9 @@
     return backend.load_traditional_openssl_pem_private_key(
         data, password
     )
+
+
+def load_pem_pkcs8_private_key(data, password, backend):
+    return backend.load_pkcs8_pem_private_key(
+        data, password
+    )
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index 8d32ae5..ac3080c 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -9,6 +9,58 @@
 keys to bytes. They generally support encryption of private keys and additional
 key metadata.
 
+Many serialization formats support multiple different types of asymmetric keys
+and will return an an instance of the appropriate type. You should check that
+the returned key matches the type your application expects when using these
+methods.
+
+    .. code-block:: pycon
+
+        >>> key = load_pkcs8_private_key(pem_data, None, backend)
+        >>> if isinstance(key, rsa.RSAPrivateKey):
+        >>>     signature = sign_with_rsa_key(key, message)
+        >>> elif isinstance(key, dsa.DSAPrivateKey):
+        >>>     signature = sign_with_dsa_key(key, message)
+        >>> else:
+        >>>     raise TypeError
+
+
+PKCS #8 Format
+~~~~~~~~~~~~~~
+
+PKCS #8 is a serialization format originally standardized by RSA and
+currently maintained by the IETF in :rfc:`5208`. It supports password based
+encryption and additional key metadata attributes.
+
+
+.. function:: load_pkcs8_private_key(data, password, backend)
+
+    .. versionadded:: 0.5
+
+    Deserialize a private key from PEM encoded data to one of the supported
+    asymmetric private key types.
+
+    :param bytes data: The PEM encoded key data.
+
+    :param bytes password: The password to use to decrypt the data. Should
+        be ``None`` if the private key is not encrypted.
+    :param backend: A
+        :class:`~cryptography.hazmat.backends.interfaces.PKCS8SerializationBackend`
+        provider.
+
+    :returns: A new instance of a private key.
+
+    :raises ValueError: If the PEM data could not be decrypted or if its
+        structure could not be decoded successfully.
+
+    :raises TypeError: If a ``password`` was given and the private key was
+        not encrypted. Or if the key was encrypted but no
+        password was supplied.
+
+    :raises UnsupportedAlgorithm: If the serialized key is of a type that
+        is not supported by the backend or if the key is encrypted with a
+        symmetric cipher that is not supported by the backend.
+
 
 Traditional OpenSSL Format
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -46,3 +98,6 @@
     :raises UnsupportedAlgorithm: If the serialized key is of a type that
         is not supported by the backend or if the key is encrypted with a
         symmetric cipher that is not supported by the backend.
+
+
+.. _`X.501`: en.wikipedia.org/wiki/X.500
diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py
index 8d3b8fd..a38450d 100644
--- a/tests/hazmat/primitives/test_serialization.py
+++ b/tests/hazmat/primitives/test_serialization.py
@@ -21,6 +21,7 @@
 
 from cryptography.hazmat.primitives.asymmetric import dsa, rsa
 from cryptography.hazmat.primitives.serialization import (
+    load_pem_pkcs8_private_key,
     load_pem_traditional_openssl_private_key
 )
 
@@ -246,3 +247,214 @@
             load_pem_traditional_openssl_private_key(
                 key_data, password, backend
             )
+
+
+@pytest.mark.traditional_openssl_serialization
+class TestPKCS8Serialisation(object):
+    @pytest.mark.parametrize(
+        ("key_file", "password"),
+        [
+            ("unencpkcs8.pem", None),
+            ("encpkcs8.pem", b"foobar"),
+            ("enc2pkcs8.pem", b"baz"),
+            ("pkcs12_s2k_pem-X_9607.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9671.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9925.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9926.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9927.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9928.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9929.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9930.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9931.pem", b"123456"),
+            ("pkcs12_s2k_pem-X_9932.pem", b"123456"),
+        ]
+    )
+    def test_load_pem_rsa_private_key(self, key_file, password, backend):
+        key = load_vectors_from_file(
+            os.path.join(
+                "asymmetric", "PKCS8", key_file),
+            lambda pemfile: load_pem_pkcs8_private_key(
+                pemfile.read().encode(), password, backend
+            )
+        )
+
+        assert key
+        assert isinstance(key, rsa.RSAPrivateKey)
+        _check_rsa_private_key(key)
+
+    def test_unused_password(self, backend):
+        key_file = os.path.join(
+            "asymmetric", "PKCS8", "unencpkcs8.pem")
+        password = b"this password will not be used"
+
+        with pytest.raises(TypeError):
+            load_vectors_from_file(
+                key_file,
+                lambda pemfile: load_pem_pkcs8_private_key(
+                    pemfile.read().encode(), password, backend
+                )
+            )
+
+    def test_wrong_password(self, backend):
+        key_file = os.path.join(
+            "asymmetric", "PKCS8", "encpkcs8.pem")
+        password = b"this password is wrong"
+
+        with pytest.raises(ValueError):
+            load_vectors_from_file(
+                key_file,
+                lambda pemfile: load_pem_pkcs8_private_key(
+                    pemfile.read().encode(), password, backend
+                )
+            )
+
+    @pytest.mark.parametrize("password", [None, b""])
+    def test_missing_password(self, backend, password):
+        key_file = os.path.join(
+            "asymmetric",
+            "PKCS8",
+            "encpkcs8.pem"
+        )
+
+        with pytest.raises(TypeError):
+            load_vectors_from_file(
+                key_file,
+                lambda pemfile: load_pem_pkcs8_private_key(
+                    pemfile.read().encode(), password, backend
+                )
+            )
+
+    def test_wrong_format(self, backend):
+        key_data = b"---- NOT A KEY ----\n"
+
+        with pytest.raises(ValueError):
+            load_pem_pkcs8_private_key(
+                key_data, None, backend
+            )
+
+        with pytest.raises(ValueError):
+            load_pem_pkcs8_private_key(
+                key_data, b"this password will not be used", backend
+            )
+
+    def test_corrupt_format(self, backend):
+        # unencpkcs8.pem with a bunch of data missing.
+        key_data = textwrap.dedent("""\
+        -----BEGIN PRIVATE KEY-----
+        MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet
+        8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk
+        FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv
+        FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i
+        +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+
+        hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj
+        NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb
+        tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd
+        SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR
+        we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL
+        z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l
+        Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/
+        mu/UpE/BRZmR
+        -----END PRIVATE KEY-----
+        """).encode()
+
+        with pytest.raises(ValueError):
+            load_pem_pkcs8_private_key(
+                key_data, None, backend
+            )
+
+        with pytest.raises(ValueError):
+            load_pem_pkcs8_private_key(
+                key_data, b"this password will not be used", backend
+            )
+
+    def test_encrypted_corrupt_format(self, backend):
+        # encpkcs8.pem with some bits flipped.
+        key_data = textwrap.dedent("""\
+        -----BEGIN ENCRYPTED PRIVATE KEY-----
+        MIICojAcBgoqhkiG9w0BDAEDMA4ECHK0M0+QuEL9AgIBIcSCAoDRq+KRY+0XP0tO
+        lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/
+        Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0adVMbt3N+CJHGB
+        85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KnhUnLT98J8rhU
+        ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU
+        5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq
+        gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T
+        0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX
+        huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o
+        3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg
+        7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7
+        cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P
+        0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT
+        6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3
+        qeJNaJFu
+        -----END ENCRYPTED PRIVATE KEY-----
+        """).encode()
+
+        password = b"this password is wrong"
+
+        with pytest.raises(ValueError):
+            load_pem_pkcs8_private_key(
+                key_data, None, backend
+            )
+
+        with pytest.raises(ValueError):
+            load_pem_pkcs8_private_key(
+                key_data, password, backend
+            )
+
+    def test_key1_pem_encrypted_values(self, backend):
+        pkey = load_vectors_from_file(
+            os.path.join(
+                "asymmetric", "PKCS8", "encpkcs8.pem"),
+            lambda pemfile: load_pem_pkcs8_private_key(
+                pemfile.read().encode(), b"foobar", backend
+            )
+        )
+        assert pkey
+
+        assert pkey.modulus == int(
+            "00beec64d6db5760ac2fd4c971145641b9bd7f5c56558ece608795c79807"
+            "376a7fe5b19f95b35ca358ea5c8abd7ae051d49cd2f1e45969a1ae945460"
+            "3c14b278664a0e414ebc8913acb6203626985525e17a600611b028542dd0"
+            "562aad787fb4f1650aa318cdcff751e1b187cbf6785fbe164e9809491b95"
+            "dd68480567c99b1a57", 16
+        )
+
+        assert pkey.public_exponent == 65537
+
+        assert pkey.private_exponent == int(
+            "0cfe316e9dc6b8817f4fcfd5ae38a0886f68f773b8a6db4c9e6d8703c599"
+            "f3d9785c3a2c09e4c8090909fb3721e19a3009ec21221523a729265707a5"
+            "8f13063671c42a4096cad378ef2510cb59e23071489d8893ac4934dd149f"
+            "34f2d094bea57f1c8027c3a77248ac9b91218737d0c3c3dfa7d7829e6977"
+            "cf7d995688c86c81", 16
+        )
+
+        assert pkey.p == int(
+            "00db122ac857b2c0437d7616daa98e597bb75ca9ad3a47a70bec10c10036"
+            "03328794b225c8e3eee6ffd3fd6d2253d28e071fe27d629ab072faa14377"
+            "ce6118cb67", 16
+        )
+
+        assert pkey.q == int(
+            "00df1b8aa8506fcbbbb9d00257f2975e38b33d2698fd0f37e82d7ef38c56"
+            "f21b6ced63c825383782a7115cfcc093300987dbd2853b518d1c8f26382a"
+            "2d2586d391", 16
+        )
+
+        assert pkey.dmp1 == int(
+            "00be18aca13e60712fdf5daa85421eb10d86d654b269e1255656194fb0c4"
+            "2dd01a1070ea12c19f5c39e09587af02f7b1a1030d016a9ffabf3b36d699"
+            "ceaf38d9bf", 16
+        )
+
+        assert pkey.dmq1 == int(
+            "71aa8978f90a0c050744b77cf1263725b203ac9f730606d8ae1d289dce4a"
+            "28b8d534e9ea347aeb808c73107e583eb80c546d2bddadcdb3c82693a4c1"
+            "3d863451", 16
+        )
+
+        assert pkey.iqmp == int(
+            "136b7b1afac6e6279f71b24217b7083485a5e827d156024609dae39d48a6"
+            "bdb55af2f062cc4a3b077434e6fffad5faa29a2b5dba2bed3e4621e478c0"
+            "97ccfe7f", 16
+        )