ADB Public Key Format - c#

I'm trying to implement ADB protocol in C#. When i tried using AUTH(2), the device always says that my signature is wrong by replying AUTH(1) over and over again until i sent AUTH(3) (which will trigger the allow this pc dialog). I have saved the public key to the phone by checking the "Always allow from this PC" option on the device.
I am suspecting that my public key format i sent is wrong because seeing the size my implementation sends vs the original ADB's is different. The public keys example below does not use the same private key.
ADB:
000022: Bulk or Interrupt Transfer (DOWN), 2019-08-16 08:23:37,2615937 +0,0000492 (1. Device: SAMSUNG_Android (Galaxy series, misc. (MTP mode)))
Pipe Handle: Control PipeSend 0x2bd bytes to the device
51 41 41 41 41 45 39 37 36 35 78 52 52 44 4D 45 QAAAAE9765xRRDME
6B 43 47 59 4F 7A 43 63 72 43 68 71 62 53 73 56 kCGYOzCcrChqbSsV
6D 32 4A 35 57 46 58 71 4A 78 59 79 4B 41 63 55 m2J5WFXqJxYyKAcU
2B 51 66 57 6A 57 7A 35 54 78 68 58 32 45 46 43 +QfWjWz5TxhX2EFC
52 4E 4F 38 55 56 4A 4D 68 49 44 33 49 58 72 47 RNO8UVJMhID3IXrG
38 49 48 54 77 41 4F 76 6D 78 2F 33 67 4F 41 73 8IHTwAOvmx/3gOAs
55 4F 53 36 4A 2F 5A 74 43 6C 38 38 35 4E 72 64 UOS6J/ZtCl885Nrd
67 66 30 63 61 33 36 6E 70 51 62 43 54 42 56 45 gf0ca36npQbCTBVE
56 59 7A 54 52 49 65 59 4A 63 69 65 78 30 37 4F VYzTRIeYJciex07O
77 38 38 51 6A 52 4C 53 76 63 53 62 52 56 6D 72 w88QjRLSvcSbRVmr
73 78 47 70 32 4C 7A 77 47 69 6E 77 43 4D 48 46 sxGp2LzwGinwCMHF
45 79 69 64 7A 52 42 2B 73 74 4F 2B 66 67 6D 44 EyidzRB+stO+fgmD
47 64 50 4D 31 51 74 59 47 73 51 74 65 53 76 54 GdPM1QtYGsQteSvT
36 79 56 57 66 73 73 70 72 6C 65 33 49 6B 74 45 6yVWfssprle3IktE
32 53 64 4B 4E 63 62 49 44 57 39 5A 56 67 46 2F 2SdKNcbIDW9ZVgF/
2B 4B 79 6B 4C 69 43 37 77 4F 72 4B 4E 46 4E 49 +KykLiC7wOrKNFNI
61 45 4C 4B 52 50 37 33 33 31 73 30 76 67 75 34 aELKRP7331s0vgu4
57 61 76 6F 39 39 66 79 69 43 66 41 48 65 64 45 Wavo99fyiCfAHedE
6E 42 45 7A 4B 41 50 52 53 42 36 6A 74 39 6D 34 nBEzKAPRSB6jt9m4
4B 67 73 4D 4F 54 48 67 45 37 45 7A 53 6D 42 4C KgsMOTHgE7EzSmBL
72 64 38 51 31 58 44 34 52 75 77 61 6F 76 37 47 rd8Q1XD4Ruwaov7G
46 2B 31 52 78 42 44 35 73 61 38 6B 6F 4B 6E 53 F+1RxBD5sa8koKnS
50 68 6E 75 35 48 78 76 71 57 73 49 78 38 70 54 Phnu5HxvqWsIx8pT
42 59 51 45 79 2B 57 33 51 71 2F 6E 66 32 6B 46 BYQEy+W3Qq/nf2kF
70 41 33 61 6D 45 63 2F 53 71 4E 2B 30 46 73 55 pA3amEc/SqN+0FsU
31 48 71 52 78 64 59 5A 68 68 62 66 58 69 52 70 1HqRxdYZhhbfXiRp
4B 67 74 4E 70 31 50 43 55 2B 46 2F 55 32 2B 67 KgtNp1PCU+F/U2+g
64 58 39 6C 68 38 52 31 6C 59 7A 79 38 2F 4A 4C dX9lh8R1lYzy8/JL
65 5A 6F 4F 37 34 31 47 34 34 54 75 38 4A 69 4F eZoO741G44Tu8JiO
69 59 74 6D 43 32 58 42 39 54 52 51 36 49 74 71 iYtmC2XB9TRQ6Itq
46 30 36 67 4D 53 32 78 71 6C 50 6B 54 58 4F 6D F06gMS2xqlPkTXOm
4E 4B 71 72 31 39 76 4C 79 64 66 34 42 5A 34 62 NKqr19vLydf4BZ4b
57 53 58 4A 71 54 39 66 2B 54 47 39 75 48 79 56 WSXJqT9f+TG9uHyV
31 77 35 6E 68 6F 34 44 7A 68 38 6D 65 73 44 55 1w5nho4Dzh8mesDU
6C 30 2F 71 72 71 48 45 4F 73 57 6C 4B 50 72 57 l0/qrqHEOsWlKPrW
4F 59 2F 5A 42 41 56 4B 4F 6A 6D 34 5A 57 67 5A OY/ZBAVKOjm4ZWgZ
76 71 42 56 61 35 71 57 67 4A 44 68 50 37 37 72 vqBVa5qWgJDhP77r
44 68 47 69 2B 50 34 4C 57 45 35 31 49 6A 44 44 DhGi+P4LWE51IjDD
43 48 6F 59 42 4B 4C 6E 5A 4A 41 42 37 55 39 61 CHoYBKLnZJAB7U9a
44 31 7A 54 4E 52 31 47 42 31 65 58 73 50 35 4D D1zTNR1GB1eXsP5M
77 42 66 70 30 34 4C 5A 47 71 77 70 30 52 36 59 wBfp04LZGqwp0R6Y
4E 31 54 79 4E 4B 5A 47 74 65 71 42 4E 4F 51 37 N1TyNKZGteqBNOQ7
62 5A 59 76 39 46 55 76 45 76 72 47 66 38 64 62 bZYv9FUvEvrGf8db
6D 31 73 69 63 77 45 41 41 51 41 3D 00 m1sicwEAAQA=.
Mine:
000061: Bulk or Interrupt Transfer (DOWN), 2019-08-16 08:27:20,0783520 +0,0001867 (1. Device: SAMSUNG_Android (Galaxy series, misc. (MTP mode)))
Pipe Handle: Control PipeSend 0x15d bytes to the device
41 51 41 42 70 47 65 58 30 4F 38 79 6A 37 63 6B AQABpGeX0O8yj7ck
62 42 68 79 75 75 4B 76 6E 79 4F 59 44 55 49 56 bBhyuuKvnyOYDUIV
31 66 39 6A 59 4B 52 78 78 6B 38 4D 68 39 38 34 1f9jYKRxxk8Mh984
4B 7A 2F 75 46 69 55 61 54 47 68 70 59 6A 6D 2F Kz/uFiUaTGhpYjm/
7A 79 42 4A 75 5A 68 71 6E 6C 43 34 4A 6E 65 62 zyBJuZhqnlC4Jneb
75 31 6B 75 79 48 61 4F 69 38 65 71 34 63 6A 4A u1kuyHaOi8eq4cjJ
4F 79 62 63 5A 2F 4A 7A 56 43 4B 75 61 78 4D 36 OybcZ/JzVCKuaxM6
48 58 47 4B 59 70 39 74 47 63 51 6C 59 6F 36 63 HXGKYp9tGcQlYo6c
64 53 61 53 59 33 50 6D 49 47 38 74 6D 69 56 69 dSaSY3PmIG8tmiVi
61 62 4C 30 35 56 78 70 51 65 50 48 4E 37 32 2F abL05VxpQePHN72/
2B 64 37 75 73 48 36 52 54 41 35 2F 66 58 79 52 +d7usH6RTA5/fXyR
6B 54 78 33 69 2B 73 36 73 34 35 58 65 50 43 77 kTx3i+s6s45XePCw
65 32 5A 71 63 4E 44 2F 5A 61 4B 73 73 43 38 6F e2ZqcND/ZaKssC8o
4C 74 5A 4F 6A 54 35 4E 6D 44 58 34 4E 30 38 49 LtZOjT5NmDX4N08I
6C 49 30 71 61 76 46 31 2F 4F 49 64 38 65 4D 70 lI0qavF1/OId8eMp
35 64 38 4F 71 61 31 48 74 47 70 64 4D 7A 68 43 5d8Oqa1HtGpdMzhC
47 6B 69 49 2F 33 63 77 6A 48 39 71 61 58 39 5A GkiI/3cwjH9qaX9Z
76 4D 38 4F 61 6D 76 6C 76 68 4B 36 44 47 5A 39 vM8OamvlvhK6DGZ9
62 47 74 61 73 50 76 59 78 46 69 51 73 51 6A 46 bGtasPvYxFiQsQjF
6B 56 72 79 75 76 6D 6D 43 74 70 61 32 42 2B 4A kVryuvmmCtpa2B+J
78 6F 38 6F 54 37 6B 70 4B 31 69 6D 6A 45 73 6E xo8oT7kpK1imjEsn
74 7A 6E 44 75 73 49 62 4B 77 3D 3D 00 tznDusIbKw==.
Here is how i generate the key pair and data signing using BouncyCastle library:
public static string GetBase64Key(AsymmetricKeyParameter key)
{
var publicKey = (RsaKeyParameters)key;
using (MemoryStream ms = new MemoryStream())
{
byte[] buff = publicKey.Exponent.ToByteArrayUnsigned();
ms.Write(buff, 0, buff.Length);
buff = publicKey.Modulus.ToByteArrayUnsigned();
ms.Write(buff, 0, buff.Length);
ms.Flush();
return Convert.ToBase64String(ms.ToArray());
}
}
public static AsymmetricCipherKeyPair LoadKeyPair(string file)
{
using (StreamReader reader = new StreamReader(file))
{
PemReader pem = new PemReader(reader);
return pem.ReadObject() as AsymmetricCipherKeyPair;
}
}
public static void SaveKeyPair(AsymmetricCipherKeyPair pair, string file)
{
using (StreamWriter writer = new StreamWriter(file))
{
PemWriter pem = new PemWriter(writer);
pem.WriteObject(pair.Private);
pem.WriteObject(pair.Public);
}
}
public static AsymmetricCipherKeyPair GenerateKeyPair(int keySize)
{
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
SecureRandom secureRandom = new SecureRandom(randomGenerator);
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keySize);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
return keyPairGenerator.GenerateKeyPair();
}
public static byte[] SignDataSHA1(byte[] data, AsymmetricKeyParameter privateKey)
{
ISigner signer = SignerUtilities.GetSigner("SHA1WITHRSA");
signer.Init(true, privateKey);
signer.BlockUpdate(data, 0, data.Length);
return signer.GenerateSignature();
}
And this is how i used those helper functions:
// Initialiizing key pair.
AsymmetricCipherKeyPair pair;
if (File.Exists(RSA_CONTAINER_NAME))
{
pair = CryptoHelper.LoadKeyPair(RSA_CONTAINER_NAME);
}
else
{
pair = CryptoHelper.GenerateKeyPair(RSA_KEY_STRENGTH);
CryptoHelper.SaveKeyPair(pair, RSA_CONTAINER_NAME);
}
// Signing token.
byte[] signedData = CryptoHelper.SignDataSHA1(token, pair.Private);
// Writing data.
Message msg = new Message();
msg.Command = Message.A_AUTH;
msg.Arg0 = 2; // Private key signing.
msg.Data = signedData;
msg.DataLength = signedData.Length;
await SendMessageAsync(device, msg);
// Reading response.
Message reply = await ReadMessageAsync(device);
byte[] replyBody = new byte[reply.DataLength];
await device.ReadAsync(replyBody, 0, replyBody.Length);
if (reply.Command == Message.A_AUTH)
{
// Sending rsa public key.
string pem = CryptoHelper.GetBase64Key(pair.Public) + "\0";
byte[] publicKey = Encoding.ASCII.GetBytes(pem);
msg = new Message();
msg.Command = Message.A_AUTH;
msg.Arg0 = 3; // RSAPUBLICKEY
msg.Data = publicKey;
msg.DataLength = publicKey.Length;
await SendMessageAsync(device, msg);
// Reading response.
reply = await ReadMessageAsync(device);
replyBody = new byte[reply.DataLength];
await device.ReadAsync(replyBody, 0, replyBody.Length);
}
if (reply.Command != Message.A_CNXN)
{
// Something went wrong.
throw new Exception("Authentication error");
}
// Done.
Any help would be highly appreciated.
Note: I have completed the steps until i got the token to sign from the device for brevity. I also have tried PEM format (without including the BEGIN and END lines), didn't work either.

