Хэш Python base64 с тем же выводом, что и сценарий C#

#python #c# #base64 #sha256

Вопрос:

Я работаю с поставщиком, который владеет API. Чтобы вызвать API, они требуют, чтобы мы хэшировали все тело запроса и добавили его в ключ заголовка дайджеста содержимого. Content-Digest: SHA256=<digest> . Они предоставили нам закрытый ключ RSA вместе с файлом linq LINQPad, написанным на C#. Этот скрипт выводит хэш в кодировке base64, который входит в Дайджест содержимого.

Проблема в том, что я не знаю никакого C#, а приложение, для которого мы собираемся использовать этот API, написано на Python. То, что я ищу, — это способ вывода точно такого же отформатированного хэша в скрипте Python.

Это код C#, который они предоставили:

 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,}
    }";
}

 

И он выводит что-то вроде этого (344 символа):
15vgzyv8Cke3Mkkwc3ryAgDMmY6olRfvgLqNyfhfti2GAfLb6s/vgrWc1p5jlWgQHh37Ir7UYThXldspriBz5NPl BSFIW2dxXTMO2NMpzgc/5fmFN2maJCgwzDP0aqupmUGrw/DZp8zMAKtxWqs 8TGQTDthAW 4Y8g0hoLYSTEIHwvbkBUCspWo4Qr0MXj86P1Gsu5DbQ4Fs23fbajPuZqRHTyYzeANvxnma9mm30CwLD6blnKOLa xRVd6eeuHu Hp F8hl5xSJS0Bcse4K0ZKccDD6sm4KSX2vaNQeQQ45fIDYLRUXYckGifqu7nJLwHILEenxue10841IHleA==

Я пытался разобраться в этом на Python, и это самое близкое, что у меня есть:

 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"))
 

Однако это возвращает хэш, который выглядит следующим образом:
9dfgSdEzLEdGHze/SrYCSGVHurEvFabe3YgBSqKowxHb96UznenFFoeTDjx2dlk2B53qq9ISKVwv xFBXMBePQ==

Если есть кто-нибудь, кто может помочь, буду очень признателен!

Комментарии:

1. Код C# используется Encoding.Default для преобразования сообщения в байты. Код python использует "ASCII" . Вы уверены, что Encoding.Default это ASCII?

2. @Доминик, вы знаете или можете указать мне правильное направление в том, как я создаю подпись на Python?

3. @SpencerBench, я не на 100% уверен в этой кодировке. По умолчанию используется ascii, но я также пробовал utf-8.

Ответ №1:

Я надеюсь, что это приведет вас в правильном направлении.

Пожалуйста, обратите внимание, что я генерирую случайный закрытый ключ, просто для того, чтобы иметь рабочий пример. Это генерирует вывод 344 символов, как и следовало ожидать:

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

Пожалуйста, будьте осторожны с вашими вводными данными при сравнении результатов. В вашем образце то .NET appliaction не содержит новой строки в конце вашей полезной нагрузки, однако приложение python делает это.

 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))
 

Комментарии:

1. Выходные данные не идентичны, но это может быть, как вы сказали, некоторые несоответствия с входными данными. Большое вам спасибо за помощь, это делает меня намного ближе!

2. У меня есть к вам еще один вопрос. Как бы я отформатировал входные данные python, чтобы они соответствовали . ЧИСТЫЙ ввод? Похоже, что строки обрабатывают новые строки по-другому. Если я изменю оба на строку в одну строку, вывод будет одинаковым, но если у меня есть что-то подобное в .NET: « return @»{ «»ключ»»:»»значение»», «»ключ2″»:{ «»подраздел»»: true,}»; ` » Как бы я отформатировал строку Python, чтобы вывод был одинаковым? Отредактируйте, форматирование не работает в комментариях, поэтому я обновил ввод в . ЧИСТЫЙ код в главном посте.

3. Что ж, если ваш вклад исходит «откуда — то», почему он не согласован? Просто убедитесь, что ваши входные данные одинаковы, независимо от того, какое приложение используется. При назначении многострочных строк часто возникают проблемы с назначением.