0

I'm working with a vendor who owns an API. In order to call the API, they require us to hash the whole body of the request and add it to a Content-Digest digest header key. Content-Digest: SHA256=<digest>. They have provided us with a RSA Private Key along with a linq LINQPad file written in C#. This script is what outputs the base64 encoded hash that goes into the Content-Digest.

The problem is, I don't know any C# and the application that we are going to be using this API for is written in Python. What I'm looking for is a way to output the exact same formatted hash in a Python Script.

This is the C# Code they provided:

void Main()
{
    var payload = GetPayload();
    SignData(payload);
}
private void SignData(string payload)
{

    var keyFormatted = GetRSAKey();

    byte[] privateKeyInDER = Convert.FromBase64String(keyFormatted);

    var rsa = DecodeRSAPrivateKey(privateKeyInDER);

    var data = Encoding.Default.GetBytes(payload);
    using var hasher = new SHA512Managed();
    var signBytes = rsa.SignData(data, hasher);
    var computedSignature = Convert.ToBase64String(signBytes);
    computedSignature.Dump();
}

private static int GetIntegerSize(BinaryReader binary)
{
    var bt = binary.ReadByte();

    if (bt != 0x02)
    {
        return 0;
    }

    bt = binary.ReadByte();

    int count;
    if (bt == 0x81)
    {
        count = binary.ReadByte();
    }
    else if (bt == 0x82)
    {
        var highbyte = binary.ReadByte();
        var lowbyte = binary.ReadByte();
        byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
        count = BitConverter.ToInt32(modint, 0);
    }
    else
    {
        count = bt;
    }

    while (binary.ReadByte() == 0x00)
    {
        count--;
    }

    binary.BaseStream.Seek(-1, SeekOrigin.Current);

    return count;
}
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
    byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

    // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
    MemoryStream mem = new MemoryStream(privkey);
    BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
    try
    {
        var twobytes = binr.ReadUInt16();
        if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
        {
            binr.ReadByte();//advance 1 byte
        }
        else if (twobytes == 0x8230)
        {
            binr.ReadInt16();
        }       //advance 2 bytes
        else
        {
            return null;
        }

        twobytes = binr.ReadUInt16();
        if (twobytes != 0x0102) //version number
        {
            return null;
        }

        var bt = binr.ReadByte();
        if (bt != 0x00)
        {
            return null;
        }

        var elems = GetIntegerSize(binr);
        MODULUS = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        E = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        D = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        P = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        Q = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        DP = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        DQ = binr.ReadBytes(elems);

        elems = GetIntegerSize(binr);
        IQ = binr.ReadBytes(elems);

        // ------- create RSACryptoServiceProvider instance and initialize with private key -----
        RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
        RSAParameters RSAparams = new RSAParameters
        {
            Modulus = MODULUS,
            Exponent = E,
            D = D,
            P = P,
            Q = Q,
            DP = DP,
            DQ = DQ,
            InverseQ = IQ
        };
        RSA.ImportParameters(RSAparams);
        return RSA;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return null;
    }
    finally
    {
        binr.Close();
    }
}
private string GetRSAKey() {
    return "private key";
}
private string GetPayload()
{
return @"{
""key"":""value"",
""key2"":{
    ""subkey"": true,}
    }";
}

And it outputs something like this (344 Characters): 15vgzyv8Cke3Mkkwc3ryAgDMmY6olRfvgLqNyfhfti2GAfLb6s/vgrWc1p5jlWgQHh37Ir7UYThXldspriBz5NPl+BSFIW2dxXTMO2NMpzgc/5fmFN2maJCgwzDP0aqupmUGrw/DZp8zMAKtxWqs+8TGQTDthAW+4Y8g0hoLYSTEIHwvbkBUCspWo4Qr0MXj86P1Gsu5DbQ4Fs23fbajPuZqRHTyYzeANvxnma9mm30CwLD6blnKOLa+xRVd6eeuHu+Hp+F8hl5xSJS0Bcse4K0ZKccDD6sm4KSX2vaNQeQQ45fIDYLRUXYckGifqu7nJLwHILEenxue10841IHleA==

