pyca / cryptography

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers.
https://cryptography.io
Other
6.4k stars 1.47k forks source link

ModuleNotFoundError: No module named 'cryptography.hazmat.backends.openssl.ec' #11147

Closed EngineerHus closed 1 week ago

EngineerHus commented 1 week ago

If none of that works, please make sure to include the following information in your bug report:

Hi All,

I upgraded my cryptography library from 41.0.5 to 42.0.8 and now it is throwing the following error ModuleNotFoundError: No module named 'cryptography.hazmat.backends.openssl.ec'. Has these functions become depreciated or something along those lines?

Much thanks!

alex commented 1 week ago

cryptography.hazmat.backends.openssl.ec was never part of the public API of this package, why were you using it directly?

EngineerHus commented 1 week ago

cryptography.hazmat.backends.openssl.ec was never part of the public API of this package, why were you using it directly?

not sure how it isn't part of the public API as I have been using it for the past year. I used that import statement (from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey) to get the _EllipticCurvePrivateKey class which can be found within ec.py which comes with the cryptography package. The full path to it is /usr/local/lib/python3.8/site-packages/cryptography/hazmat/backends/openssl/ec.py

This is part of the code that can be found within ec.py

class _EllipticCurvePublicKey(ec.EllipticCurvePublicKey):
    def __init__(self, backend: Backend, ec_key_cdata, evp_pkey):
        self._backend = backend
        self._ec_key = ec_key_cdata
        self._evp_pkey = evp_pkey

        sn = _ec_key_curve_sn(backend, ec_key_cdata)
        self._curve = _sn_to_elliptic_curve(backend, sn)
        _mark_asn1_named_ec_curve(backend, ec_key_cdata)
        _check_key_infinity(backend, ec_key_cdata)

    @property
    def curve(self) -> ec.EllipticCurve:
        return self._curve

    @property
    def key_size(self) -> int:
        return self.curve.key_size

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, _EllipticCurvePublicKey):
            return NotImplemented

        return (
            self._backend._lib.EVP_PKEY_cmp(self._evp_pkey, other._evp_pkey)
            == 1
        )

    def public_numbers(self) -> ec.EllipticCurvePublicNumbers:
        group = self._backend._lib.EC_KEY_get0_group(self._ec_key)
        self._backend.openssl_assert(group != self._backend._ffi.NULL)

        point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key)
        self._backend.openssl_assert(point != self._backend._ffi.NULL)

        with self._backend._tmp_bn_ctx() as bn_ctx:
            bn_x = self._backend._lib.BN_CTX_get(bn_ctx)
            bn_y = self._backend._lib.BN_CTX_get(bn_ctx)

            res = self._backend._lib.EC_POINT_get_affine_coordinates(
                group, point, bn_x, bn_y, bn_ctx
            )
            self._backend.openssl_assert(res == 1)

            x = self._backend._bn_to_int(bn_x)
            y = self._backend._bn_to_int(bn_y)

        return ec.EllipticCurvePublicNumbers(x=x, y=y, curve=self._curve)

    def _encode_point(self, format: serialization.PublicFormat) -> bytes:
        if format is serialization.PublicFormat.CompressedPoint:
            conversion = self._backend._lib.POINT_CONVERSION_COMPRESSED
        else:
            assert format is serialization.PublicFormat.UncompressedPoint
            conversion = self._backend._lib.POINT_CONVERSION_UNCOMPRESSED

        group = self._backend._lib.EC_KEY_get0_group(self._ec_key)
        self._backend.openssl_assert(group != self._backend._ffi.NULL)
        point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key)
        self._backend.openssl_assert(point != self._backend._ffi.NULL)
        with self._backend._tmp_bn_ctx() as bn_ctx:
            buflen = self._backend._lib.EC_POINT_point2oct(
                group, point, conversion, self._backend._ffi.NULL, 0, bn_ctx
            )
            self._backend.openssl_assert(buflen > 0)
            buf = self._backend._ffi.new("char[]", buflen)
            res = self._backend._lib.EC_POINT_point2oct(
                group, point, conversion, buf, buflen, bn_ctx
            )
            self._backend.openssl_assert(buflen == res)

        return self._backend._ffi.buffer(buf)[:]

    def public_bytes(
        self,
        encoding: serialization.Encoding,
        format: serialization.PublicFormat,
    ) -> bytes:
        if (
            encoding is serialization.Encoding.X962
            or format is serialization.PublicFormat.CompressedPoint
            or format is serialization.PublicFormat.UncompressedPoint
        ):
            if encoding is not serialization.Encoding.X962 or format not in (
                serialization.PublicFormat.CompressedPoint,
                serialization.PublicFormat.UncompressedPoint,
            ):
                raise ValueError(
                    "X962 encoding must be used with CompressedPoint or "
                    "UncompressedPoint format"
                )

            return self._encode_point(format)
        else:
            return self._backend._public_key_bytes(
                encoding, format, self, self._evp_pkey, None
            )

    def verify(
        self,
        signature: bytes,
        data: bytes,
        signature_algorithm: ec.EllipticCurveSignatureAlgorithm,
    ) -> None:
        _check_signature_algorithm(signature_algorithm)
        data, _ = _calculate_digest_and_algorithm(
            data,
            signature_algorithm.algorithm,
        )
        _ecdsa_sig_verify(self._backend, self, signature, data)

I did a quick test by installing cryptography 41.0.5 and the ec.py is available, but when installing 42.0.8 - the ec.py file is gone. So that is probably the issue behind this. So my question is why was ec.py file removed? Was the classes/functions inside the file relocated to other files?

reaperhulk commented 1 week ago

The file was removed because we rewrote that section. The entire package was never part of our public API and _EllipticCurvePrivateKey is part of that private surface area.

In Python there is no way to stop people from importing things that are private, so private is convention and documentation. Chances are what you actually wanted was the abstract base class EllipticCurvePrivateKey which is the documented interface, rather than the concrete instantiation of it which can (and did) change.

alex commented 1 week ago

I'll add that our documentation explicitly states the API stability is only offered for documented behavior: https://cryptography.io/en/latest/api-stability/

On Mon, Jun 24, 2024, 9:58 AM Paul Kehrer @.***> wrote:

The file was removed because we rewrote that section. The entire package was never part of our public API and _EllipticCurvePrivateKey is part of that private surface area.

In Python there is no way to stop people from importing things that are private, so private is convention and documentation. Chances are what you actually wanted was the abstract base class EllipticCurvePrivateKey https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/#cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey which is the documented interface, rather than the concrete instantiation of it which can (and did) change.

— Reply to this email directly, view it on GitHub https://github.com/pyca/cryptography/issues/11147#issuecomment-2186650436, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAAGBCIX5G4BGNM2SLTWALZJAQZ3AVCNFSM6AAAAABJZUBVSWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBWGY2TANBTGY . You are receiving this because you commented.Message ID: @.***>

EngineerHus commented 1 week ago

so in other words I was using a private API instead of the public APIs?

reaperhulk commented 1 week ago

Yes -- but it should be a very easy fix for your code.

EngineerHus commented 1 week ago

Yes -- but it should be a very easy fix for your code.

much thanks