6

Just trying to rewrite this c# code to python. Server send public key(modulus, exponent), need to encrypt it with pkcs1 padding.

using (TcpClient client = new TcpClient())
{
    await client.ConnectAsync(ip, port);
    using (NetworkStream stream = client.GetStream())
    {
        await App.SendCmdToServer(stream, "auth", this.Ver.ToString().Split('.', StringSplitOptions.None));
        
        byte[] modulus = new byte[256];
        int num2 = await stream.ReadAsync(modulus, 0, modulus.Length);
        byte[] exponent = new byte[3];
        int num3 = await stream.ReadAsync(exponent, 0, exponent.Length);
        
        this.ServerRsa = RSA.Create();
        this.ServerRsa.ImportParameters(new RSAParameters()
        {
          Modulus = modulus,
          Exponent = exponent
        });

        using (MemoryStream data = new MemoryStream())
        {
          using (BinaryWriter writer = new BinaryWriter((Stream) data))
          {
            writer.Write(string1);
            writer.Write(string2);
            await App.SendDataToServer(stream, this.ServerRsa.Encrypt(data.ToArray(), RSAEncryptionPadding.Pkcs1));
          }
        }
    }
}

Everything works fine, except encrypted result by python. I've tried with rsa and pycryptodome, no luck at all, server returns reject. Tried something like this (rsa)

server_rsa = rsa.newkeys(2048)[0]
server_rsa.n = int.from_bytes(modulus, byteorder='big')
server_rsa.e = int.from_bytes(exponent, byteorder='big')
data = (string1 + string2).encode()
encrypted_data = rsa.encrypt(data, server_rsa)

or this (pycryptodome)

pubkey = construct((int.from_bytes(modulus, 'big'), int.from_bytes(exponent, 'big')))
cipher = PKCS1_v1_5.new(pubkey)
encrypted_data = cipher.encrypt(data)

Is there some special python RSA implementation, that just not working with C#, or vice versa?

2
  • 1
    not sure if I understand where the problem is but one thing to know is that the BinaryWriter, Write method writes length prefixed data to the stream. So the data.ToArray() in C# output is for sure not the same as data = (string1 + string2).encode() in Python? Commented Feb 6 at 20:14
  • Yeah, it was my fault. Server-side app is parcing BinaryWriter result byte sequence Commented Feb 6 at 21:35

1 Answer 1

3

The PyCryptodome is a good choice for cryptographic tasks in Python. The problem is with the data formatting, you are concatenating the strings directly in Python and the BinaryWriter in C# write the lengths of the strings as prefixes.

This code show how you can do that:

import struct

data = b""
data += struct.pack(">I", len(string1.encode('utf-8')))   # add length as big-endian unsigned int
data += string1.encode('utf-8')
data += struct.pack(">I", len(string2.encode('utf-8')))
data += string2.encode('utf-8')


In the code above I encoded the length of the strings as big-endian unsigned int but as was commented by @Topaco the BinaryWriter encodes the length prefix with LEB128. So to replicate BinaryWriter you can do this:

import leb128

data = bytearray()
data += leb128.u.encode(len(string1.encode()))
data += string1.encode()
data += leb128.u.encode(len(string2.encode())) 
data += string2.encode()

I used the leb128 package that can be installed with pip install leb128. But you can create a function to do that encoding

def encode_leb128(number):
    if number == 0:
        return bytearray([0])
    
    result = bytearray()
    while number > 0:
        byte = number & 0x7f
        number >>= 7
        if number > 0:
            byte |= 0x80
        result.append(byte)
    return result

Sign up to request clarification or add additional context in comments.

6 Comments

Niiiiice! You saved my day! I dont even thought about BinaryWriter byte sequence.
Actually, the BinaryWriter encodes the length prefix with LEB128, see e.g. also this library.
@Topaco You are right about BinaryWriter but many network protocols use big-endian byte order because it's easier to implement. The important is to use a consistent byte order across different devices to ensure that data is interpreted correctly.
Your encoding (length as 4 byte int) and LEB128 are quite different. Short strings (<128 bytes) will presumably work, longer ones rather not.
I've implemented BW myself, but have some tip to say. Your code not working in my case, because data result contains 4 bytes in prefix, I need only one. For ex, your code "one" = 3000111110101, but I need "one" = 3111110101, I made it simplier len(i).to_bytes(1, byteorder='big')
|

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.