I've been trying to figure this out in Python and this is as close as I've got:

import hashlib
import base64
import hmac

key = '''
private key
'''
msg = '''
{"body to hash"}
'''
print((base64.b64encode(hmac.new(bytearray(key.upper(), "ASCII") , bytearray(msg,"ASCII") , hashlib.sha512).digest())).decode("ASCII"))

However, this returns a hash that looks like this: 9dfgSdEzLEdGHze/SrYCSGVHurEvFabe3YgBSqKowxHb96UznenFFoeTDjx2dlk2B53qq9ISKVwv+xFBXMBePQ==

If there is anyone who can help, it would be greatly appreciated!

3
  • The C# code uses Encoding.Default to convert the message to bytes. The python code uses "ASCII". Are you sure that Encoding.Default is ASCII? Commented Sep 1, 2021 at 19:54
  • @Dominik, do you know or can you point me in the right direction in how I create a signature in Python? Commented Sep 1, 2021 at 19:57
  • @SpencerBench, I'm not 100% sure that Encoding.Default uses ascii, but I've also tried utf-8. Commented Sep 1, 2021 at 19:59

1 Answer 1

1

I hope this gets you into the right direction.

Please note that I'm generating a random private key, just in order to have a working example. This generates output of 344 characters, just like you'd expect:

b'Q9X/TOUJwJI101e5pXSg75zhNXk0VKA+cbbFJLF1OttmVIT3Pfa6xcpSvjE3ErW6SBKFlK+e/3AxNRr6h1TXhQQEtbMl9GmcBgJnvKOOWN8Ev40NvO+Ut7MEiHXDWZ888AXYe4sNMc61oUlj1d7wop0mZIL/+hMTQi9zVldfxWB/5PLLe/J3T451Ldj3XH5lL2AnesoCDgQTwWS20iCX8SE5JGh0pAJj+rImgyPinqvbf49uBq1DByKrAI5SVtB/6IoWpztyKKOfjy7QtcM71/CIWBrTAUi7TBXUlJ2si9s8alm+NUNKZZkWNS5SIkcZQrWPz+no6J9CGJt+JAXTRw=='

Please be careful with your input, when comparing results. In your sample the .NET appliaction contains no new line at the end of your payload - however the python application does.

from Crypto.Cipher import DES
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA512, SHA384, SHA256, SHA, MD5
from Crypto import Random
from base64 import b64encode, b64decode


def sign(message, priv_key):
    signer = PKCS1_v1_5.new(priv_key)        
    digest = SHA512.new()        
    digest.update(message)

    return signer.sign(digest)
    
# pem prefix and suffix in case it's not provided in your key
#pem_prefix = '-----BEGIN RSA PRIVATE KEY-----\n'
#pem_suffix = '\n-----END RSA PRIVATE KEY-----'

# here should be your key (without pem prefix and suffix)
#key = "thisisyourkey"

# assemble 
#key = '{}{}{}'.format(pem_prefix, key, pem_suffix)

# generate private key rsa object
#private = RSA.importKey(key)

# the following 3 lines are just to showcase a working key
random_generator = Random.new().read
keysize = 2048
private = RSA.generate(keysize, random_generator)

# payload to sign
msg = '''
{"body to hash"}
'''.encode("utf-8")

signature = sign(msg, private)
print(b64encode(signature))
Sign up to request clarification or add additional context in comments.

3 Comments

The output isn't identical, but that could be like you said, some inconsistencies with the input. Thank you so much for the help, this gets me much closer!
I have one more question for you. How would I format the python input to match the .NET input? It seems like the strings handle new lines differently. If I change both to a one line string, the output is the same, but if I have something like this in .NET: ``` return @"{ ""key"":""value"", ""key2"":{ ""subkey"": true, }"; ``` How would I format the Python string so the output is the same? Edit, the formatting doesn't work in the comments, so I updated the input in the .NET code in the main post.
Well - if your input comes from "somewhere", why is it not consistent? Just make sure that your input is the same, no matter which application is used. With multiline strings intendation there are often issues with intendation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.