[Cryptography-dev] ECDSA Interoperablity with Microsoft CNG-based peer

André Caron andre.l.caron at gmail.com
Wed Aug 17 20:31:37 EDT 2016


For posterity, my two functions can be replaced with these:

    from cryptography.utils import int_from_bytes, int_to_bytes
    from cryptography.hazmat.primitives.asymmetric.utils import (
        decode_dss_signature,
        encode_dss_signature,
    )

    def decode_ieee_p1363_signature(data):
        """Decode ECDSA signature in IEEE P1363 format to ASN.1 (DER)."""
        return encode_dss_signature(
            int_from_bytes(data[:32], 'big'),
            int_from_bytes(data[32:], 'big'),
        )

    def encode_ieee_p1363_signature(data):
        """Encode ECDSA signature in ASN.1 (DER) to IEEE P1363 format."""
        r, s = decode_dss_signature(data)
        return int_to_bytes(r, 32) + int_to_bytes(s, 32)

André

On Tue, Aug 16, 2016 at 11:18 AM, André Caron <andre.l.caron at gmail.com>
wrote:

> Hi Alex,
>
> Thanks a bunch for the tip!
>
> I managed to get the signature verification step to work. Here is my code:
>
>     signature = ...  # 64 bytes in P1363 format.
>
>     # Load signature from Microsoft's raw format to the DER-encoded
>     # format expected by cryptography.
>     #
>     # Conversion steps:
>     # - decode public numbers as 2 big-endian octet stream;
>     # - DER-encode public numbers.
>     def _bin2int(backend, x):
>         backend = backend._backends[0]
>         i = backend._lib.BN_bin2bn(x, len(x), backend._ffi.NULL)
>         return backend._bn_to_int(i)
>     signature = encode_dss_signature(
>         _bin2int(backend, signature[:32]),
>         _bin2int(backend, signature[32:]),
>     )
>
> I also managed to get the CGN API-based signature verification step to
> work when using a Cryptography-based server to sign the payload using this
> piece of code:
>
>     signature = ...  # ASN.1
>
>     # Convert signature from the DER-encoded format used by
>     #  OpenSSL into Microsoft's raw format
>     #
>     # Conversion steps:
>     # - DER-decode numbers;
>     # - encode numbers as 2 big-endian octet streams.
>     def int2bin(backend, i):
>         backend = backend._backends[0]
>         i = backend._int_to_bn(i)
>         x = backend._ffi.new(
>             "unsigned char[]", backend._lib.BN_num_bytes(i)
>         )
>         n = backend._lib.BN_bn2bin(i, x)
>         i = backend._ffi.buffer(x)[:n]
>         return i
>     r, s = decode_dss_signature(signature)
>     signature = int2bin(backend, r) + int2bin(backend, s)
>
> (I updated my gist with the C++ client & server and the Python client &
> server if you want to look at the rest.)
>
> I also have related snippets that allow me to convert public & private
> keys to and from Microsoft's format.
>
> Now, i have full interop: I can generate a keypair, share the public key,
> sign and verify using both Python and C++ (both directions).
>
> However, I'm relying on cryptography internals to do this, which is
> definitely not desirable in the medium-long term.  Know of a better way to
> do these conversions by relying only on public APIs?
>
> Also, I guess I'm not the only person that's going to be running into
> this.  Any interest in adding built-in support for this in cryptography?
> If so, I'd be willing to put some effort into a PR.
>
> Thanks,
>
> André
>
>
> On Fri, Aug 12, 2016 at 7:41 AM, Alex Gaynor <alex.gaynor at gmail.com>
> wrote:
>
>> https://stackoverflow.com/questions/20992760/understanding-
>> bcryptsignhash-output-signature matches your intuition: the format out
>> of Microsoft's function is just the two numbers concatenated together,
>> perhaps they are little endian instead of big endian though?
>>
>> Alex
>>
>> On Thu, Aug 11, 2016 at 1:38 PM, André Caron <andre.l.caron at gmail.com>
>> wrote:
>>
>>> Hi all,
>>>
>>> I'm dealing with a C++ client & server pair that uses ECDSA to verify
>>> the server's identity.  I'm trying to write a new Python client that will
>>> exchange with the server without making any changes to the server.  I've
>>> gotten quite a bit of this in place with cryptography (the Python package
>>> :-), but I'm incapable of getting the Python client to verify the signature
>>> sent by the server and I'd like to see if you can help me out.
>>>
>>> One of the problems here is that the serialization formats seem to be
>>> internal to Microsoft's CNG API.  For example, the public key is the raw
>>> output of BCryptExportKey() and the signature is the raw output of
>>> BCryptSignHash().  These are Microsoft APIs, so... needless to say
>>> cryptography doesn't "just work" with these formats.
>>>
>>> I'm pretty sure I managed to nail the key format conversion as Microsoft
>>> makes an obscure reference to the format[1], but I'm still having trouble
>>> with signatures.  The blob I get as output from BCryptSignHash() has 64
>>> bytes, but signatures for the same algorithm using cryptography are usually
>>> 70-72 bytes, so I'm confused.  Cryptography's ECC signature computation
>>> clearly documents the format: "The signature is formatted as DER-encoded
>>> bytes, as specified in RFC 3279."  However, Microsoft doesn't seem to
>>> output record an equivalent anywhere.  They're usually pretty consistent
>>> with their APIs and storage formats, so I assume some sort of storage
>>> similar to the keys where we have two 32-byte octet streams in big endian
>>> format containing the values for R and S, but I haven't had any luck with
>>> this.  I also know that the DER encoding for two integer fields will
>>> normally add 6 bytes of overhead, which gets us up to 70, but there is
>>> still the occasional extra 1 or 2 bytes, so I'm obviously missing something
>>> and may not be on the right track.
>>>
>>> [1] :https://msdn.microsoft.com/library/aa375520.aspx
>>>
>>> Anyways, I managed to extract the BCrypt* function calls from the server
>>> and client into a pair of C++ program the contain only the signing and
>>> signature verification code to reproduce the flow.  The total is ~30 lines
>>> of C++ code on each side, plus ~400 lines wrappers for BCrypt* calls
>>> (resource management, error handling and links to CNG API docs).
>>>
>>> I've also written a small cryptography-based Python program that tries
>>> to mimic the C++ client and I cannot get that part to run.
>>>
>>> If anyone has a few minutes to spare to give my Python code a second
>>> pair of eyeballs, I'd really appreciate it.
>>>
>>> I've saved up all of that on this Gist: https://gist.github.com/
>>> AndreLouisCaron/ab5ee411d0722a0981feceddbf5cb3d9
>>>
>>> The gist contents are as follows:
>>> - genkeys.py: generate a public/private key pair, write to disk in
>>> Microsoft's format;
>>> - server.cpp: load secret key, compute signature, save payload &
>>> signature to disk;
>>> - client.cpp: load public key, payload & signature from disk, verify
>>> signature;
>>> - common.h: stuff shared by client.cpp & server.cpp;
>>> - client.py: same as client.cpp, but using cryptography.
>>>
>>> I also have an alternate C++ client based on OpenSSL which might be a
>>> better source of inspiration.  I'll see if I can extract pars of that too
>>> as a reference since it might be easier to map to cryptography's internals.
>>>
>>> Thanks in advance,
>>>
>>> André
>>>
>>> _______________________________________________
>>> Cryptography-dev mailing list
>>> Cryptography-dev at python.org
>>> https://mail.python.org/mailman/listinfo/cryptography-dev
>>>
>>>
>>
>>
>> --
>> "I disapprove of what you say, but I will defend to the death your right
>> to say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
>> "The people's good is the highest law." -- Cicero
>> GPG Key fingerprint: D1B3 ADC0 E023 8CA6
>>
>>
>> _______________________________________________
>> Cryptography-dev mailing list
>> Cryptography-dev at python.org
>> https://mail.python.org/mailman/listinfo/cryptography-dev
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/cryptography-dev/attachments/20160817/493a47a8/attachment-0001.html>


More information about the Cryptography-dev mailing list