I finally managed to fix my authentication problem. I decided to include pre generated public and private key to my app (not generated automatically anymore). I also installed bouncy castle library for C# to parse private key provided by adb. Public key problem fixed.
Turned out that my signing method is also wrong. Here is the correct one (using bouncy castle):
public static byte[] SignDataSHA1(byte[] data, AsymmetricKeyParameter privateKey)
{
// Converting bouncy castle key to native csp.
RSAParameters rsaParam = DotNetUtilities.ToRSAParameters(privateKey as RsaPrivateCrtKeyParameters);
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParam);
// Signing data.
return rsa.SignHash(data, CryptoConfig.MapNameToOID("SHA1"));
}
}
I basically just tell the signer that the data already hashed and voila, it worked.

Related

Why does this C# TcpClient code not always see a response?

I am using the following code to read a psuedo-HTTP request response. It works sometimes but not always and I do not understand.
Background: I have a device that takes HTTP GET requests and sends a chunked HTTP response. In one case, the response is not a proper chunked HTTP response. It leaves out the null chunk that indicates the end of data. I have fixed that problem in the device, but I am trying to figure out how to read the non-comforming HTTP response. I found code from Create http request using TcpClient that sometimes works and sometimes doesn't and I do not understand why.
If I use the code unaltered, it works fine. If I use it by replacing the "www.bing.com" with my device's IP, "192.1.168.89" in both places the string appears, for example, and change the GET command line to "GET /index.htm HTTP/1.1", it works fine. This version of the command returns a web page that is constructed by the device and sends several TCP buffers (about 1400 bytes in my device) of chunked data.
However, if I change to another command that my device understands, "GET /request.htm?T HTTP/1.1", but returns less than 500 bytes of chunked data, then I never see the response. In fact it never gets past the call to "CopyToAsync(memory)" and I do not understand why. The device sees the request, parses it and sends a proper HTTP response. (I know it is a proper response because I have code that uses HTTPClient to read the response and it sees the response fine. And I see the response data from the device side is exactly the same going out in both cases. I can see the device data because I am writing the device's firmware and can change it to printf() the data being sent out to the TCP routines.)
Anyone have an explanation for why the code below isn't always seeing a response?
private static async Task<string> HttpRequestAsync() {
string result = string.Empty;
using (var tcp = new TcpClient("www.bing.com", 80))
using (var stream = tcp.GetStream())
{
tcp.SendTimeout = 500;
tcp.ReceiveTimeout = 1000;
// Send request headers
var builder = new StringBuilder();
builder.AppendLine("GET /?scope=images&nr=1 HTTP/1.1");
builder.AppendLine("Host: www.bing.com");
//builder.AppendLine("Content-Length: " + data.Length); // only for POST request
builder.AppendLine("Connection: close");
builder.AppendLine();
var header = Encoding.ASCII.GetBytes(builder.ToString());
await stream.WriteAsync(header, 0, header.Length);
// Send payload data if you are POST request
//await stream.WriteAsync(data, 0, data.Length);
// receive data
using (var memory = new MemoryStream())
{
await stream.CopyToAsync(memory);
memory.Position = 0;
var data = memory.ToArray();
var index = BinaryMatch(data, Encoding.ASCII.GetBytes("\r\n\r\n")) + 4;
var headers = Encoding.ASCII.GetString(data, 0, index);
memory.Position = index;
if (headers.IndexOf("Content-Encoding: gzip") > 0)
{
using (GZipStream decompressionStream = new GZipStream(memory, CompressionMode.Decompress))
using (var decompressedMemory = new MemoryStream())
{
decompressionStream.CopyTo(decompressedMemory);
decompressedMemory.Position = 0;
result = Encoding.UTF8.GetString(decompressedMemory.ToArray());
}
}
else
{
result = Encoding.UTF8.GetString(data, index, data.Length - index);
//result = Encoding.GetEncoding("gbk").GetString(data, index, data.Length - index);
}
}
//Debug.WriteLine(result);
return result;
}
}
private static int BinaryMatch(byte[] input, byte[] pattern)
{
int sLen = input.Length - pattern.Length + 1;
for (int i = 0; i < sLen; ++i)
{
bool match = true;
for (int j = 0; j < pattern.Length; ++j)
{
if (input[i + j] != pattern[j])
{
match = false;
break;
}
}
if (match)
{
return i;
}
}
return -1;
}
=====================
Let me edit the function above to show what it is now and maybe clarify things.
static async Task<byte[]> getTcpClientHttpDataRequestAsync(string ipAddress, string request)
{
string result = string.Empty;
List<byte> arrayList = new List<byte>();
using (var tcp = new TcpClient("192.168.1.89", 80))
using (var stream = tcp.GetStream())
using (var memory = new MemoryStream())
{
tcp.SendTimeout = 500;
tcp.ReceiveTimeout = 10000;
tcp.NoDelay = true;
// Send request headers
var builder = new StringBuilder();
builder.AppendLine("GET /request.htm?x01011920000000000001 HTTP/1.1");
builder.AppendLine("Host: 192.168.1.89");
builder.AppendLine("Connection: Close");
builder.AppendLine();
var header = Encoding.ASCII.GetBytes(builder.ToString());
Console.WriteLine("======");
Console.WriteLine(builder.ToString());
Console.WriteLine("======");
await stream.WriteAsync(header, 0, header.Length);
do { } while (stream.DataAvailable == 0);
Console.WriteLine("Data available");
bool done = false;
do
{
int next = stream.ReadByte();
if (next < 0)
{
done = true;
}
else
{
arrayList.Add(Convert.ToByte(next));
}
} while (stream.DataAvailable && !done);
byte[] data = arrayList.ToArray();
return data;
}
}
The GET command is what my device is responding to. If the command starts with 'x' as shown then it responds with a proper HTTP response and the function above reads the data. If it starts with 'd' it is missing the 0 length chunk at the end and the function above never sees any data from the device.
With Wireshark, I am seeing the following responses for the 'x' and 'd' commands.
The 'x' command returns 2 TCP frames with the following data:
0000 1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00 .oe...L`.A?g..E.
0010 00 9c 00 47 00 00 64 06 d2 49 c0 a8 01 59 c0 a8 ...G..d..I...Y..
0020 01 22 00 50 05 5d fc f5 9e 72 ad 75 e3 2c 50 18 .".P.]...r.u.,P.
0030 00 01 a9 cd 00 00 48 54 54 50 2f 31 2e 31 20 32 ......HTTP/1.1 2
0040 30 30 20 4f 4b 0d 0a 43 6f 6e 6e 65 63 74 69 6f 00 OK..Connectio
0050 6e 3a 20 63 6c 6f 73 65 0d 0a 43 6f 6e 74 65 6e n: close..Conten
0060 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 68 74 6d t-Type: text/htm
0070 6c 0d 0a 43 61 63 68 65 2d 43 6f 6e 74 72 6f 6c l..Cache-Control
0080 3a 20 6e 6f 2d 63 61 63 68 65 0d 0a 54 72 61 6e : no-cache..Tran
0090 73 66 65 72 2d 45 6e 63 6f 64 69 6e 67 3a 20 63 sfer-Encoding: c
00a0 68 75 6e 6b 65 64 0d 0a 0d 0a hunked....
0000 1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00 .oe...L`.A?g..E.
0010 00 45 00 48 00 00 64 06 d2 9f c0 a8 01 59 c0 a8 .E.H..d......Y..
0020 01 22 00 50 05 5d fc f5 9e e6 ad 75 e3 2c 50 18 .".P.].....u.,P.
0030 00 01 fc 20 00 00 30 30 31 0d 0a 2b 0d 0a 30 30 ... ..001..+..00
0040 37 0d 0a 01 85 86 00 00 0d 0a 0d 0a 30 30 30 0d 7...........000.
0050 0a 0d 0a ...
By comparison the 'd' command returns data in 2 TCP frames as:
0000 1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00 .oe...L`.A?g..E.
0010 00 9c 00 4e 00 00 64 06 d2 42 c0 a8 01 59 c0 a8 ...N..d..B...Y..
0020 01 22 00 50 05 5e d3 c3 f9 f5 69 cc 6d a3 50 18 .".P.^....i.m.P.
0030 00 01 30 ae 00 00 48 54 54 50 2f 31 2e 31 20 32 ..0...HTTP/1.1 2
0040 30 30 20 4f 4b 0d 0a 43 6f 6e 6e 65 63 74 69 6f 00 OK..Connectio
0050 6e 3a 20 63 6c 6f 73 65 0d 0a 43 6f 6e 74 65 6e n: close..Conten
0060 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 68 74 6d t-Type: text/htm
0070 6c 0d 0a 43 61 63 68 65 2d 43 6f 6e 74 72 6f 6c l..Cache-Control
0080 3a 20 6e 6f 2d 63 61 63 68 65 0d 0a 54 72 61 6e : no-cache..Tran
0090 73 66 65 72 2d 45 6e 63 6f 64 69 6e 67 3a 20 63 sfer-Encoding: c
00a0 68 75 6e 6b 65 64 0d 0a 0d 0a hunked....
0000 1c 6f 65 d3 f0 e2 4c 60 de 41 3f 67 08 00 45 00 .oe...L`.A?g..E.
0010 00 36 00 4f 00 00 64 06 d2 a7 c0 a8 01 59 c0 a8 .6.O..d......Y..
0020 01 22 00 50 05 5e d3 c3 fa 69 69 cc 6d a3 50 18 .".P.^...ii.m.P.
0030 00 01 64 c2 00 00 30 30 37 0d 0a 01 90 91 00 00 ..d...007.......
0040 0d 0a 0d 0a ....
The only discernible differences that I see is that in the second frame of the 'd' command it is missing a 1 byte chunk that is part of our protocol (and shouldn't have any effect on the TCP/HTTP function) and the last 7 bytes of data that the 'x' command provides, which is the 0 length chunk expected for HTTP.
Going back to the code in HttpRequestAsync(), if the 'd' command is sent then the code never sees stream.DataAvailable become true, even though the data has been sent. Why?
await stream.CopyToAsync()
will not complete until
stream.DataAvailable == false
You have indicated to the server, in the headers that you will close the TCP connection when done, but have not done so. The server will eventually close the connection when it thinks you're gone. The server is not obligated to obey your "Connection: close" request and that should be indicated in the headers the server returns.
Before you call stream.CopyToAsync() you should check the headers to determine if what Content-Length has been supplied and pass a buffer length to stream.CopyToAsync() and then call TcpClient.Close()

C# BinaryFormatter bytes orde

I am using binary formatter in order to serialize my object.
I would like to know what is the order of the properties in the serialized byte array (according to properties order in the object class? randomaly?)
And if I can control the order of the bytes according to the props.
For example,
If I serialize the following obj:
public class Human
{
int Age {get;set;}
int Weight {get; set;}
}
If I will serialize it, what is the order of bytes means? (does the first 4 bytes will represent the age, and the next are the weight? and so on.. or the binary formatter set it randomaly)
Why don't you just try it? Lets take your class
[Serializable]
public class Human
{
public int Age {get;set;}
public int Weight {get; set;}
}
And serialize it, then inspect the result by examining the HexDump
var bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
using(var ms = new MemoryStream())
{
bf.Serialize(ms, new Human{ Age = 42, Weight = -1 });
HexDump(ms.ToArray());
}
This will give:
00000 : 00 01 00 00 00 FF FF FF FF 01 00 00 00 00 00 00 .....????.......
00016 : 00 0C 02 00 00 00 43 71 75 65 72 79 5F 6C 68 68 ......Cquery_lhh
00032 : 75 78 68 2C 20 56 65 72 73 69 6F 6E 3D 30 2E 30 uxh, Version=0.0
00048 : 2E 30 2E 30 2C 20 43 75 6C 74 75 72 65 3D 6E 65 .0.0, Culture=ne
00064 : 75 74 72 61 6C 2C 20 50 75 62 6C 69 63 4B 65 79 utral, PublicKey
00080 : 54 6F 6B 65 6E 3D 6E 75 6C 6C 05 01 00 00 00 0F Token=null......
00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 02 UserQuery+Human.
00112 : 00 00 00 14 3C 41 67 65 3E 6B 5F 5F 42 61 63 6B ....<Age>k__Back
00128 : 69 6E 67 46 69 65 6C 64 17 3C 57 65 69 67 68 74 ingField.<Weight
00144 : 3E 6B 5F 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 >k__BackingField
00160 : 00 00 08 08 02 00 00 00 2A 00 00 00 FF FF FF FF ........*...????
00176 : 0B .
That is the convoluted format Hans is talking about. If you squint a bit you recognize an assemblyname, the classname, the fieldnames (kind of) and if you apply the magic offered by jdweng you notice the 4 bytes 2A 00 00 00 which would make 42 (Age) and the next 4 bytes represent -1 (Weight).
Let's add a public field Name as the first field:
[Serializable]
public class Human
{
public string Name;
public int Age {get;set;}
public int Weight {get; set;}
}
and let's look at the changed bytes:
00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 03 UserQuery+Human.
00112 : 00 00 00 04 4E 61 6D 65 14 3C 41 67 65 3E 6B 5F ....Name.<Age>k_
00128 : 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 17 3C 57 _BackingField.<W
00144 : 65 69 67 68 74 3E 6B 5F 5F 42 61 63 6B 69 6E 67 eight>k__Backing
00160 : 46 69 65 6C 64 01 00 00 08 08 02 00 00 00 06 03 Field...........
00176 : 00 00 00 04 54 65 73 74 2A 00 00 00 FE FF FF FF ....Test*...????
00192 : 0B .
That seems to make sense. Let's put that field at the end:
[Serializable]
public class Human
{
public int Age {get;set;}
public int Weight {get; set;}
public string Name;
}
and the result is:
00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 03 UserQuery+Human.
00112 : 00 00 00 04 4E 61 6D 65 14 3C 41 67 65 3E 6B 5F ....Name.<Age>k_
00128 : 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 17 3C 57 _BackingField.<W
00144 : 65 69 67 68 74 3E 6B 5F 5F 42 61 63 6B 69 6E 67 eight>k__Backing
00160 : 46 69 65 6C 64 01 00 00 08 08 02 00 00 00 06 03 Field...........
00176 : 00 00 00 04 54 65 73 74 2A 00 00 00 FE FF FF FF ....Test*...????
00192 : 0B .
No change at all.
One final example to convince you that the output of the BinaryFormatter is an implementation detail and that serializing and deserializing should be left to that class and is not be attempted by other means.
[Serializable]
public class Human
{
public string[] Address;
private string _name;
public int Weight {get; set;} // switched
public int Age {get;set;}
public string Name {get{return _name;} set{_name=value;}}
}
And if we initialize that class as follows:
new Human{ Name ="Test", Age = 42, Weight = -1, Address =new []{"foo","bar"}}
the hexdump will show this:
00096 : 55 73 65 72 51 75 65 72 79 2B 48 75 6D 61 6E 04 UserQuery+Human.
00112 : 00 00 00 07 41 64 64 72 65 73 73 05 5F 6E 61 6D ....Address._nam
00128 : 65 17 3C 57 65 69 67 68 74 3E 6B 5F 5F 42 61 63 e.<Weight>k__Bac
00144 : 6B 69 6E 67 46 69 65 6C 64 14 3C 41 67 65 3E 6B kingField.<Age>k
00160 : 5F 5F 42 61 63 6B 69 6E 67 46 69 65 6C 64 06 01 __BackingField..
00176 : 00 00 08 08 02 00 00 00 09 03 00 00 00 06 04 00 ................
00192 : 00 00 04 54 65 73 74 FF FF FF FF 2A 00 00 00 11 ...Test????*....
00208 : 03 00 00 00 02 00 00 00 06 05 00 00 00 03 66 6F ..............fo
00224 : 6F 06 06 00 00 00 03 62 61 72 0B o......bar.
Notice the order of Address and _name although the actual values of the string[] array are put at the end.
So to answer your question:
I would like to know what is the order of the properties in the serialized byte array (according to properties order in the object class? randomly?)
It is an implementation detail that depends on the type of the field and its order in the class. It's metadata and actual value might be in a different order as well. It is not randomly and it is not the order in the class.
And if I can control the order of the bytes according to the props.
It might seems you can control it to some extent but this is so much of an implementation detail that it is not practical to try to influence it, predict it or rely on it.
Keep in mind that you can only serialize and deserialize the specific version of the class. There is no backward compatibility.
If you need to have strict control over the serialization format use an open standard, like XML, JSON or proto-buf. Or roll your own serializer, leveraging the BinaryWriter as suggested by Peter.

C# RegEx a value between numbers and characters?

Hello I want to search for the first occurrence of a specific string and capture a value between a set length of numbers and characters that change.
Using Nate Barbettini’s https://dotnetfiddle.net/vhkUV5 example I butchered it into doing what I almost need, it won’t compile and from what I’ve seen my RegEx is way off so I defiantly need help with it.
In my example I want to find the first occurrence of the PID value “116c” for chrome.exe and not all three PID values. What’s the best way to get one PID value?
Code:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class Program
{
public static void Main()
{
var output = #"
0a80 6e 6f 74 65 70 61 64 2b 2b 2e 65 78 65 20 20 20 notepad++.exe
0a90 50 49 44 3d 31 64 38 63 7c 30 37 35 36 34 0d 0a PID=1d8c|07564..
0aa0 6a 68 69 5f 73 65 72 76 69 63 65 2e 65 78 20 20 jhi_service.ex
0ab0 50 49 44 3d 31 38 64 34 7c 30 36 33 35 36 0d 0a PID=18d4|06356..
0ac0 4c 4d 53 2e 65 78 65 20 20 20 20 20 20 20 20 20 LMS.exe
0ad0 50 49 44 3d 31 63 36 38 7c 30 37 32 37 32 0d 0a PID=1c68|07272..
0ae0 63 6d 64 2e 65 78 65 20 20 20 20 20 20 20 20 20 cmd.exe
0af0 50 49 44 3d 30 66 37 38 7c 30 33 39 36 30 0d 0a PID=0f78|03960..
0b00 63 6f 6e 68 6f 73 74 2e 65 78 65 20 20 20 20 20 conhost.exe
0b10 50 49 44 3d 30 62 64 30 7c 30 33 30 32 34 0d 0a PID=0bd0|03024..
0b20 76 63 74 69 70 2e 65 78 65 20 20 20 20 20 20 20 vctip.exe
0b30 50 49 44 3d 31 38 30 38 7c 30 36 31 35 32 0d 0a PID=1808|06152..
0b40 63 68 72 6f 6d 65 2e 65 78 65 20 20 20 20 20 20 chrome.exe
0b50 50 49 44 3d 31 31 36 63 7c 30 34 34 36 30 0d 0a PID=116c|04460..
0b60 63 68 72 6f 6d 65 2e 65 78 65 20 20 20 20 20 20 chrome.exe
0b70 50 49 44 3d 31 36 39 34 7c 30 35 37 38 30 0d 0a PID=1694|05780..
0b80 63 68 72 6f 6d 65 2e 65 78 65 20 20 20 20 20 20 chrome.exe
0b90 50 49 44 3d 31 30 62 30 7c 30 34 32 37 32 0d 0a PID=10b0|04272..";
var regex = new Regex(#"chrome.exe[\s].................................................................(.*)........");
var resultList = new List<string>();
foreach (Match match in regex.Matches(output))
{
resultList.Add(match.Groups[1].ToString());
}
var pid = string.Join(", ", resultList);
Console.WriteLine(pid);
}
}
Output:
116c, 1694, 10b0
I’m extremely new so any help or pointers are welcome.
Try this code:
var regex = new Regex("chrome\\.exe\\s*.*PID=(.*)\\|");
var pid = regex.Matches(output)
.Cast<Match>()
.Select(match => match.Groups[1].ToString())
.First();
You can test the regular expression here.

Why Does BouncyCastle Generate Keys Smaller Than .Net's ECDiffieHellmanCng

I'm trying to generate a public key with BouncyCastle (because I'm using Unity and do not have access to ECDiffieHellmanCng), and then I transfer the public key to the server which is using ECDiffieHellmanCng for its key handling.
The server is rejecting my key, for what appears to be because of its small length. ECDiffieHellmanCng generates a public key that is much larger in size compared to that of what Bouncy castle generates.
Is there a way to generate a larger key in bouncy castle?
I tried changing the keybit size, but get an error saying: InvalidParameterException: unknown key size.
Key that BouncyCastle generates:
3059301306072A8648CE3D020106082A8648CE3D03010703420004272F71C1D8B3DC0A7FCB1E9650EEF64EA8F639BEC97D49F8848455C2F5869F7324332D188129C84727F834EE7EE7D8EB7DFC8D40CD4ED219A4FBCEF6C15200F3
Key that ECDiffieHellmanCng generates:
45434B35420000000055CC8665A66A7CDF2E9BF7C69A25B322C72CDBDB1EA8F348050B0A7CF32F9AAD890EA513583367977D5157B2F7FBF55661C9AE2DBAF09B1DC1EA8F193688C3C09501BEE326867ABCB41CA1029F66AF888649F0A6C0674D19670CF32461BA7B3867C1623D68829A7A9A7F1CFC6F5DB99E13C8D960AEF6F5CDAB5B3B62ED6CBEC7222C9F
Here is the code thats generating the bouncy castle key:
const string Algorithm = "ECDH";
const int KeyBitSize = 256;
const int NonceBitSize = 128;
const int MacBitSize = 128;
const int DefaultPrimeProbability = 30;
IAsymmetricCipherKeyPairGenerator aliceKeyGen = GeneratorUtilities.GetKeyPairGenerator(Algorithm);
DHParametersGenerator aliceGenerator = new DHParametersGenerator();
aliceGenerator.Init(KeyBitSize, DefaultPrimeProbability, new SecureRandom());
DHParameters aliceParameters = aliceGenerator.GenerateParameters();
KeyGenerationParameters aliceKGP = new DHKeyGenerationParameters(new SecureRandom(), aliceParameters);
aliceKeyGen.Init(aliceKGP);
AsymmetricCipherKeyPair aliceKeyPair = aliceKeyGen.GenerateKeyPair();
IBasicAgreement aliceKeyAgree = AgreementUtilities.GetBasicAgreement(Algorithm);
aliceKeyAgree.Init(aliceKeyPair.Private);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(aliceKeyPair.Public);
byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPublic = AsString(serializedPublicBytes);
public static string AsString(byte[] bytes, bool keepDashes = false)
{
string hex = BitConverter.ToString(bytes);
return (keepDashes ? hex : hex.Replace("-", ""));
}
I also tried the Mentalis.org DH library, which gives me a larger key, but still just a hair too short.
// create a new DH instance
DiffieHellman dh1 = new DiffieHellmanManaged();
// generate the public key of the first DH instance
byte[] ke1 = dh1.CreateKeyExchange();
string publicKeyString = AsString(ke1);
Key from mentalis.org library:
5F4542F9A8F5636ECCBBAC38238C97ABE757B8F65E25B181BCF41C58985E699EFD6B9606B99F7074717E83F7AC1B5E97DFF6DBA94876F74645F25F0D7FAA1528898C1BD0BB568DF15A98724093766B213769893A05B47E40410B0F395C834F68F57B2EE01852895D912C1D56675A7D8C5367B5E06DE08AAA18CBB4C69F3AE142
If you were to decode the BouncyCastle version you'd see that it is
30 59
SEQUENCE
30 13
SEQUENCE
06 07 2A 86 48 CE 3D 02 01
OBJECT IDENTIFIER 1.2.840.10045.2.1 (id-ecPublicKey)
06 08 2A 86 48 CE 3D 03 01 07
OBJECT IDENTIFIER 1.2.840.10045.3.1.7 (id-secp256r1)
03 42 00
BIT STRING
04 27 2F 71 C1 D8 B3 DC 0A 7F CB 1E 96 50 EE F6
4E A8 F6 39 BE C9 7D 49 F8 84 84 55 C2 F5 86 9F
73 24 33 2D 18 81 29 C8 47 27 F8 34 EE 7E E7 D8
EB 7D FC 8D 40 CD 4E D2 19 A4 FB CE F6 C1 52 00
F3
The BIT STRING's payload is the encoded value of an ecPublicKey whose curve is secp256r1.
Then, following 2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion from the SEC-1 paper we see that it's encoded as
04
Uncompressed Point
X = 27 2F 71 C1 D8 B3 DC 0A 7F CB 1E 96 50 EE F6 4E
A8 F6 39 BE C9 7D 49 F8 84 84 55 C2 F5 86 9F 73
Y = 24 33 2D 18 81 29 C8 47 27 F8 34 EE 7E E7 D8 EB
7D FC 8D 40 CD 4E D2 19 A4 FB CE F6 C1 52 00 F3
Following the logic from the .NET Core import/export ECC feature we see that the equivalent CNG blob is
// BCRYPT_ECDH_PUBLIC_P256_MAGIC (little-endian)
45 43 B4 31
// cbKey=(DWORD)32 (little-endian)
20 00 00 00
// The X bytes (big-endian):
27 2F 71 C1 D8 B3 DC 0A 7F CB 1E 96 50 EE F6 4E
A8 F6 39 BE C9 7D 49 F8 84 84 55 C2 F5 86 9F 73
// The Y bytes (big-endian):
27 2F 71 C1 D8 B3 DC 0A 7F CB 1E 96 50 EE F6 4E
A8 F6 39 BE C9 7D 49 F8 84 84 55 C2 F5 86 9F 73

Reading a ASN.1 DER-encoded RSA Public key

I'm writing an app to get a better understanding of DKIM. The spec says I retrieve a "ASN.1 DER-encoded" public key from the domain TXT record. I can seen the key on "s1024._domainkey.yahoo.com" = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfm".
How can I use this key from .net? The examples I've seen get the key from a X509Certificate2, or an XML file containing the RSAParameters.
CORRECTION: I copy/pasted the key above from the network-tools.com DNS tool, which must've cut it short. nslookup gives me the full key:
s1024._domainkey.yahoo.com text =
"k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfm"
"JiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bTxhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj+XcwIDAQAB; n=A 1024 bit key;"
So abelenky was on the right track with BASE64..
This is the base64-encoding of the DER-encoding of an ASN.1 PublicKeyInfo containing an RSA public key.
Here is a translation:
0 30 159: SEQUENCE {
3 30 13: SEQUENCE {
5 06 9: OBJECT IDENTIFIER '1 2 840 113549 1 1 1'
16 05 0: NULL
: }
18 03 141: BIT STRING 0 unused bits, encapsulates {
22 30 137: SEQUENCE {
25 02 129: INTEGER
: 00 EB 11 E7 B4 46 2E 09 BB 3F 90 7E 25 98 BA 2F
: C4 F5 41 92 5D AB BF D8 FF 0B 8E 74 C3 F1 5E 14
: 9E 7F B6 14 06 55 18 4D E4 2F 6D DB CD EA 14 2D
: 8B F8 3D E9 5E 07 78 1F 98 98 83 24 E2 94 DC DB
: 39 2F 82 89 01 45 07 8C 5C 03 79 BB 74 34 FF AC
: 04 AD 15 29 E4 C0 4C BD 98 AF F4 B7 6D 3F F1 87
: 2F B5 C6 D8 F8 46 47 55 ED F5 71 4E 7E 7A 2D BE
: 2E 75 49 F0 BB 12 B8 57 96 F9 3D D3 8A 8F FF 97
: 73
157 02 3: INTEGER 65537
: }
: }
: }
The OBJECT IDENTIFIER indicates that the following BIT STRING contains the encoding of an RSAPublicKey. The INTEGERs are the modulus and the public exponent.
You can decode the base64 with Convert.FromBase64String, but I don't think .NET has built-in functionality for parsing PublicKeyInfos, so you need to use a 3rd party tool like BouncyCastle.
For anyone interested in this matter I would suggest the System.Security.Cryptography.X509Certificates.PublicKey which can be used to read a DER encoded public key.
That string looks like its some sort of base-64 encoding.
If you convert that string from base-64 to a BLOB, it should then be in valid ASN.1 format.
Try the bouncycastle library, it provides great functionality for such cases.

Categories