I'm currently trying to create an RSACryptoServiceProvider object solely from a decoded PEM file. After several days of searching, I did manage to wrangle a working solution but it's not one that would be production ready.
In a nutshell, in order to create an RSACryptoServiceProvider object from the bytes that make up the public key in a PEM file, I must create the object specifying the keysize (currently 2048 using SHA256, specifically) and then importing a RSAParameters object with the Exponent and Modulus set. I'm doing this as so;
byte[] publicKeyBytes = Convert.FromBase64String(deserializedPublicKey.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", ""));
// extract the modulus and exponent based on the key data
byte[] exponentData = new byte[3];
byte[] modulusData = new byte[256];
Array.Copy(publicKeyBytes, publicKeyBytes.Length - exponentData.Length, exponentData, 0, exponentData.Length);
Array.Copy(publicKeyBytes, 9, modulusData, 0, modulusData.Length);
// import the public key data (base RSA - works)
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(dwKeySize: 2048);
RSAParameters rsaParam = rsa.ExportParameters(false);
rsaParam.Modulus = modulusData;
rsaParam.Exponent = exponentData;
rsa.ImportParameters(rsaParam);
While this works, it's not viable to assume that the deserializedPublicKey will be exactly 270 bytes and that the modulus I need is found at position 9 and always be 256 bytes in length.
How do I change this to correctly pick out the Modulus and Exponent byte given a set of public key bytes? I've tried to make sense of the ASN.1 standard but with little luck finding what I need from it - the standard(s) are somewhat byzantine.
Any help is appreciated.
You don't need to export existing parameters then re-import over top of them. That forces your machine to generate an RSA key then throw it away. So specifying a keysize to the constructor doesn't matter (if you don't use the key it won't generate one... usually).
The public key file is a DER encoded blob.
-----BEGIN PUBLIC KEY-----
MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggC8rLGlNJ17NaWArDs5mOsV6/kA
7LMpvx91cXoAshmcihjXkbWSt+xSvVry2w07Y18FlXU9/3unyYctv34yJt70SgfK
Vo0QF5ksK0G/5ew1cIJM8fSxWRn+1RP9pWIEryA0otCP8EwsyknRaPoD+i+jL8zT
SEwV8KLlRnx2/HYLVQkCAwEAAQ==
-----END PUBLIC KEY-----
If you take the contents inside the PEM armor, it's a Base64-encoded byte array.
30 81 A0 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01
05 00 03 81 8E 00 30 81 8A 02 81 82 00 BC AC B1
A5 34 9D 7B 35 A5 80 AC 3B 39 98 EB 15 EB F9 00
EC B3 29 BF 1F 75 71 7A 00 B2 19 9C 8A 18 D7 91
B5 92 B7 EC 52 BD 5A F2 DB 0D 3B 63 5F 05 95 75
3D FF 7B A7 C9 87 2D BF 7E 32 26 DE F4 4A 07 CA
56 8D 10 17 99 2C 2B 41 BF E5 EC 35 70 82 4C F1
F4 B1 59 19 FE D5 13 FD A5 62 04 AF 20 34 A2 D0
8F F0 4C 2C CA 49 D1 68 FA 03 FA 2F A3 2F CC D3
48 4C 15 F0 A2 E5 46 7C 76 FC 76 0B 55 09 02 03
01 00 01
ITU-T X.690 defines how to read things encoded under Basic Encoding Rules (BER), Canonical Encoding Rules (CER, which I've never seen explicitly used), and Distinguished Encoding Rules (DER). For the most part CER restricts BER and DER restricts CER, making DER the easiest to read. (ITU-T X.680 describes Abstract Syntax Notation One (ASN.1), which is the grammar that DER is a binary encoding for)
We can do a bit of parsing now:
30
This identifies a SEQUENCE (0x10) with the CONSTRUCTED bit set (0x20), which means that it contains other DER/tagged values. (SEQUENCE is always CONSTRUCTED in DER)
81 A0
This next part is a length. Since it has the high bit set (> 0x7F) the first byte is a "length length" value. It indicates that the true length is encoded in the next 1 byte(s) (lengthLength & 0x7F). Therefore the contents of this SEQUENCE are 160 bytes total. (In this case, "the rest of the data", but the SEQUENCE could have been contained within something else). So let's read the contents:
30 0D
We see our CONSTRUCTED SEQUENCE again (0x30), with a length value of 0x0D, so we have a 13 byte payload.
06 09 2A 86 48 86 F7 0D 01 01 01 05 00
The 06 is OBJECT IDENTIFIER, with a 0x09 byte payload. OID has a slightly non-intuitive encoding, but this one is equivalent to the text representation 1.2.840.113549.1.1.1, which is id-rsaEncryption (http://www.oid-info.com/get/1.2.840.113549.1.1.1).
This still leaves us with two bytes (05 00) which we see is a NULL (with a 0 byte payload, because, well, it's NULL).
So so far we have
SEQUENCE
SEQUENCE
OID 1.2.840.113549.1.1.1
NULL
143 more bytes.
Continuing on:
03 81 8E 00
The 03 means BIT STRING. BIT STRING is encoded as [tag] [length] [number of unused bits]. The unused bits is essentially always zero. So this is a sequence of bits, 0x8E bytes long, and all of them are used.
Technically we should stop there, because CONSTRUCTED wasn't set. But since we happen to know the format of this structure, we treat the value as if the CONSTRUCTED bit was set anyways:
30 81 8A
Here's our friend CONSTRUCTED SEQUENCE again, 0x8A payload bytes, which conveniently corresponds to "everything that's left".
02 81 82
02 identifies an INTEGER, and this one has 0x82 payload bytes:
00 BC AC B1 A5 34 9D 7B 35 A5 80 AC 3B 39 98 EB
15 EB F9 00 EC B3 29 BF 1F 75 71 7A 00 B2 19 9C
8A 18 D7 91 B5 92 B7 EC 52 BD 5A F2 DB 0D 3B 63
5F 05 95 75 3D FF 7B A7 C9 87 2D BF 7E 32 26 DE
F4 4A 07 CA 56 8D 10 17 99 2C 2B 41 BF E5 EC 35
70 82 4C F1 F4 B1 59 19 FE D5 13 FD A5 62 04 AF
20 34 A2 D0 8F F0 4C 2C CA 49 D1 68 FA 03 FA 2F
A3 2F CC D3 48 4C 15 F0 A2 E5 46 7C 76 FC 76 0B
55 09
The leading 0x00 would be a violation of DER, except the next byte has the high bit set. This means that the 0x00 was there to keep the sign bit from being set, making this a positive number.
02 03 01 00 01
Another INTEGER, 3 bytes, value 01 00 01. And we're done.
SEQUENCE
SEQUENCE
OID 1.2.840.113549.1.1.1
NULL
BIT STRING
SEQUENCE
INTEGER 00 BC AC ... 0B 55 09
INTEGER 01 00 01
Harvesting https://www.rfc-editor.org/rfc/rfc5280 we see that this looks a lot like a SubjectPublicKeyInfo structure:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
-- contains a value of the type
-- registered for use with the
-- algorithm object identifier value
Of course, it doesn't know what the RSA public key format is. But the oid-info site told us to check out RFC 2313, where we see
An RSA public key shall have ASN.1 type RSAPublicKey:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e }
So that says that the first INTEGER we read is the Modulus value, and the second is (public)Exponent.
The DER encoding is big-endian, which is also the RSAParameters encoding, but for RSAParameters you need to remove leading 0x00 values from Modulus.
While that isn't as easy as giving you the code to do it, it should be fairly straightforward to write a parser for RSA keys given this information. I'd recommend that you write it as internal static RSAParameters ReadRsaPublicKey(...), and then you just need to do
RSAParameters rsaParameters = ReadRsaPublicKey(...);
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
// things you want to do with the key go here
}
After a lot of time, searching and bartonjs's outstanding response, the code to do this is actually straight forward in the end albeit a little unintuitive to anyone not familiar with the structure of a public key.
TL;DR Basically, if your public key is coming from a non-.NET source, this answer won't help as .NET doesn't provide a way to natively parse a correctly formed PEM. However, if the code that generated the PEM is .NET based, then this answer describes the creation of the public key-only PEM and how to load it back in.
A public key PEM can describe a variety of key types, not just RSA so rather than something like new RSACryptoServiceProvider(pemBytes), we have to parse the PEM based on its structure/syntax, ASN.1, and it then tells us if it's an RSA key (it could be a range of others). Knowing that;
const string rsaOid = "1.2.840.113549.1.1.1"; // found under System.Security.Cryptography.CngLightup.RsaOid but it's marked as private
Oid oid = new Oid(rsaOid);
AsnEncodedData keyValue = new AsnEncodedData(publicKeyBytes); // see question
AsnEncodedData keyParam = new AsnEncodedData(new byte[] { 05, 00 }); // ASN.1 code for NULL
PublicKey pubKeyRdr = new PublicKey(oid, keyParam, keyValue);
var rsaCryptoServiceProvider = (RSACryptoServiceProvider)pubKeyRdr.Key;
NOTE: The above code is not production ready! You'll need to put appropriate guards around the object creation (e.g. the public key might not be RSA), the cast to RSACryptoServiceProvider, etc. The code sample here is short to illustrate that it can be done reasonably cleanly.
How did I get this? Spelunking down through the Cryptographic namespace in ILSpy, I had noticed AsnEncodedData which rang a bell with bartonjs's description. Doing more research, I happened upon this post (look familiar?). This was trying to determine the key size specifically but it creates the necessary RSACryptoServiceProvider along the way.
I'm leaving bartonjs's answer as Accepted, and rightly so. The code above is the result of that research and I'm leaving it here so that others looking to do the same can do so cleanly without any array copying hacks like I had in my OP.
Also, for decoding and testing purposes, you can check if your public key is parsable using the ASN.1 decoder here.
UPDATE
It's on the .NET roadmap to make this easier with ASN.1 parsing for Core >2.1.0.
UPDATE 2
There is now a private implementation in Core .NET 2.1.1. MS is dogfooding until satisfied all is well and we'll (hopefully) see the public API in a subsequent version.
UPDATE 3
As I found out via a question here, the above info is incomplete. What's missing is that the public key being loaded with this solution is one that was generated programmatically from a loaded public+private key pair. Once an RSACryptoServiceProvider is created from a key pair (not just the public key), you can export just the public bytes and encode them as a public key PEM. Doing so will be compatible with the solution here. What's with this?
Load the public + private keypair into an RSACryptoServiceProvider and then export it like so;
var cert = new X509Certificate2(keypairBytes, password,
X509KeyStorageFlags.Exportable
| X509KeyStorageFlags.MachineKeySet);
var partialAsnBlockWithPublicKey = cert.GetPublicKey();
// export bytes to PEM format
var base64Encoded = Convert.ToBase64String(partialAsnBlockWithPublicKey, Base64FormattingOptions.InsertLineBreaks);
var pemHeader = "-----BEGIN PUBLIC KEY-----";
var pemFooter = "-----END PUBLIC KEY-----";
var pemFull = string.Format("{0}\r\n{1}\r\n{2}", pemHeader, base64Encoded, pemFooter);
If you create a PEM from this key, you'll be able to load it back in using the method described earlier. Why is this different? The call to cert.GetPublicKey() will actually return the ASN.1 block structure;
SEQUENCE(2 elem)
INTEGER (2048 bit)
INTEGER 65537
This is actually an incomplete DER blob but one which .NET can decode (full ASN.1 parsing and generation is not supported by .NET at time of writing - https://github.com/dotnet/designs/issues/11).
A correct DER (ASN.1) encoded public key bytes has the following structure;
SEQUENCE(2 elem)
SEQUENCE(2 elem)
OBJECT IDENTIFIER "1.2.840.113549.1.1.1" - rsaEncryption(PKCS #1)
NULL
BIT STRING(1 elem)
SEQUENCE(2 elem)
INTEGER (2048 bit)
INTEGER 65537
OK, so the above gets you a public key (kind of) that you can load. It's ugly and technically incomplete but does use .NET's own output from RSACryptoServiceProvider.GetPublicCert() method. The constructor can use those same bytes when loading just the public key later. Unfortunately, it's not a true, fully-formed PEM. We're still awaiting MS's ASN.1 parser in .NET Core 3.0>.
PEM files are just a serie of base64 encoded DER files and .net allow to import directly DER files, so you can do something like this (I assume you're using just the public key as you state you use it only):
byte[] certBytes = Convert.FromBase64String(deserializedPublicKey
.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", ""));
X509Certificate2 cert = new X509Certificate2(certBytes);
RSACryptoServiceProvider publicKeyProvider =
(RSACryptoServiceProvider)cert.PublicKey.Key;
Related
I'm trying to implement public-private key encryption/decryption for c# server and website. If I encrypt with the public key on PHP I can easily decrypt it using the private key, but when I encrypt the data in C# with the public key I cannot decrypt it with the private key on PHP. Basically, return nothing (or false from var_dump).
C#
var pkey = $"<RSAKeyValue>\r\n\t<Modulus>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsaxzQmdhvuM+yjJJgpVHUr0DW 3XbAr/sWbSdceanVX7IhccU8XCGurrM7rNYWhyIBzD8/RGGnqupfdt3rsNYvMKMO rU6thBEDBUVYJO58olDgW7ACbd/u95/0uRPzwGeqDQUn565iDfMyU6+jjaoGn7+D 5D0Bt8x6mLiu43y0zQIDAQAB</Modulus>\r\n\t<Exponent>AQAB</Exponent>\r\n</RSAKeyValue>";
//Encrypting the text using the public key
cipher = new RSACryptoServiceProvider();
cipher.FromXmlString(pkey);
byte[] data = Encoding.UTF8.GetBytes(message);
byte[] cipherText = cipher.Encrypt(data, false);
var encrypted = Convert.ToBase64String(cipherText);
Console.WriteLine(encrypted);
C# code image
From PHP
$rsa = new RSA();
$rsa->loadKey('MIICXQIBAAKBgQCsaxzQmdhvuM+yjJJgpVHUr0DW3XbAr/sWbSdceanVX7IhccU8/nXCGurrM7rNYWhyIBzD8/RGGnqupfdt3rsNYvMKMOrU6thBEDBUVYJO58olDgW7AC/nbd/u95/0uRPzwGeqDQUn565iDfMyU6+jjaoGn7+D5D0Bt8x6mLiu43y0zQIDAQAB/nAoGAXLIRgczUYew4LcQJhlAbGH9IGrxh9eIm2b3BZaQ7PG4AdJ6X7YWSzjk6PyEC/n0IXCMLlAX6FB50SqULy1PtuJql6HAGP5E1YLUxbgct/+JTC3Txy7jLlBRENyf4Nz/nj0oVvR+//vLLNc/MRl4g8gsCOvtTv4QWDkhvPYq0nLNkGHkCQQDkdiGs103RC5pf/n9/SUMq95bIpAS3jDOrjNKrHjTCYPzchExh1Q3qcyJWWCDgUVP4rUwk5NBYyJ/Glb/nRUDeXuQbAkEAwTOag7BbRguTjjcZeyHrbCPEaimpEClzdEWp6CPxGwHYcTWes74K/ncJrhesgy3ZTqVi4lILdNbpUvq1JjvwpJNwJAVplYxnWOb7EgQURyF0LnNPWpqhlx/nmz9FEbAfYfNZciAky7z1G9RqOB3ozlWWI6nEbEWC/LsfvKZLIWes4R3DBQJBAJq3/nOmtu1Qj4yEdA9JTYivDki6wAhLS+nQhlqTSPY8se5Tdzmw2RiEa4oQx0OdecIzS0/na2MRp16A9dWaeupH0G0CQQDii6l2MeVK4ImBqeEjycRQYRhRxLsiEA7ad0ptGvfC/nir/AowGmQ6jTmkMxp+zSFFCHS8ZHAAcBnZ3Ef5kA8SFY');
$convert = base64_decode("CkR7GLQZFUHLcFnhMGxsLYX/4rbrOM+NaXFLyTED3H+xbTChLsXRBQTGo7Xme44b4+/1c7SWahah/FI4gqFx5aDJ1olUmvUR/cto2X7QjBbIBJiFmXRAokbyyBBZ14v8iFqHwunv6Yc+5k/kO/fMK41mJn8xQms+K79CERkCASB7W3dxAFqh06Ksq/Mi9HwW0RakIsbHsdhu3hGQQy4d4h5J");
var_dump($rsa->decrypt($convert));
Note: Those keys are not real and they are generated for development purpose
Link to testing environment code
Wrong padding scheme
You have to use the exact same padding scheme for this to work.
The false in cipher.Encrypt(data, false) disables OAEP. So, PKCS#1 v1.5 padding will be used for encryption.
You have to set the following to use the same padding scheme:
$rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
Broken keys
Keep in mind that you need to use proper keys for that. You seem to be using keys from here for testing purposes.
If we decode the public key, we get this in Hex:
modulus = 94 FB 56 CB 7C F9 9C 63 BA AB 17 F7 53 6C 0B 6E 26 5B F8 47 F1 55 73 55 84 1F D1 CA 53 48 D0 8D 7F E7 FD D6 FD BF 3F F1 D2 2D ED 7B F6 CA 2C D1 1C C1 48 41 BB E3 D6 3E 73 AD FE A5 54 44 6F 41 51 3E 91 B0 24 45 52 7D 02 C8 68 94 2A 8C B8 E6 8F 4A FE 62 40 1E 66 EB 36 DA D4 07 A7 24 AD 82 C5 EF 96 39 D2 75 0D B0 15 47 50 3C 3C C9 0C A6 9A 23 81 4F 58 1C FC A3 39 B3 5C 53 CF 8E 0B 33
exponent = 01 00 01
We can use this tool to convert this Hex representation to a Base64 representation. Remember, do not transmit your private key if you want to transform it. Your public key for C# would look like this:
<RSAKeyValue><Modulus>lPtWy3z5nGO6qxf3U2wLbiZb+EfxVXNVhB/RylNI0I1/5/3W/b8/8dIt7Xv2yizRHMFIQbvj1j5zrf6lVERvQVE+kbAkRVJ9AsholCqMuOaPSv5iQB5m6zba1AenJK2Cxe+WOdJ1DbAVR1A8PMkMppojgU9YHPyjObNcU8+OCzM=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
Now to your private key. PHP supports multiline strings. Use them! You tried to reduce your private key to a single line and because of that, you made a mistake. Newline characters are \n not /n.
Solution
C# example:
var pkey = "<RSAKeyValue><Modulus>lPtWy3z5nGO6qxf3U2wLbiZb+EfxVXNVhB/RylNI0I1/5/3W/b8/8dIt7Xv2yizRHMFIQbvj1j5zrf6lVERvQVE+kbAkRVJ9AsholCqMuOaPSv5iQB5m6zba1AenJK2Cxe+WOdJ1DbAVR1A8PMkMppojgU9YHPyjObNcU8+OCzM=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
var cipher = new RSACryptoServiceProvider();
cipher.FromXmlString(pkey);
byte[] data = Encoding.UTF8.GetBytes("some message");
byte[] cipherText = cipher.Encrypt(data, false);
var encrypted = Convert.ToBase64String(cipherText);
Console.WriteLine(encrypted);
C# output:
kRyQsT55mQWmjQ0n1GbXOpaoMvng/BDNWk/0S2G4cqsKD1Fm7ktFdQIvnGlY6kbPXY0unwzGHbBpUGrALOITeyPHCRdsGIGVUc2O0lqI74QROloyoShXwX7TyAwzLJB6yiVSM7SDpy/6wJ5XkHLZRJu9KxmFrIBgOB9+b2x6C4A=
PHP example:
use \phpseclib\Crypt\RSA;
$private_key = "-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCU+1bLfPmcY7qrF/dTbAtuJlv4R/FVc1WEH9HKU0jQjX/n/db9vz/x0i3te/bK
LNEcwUhBu+PWPnOt/qVURG9BUT6RsCRFUn0CyGiUKoy45o9K/mJAHmbrNtrUB6ckrYLF75Y50nUN
sBVHUDw8yQymmiOBT1gc/KM5s1xTz44LMwIDAQABAoGAGsiMtAyFu23ac0PdvOuhqm3O/vXAF0Ki
zxwBVURfxM6LfiOOcRPe5RSKGTW+Cl7AQVEmMlsa/LtBhLhQ9LNQ5L/4oTmRhCGiZZEmccAdjKsx
yMeaxkp+ZHvMxMKQNDgYg1CXqrCrpwwUuMUlA26tfxZ3xSFtFyDTaV9mgDQ1IGECQQCkX9Tum7D1
vQTwbhbYnu7eC4eUOaZeGWSEs2csK7U6vfZ3BzUZW/0tPqcSpQqcNxMtY9TiUsNRj1uM6jX3byp7
AkEA6Ab+wvOTNRtES77OAYG9gHGZZ+iXjQJ/6Z7JehN4p41UbDIf9nNUOLUPL9z5V1uOYnl1CWoo
Cw95cdhKXxEAqQJBAIU5Or6tp250ZdVslM27ewSyuY9UblfkIsk/EscFIdzbbDAqwkmsefW6yvTc
mU3lgYCPYlKRG8c19tCuX1ENY5MCQAz37x9YW975Ai01ofAFn2DheJCNOINCI4IcROiU1AaRaKmP
d6fftFJjFFE5iZovXNr2LOt0yn4rxD7vtuBvY9kCQGyty6YCB6qaD7qXPMhLrLbGajAIWd6ETgxv
frK/BJu+buPfDky/g1FhI5R9iMtL1xH0JYLJlaVocU+xSeA9DkY=
-----END RSA PRIVATE KEY-----";
$rsa = new RSA();
var_dump($rsa->loadKey($private_key));
$rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
$convert = base64_decode("kRyQsT55mQWmjQ0n1GbXOpaoMvng/BDNWk/0S2G4cqsKD1Fm7ktFdQIvnGlY6kbPXY0unwzGHbBpUGrALOITeyPHCRdsGIGVUc2O0lqI74QROloyoShXwX7TyAwzLJB6yiVSM7SDpy/6wJ5XkHLZRJu9KxmFrIBgOB9+b2x6C4A=");
var_dump($rsa->decrypt($convert));
PHP output:
bool(true)
string(12) "some message"
I am using openssl 0.9.6g and I have created public/private keypair using RSA_generate_key(). When I save the key with PEM_write_bio_RSAPublicKey, it gives me keys like:
-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----
I have another module in .NET which throws an exception when passed in this key due to its format. It takes format like:
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
How to convert my keys to this format. I am using C++.
In .NET, I am using openssl.net, the code is as follows:
string publicKey = #"-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAKGtqUVBBqcGCRYa7Sb6JVQirOX3hggWP2k7CzEtbF/soOONK510Kefm
omXBrGn2t79ES+hAcCvGSiiVZGuEb3UPiznzbiY150SME5nRC+zU0vvdX64ni0Mu
DeUlGcxM1eWSpozO71at6mxLloEMUg0oSWHfAlS5a4LVaURrJqXfAgMBAAE=
-----END RSA PUBLIC KEY-----";
Encoding enc = Encoding.ASCII;
string text = "hello world";
byte[] msg = enc.GetBytes(text);
CryptoKey key = CryptoKey.FromPublicKey(publicKey, "");
RSA rsa = key.GetRSA();
byte[] res = rsa.PublicEncrypt(msg, RSA.Padding.PKCS1);
The exception comes in line:
CryptoKey key = CryptoKey.FromPublicKey(publicKey, "");
If I use the key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCbhcU+fhYmYLESZQAj1hKBXsNY
si0kYHNkxpP7ftxZiTFowWUVXHzQgkcYiCNnp3pt1eG6Vt0WDzyFYXqUUqugvX41
gkaIrKQw/sRiWEx49krcz7Vxr3dufL6Mg3eK7NyWDGsqwFrx/qVNqdhsHg12PGNx
IMY4UBtxin2A8pd4OwIDAQAB
-----END PUBLIC KEY-----
It works fine.
I was looking around for this issue. I think what I am looking for is "how to convert rsa public key from pkcs#1 to x509 format.
I am using openssl 0.9.6g and I have created public/private keypair
using RSA_generate_key(). It gives me keys like:
-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----
I think what I am looking for is "how to convert rsa public key from pkcs#1 to x509 format.
Yeah, .Net can consume some ASN.1/DER encoded keys, and some PEM encoded keys. The difference is PKCS encoding versus Traditional encoding (OpenSSL calls it "Traditional"). The traditional encoding is the SubjectPublicKeyInfo and it includes the OID and the public key.
So you are looking for either an ASN.1/DER encoding or a PEM encoding that writes SubjectPublicKeyInfo, and not just the public key.
I have another module in .NET which throws an exception when passed in
this key due to its format. It takes format like:
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
In this case, use PEM_write_bio_PUBKEY rather than PEM_write_bio_RSAPublicKey.
PEM_write_bio_PUBKEY writes the SubjectPublicKeyInfo; while PEM_write_bio_RSAPublicKey writes only the public key.
You will need an EVP_PKEY, so use EVP_PKEY_set1_RSA to convert it.
This is a PKCS key in OpenSSL. Its just the public key. You would use PEM_write_RSAPublicKey to write it:
-----BEGIN RSA PUBLIC KEY-----
And this is a Traditional key in OpenSSL. Its the SubjectPublicKeyInfo, and it includes an OID for the algorithm (rsaEncryption) and the public key. You would use PEM_write_bio_PUBKEY to write it:
-----BEGIN PUBLIC KEY-----
Instead of saving the key with PEM_write_RSAPublicKey, you should write out the SubjectPublicKeyInfo structure in ASN.1/DER format with i2d_RSA_PUBKEY_bio; or write it out in PEM format with PEM_write_bio_PUBKEY.
The program below creates a RSA key pair, and then writes out the public key in all the formats. Be sure to save the private key, too.
(And I'm glad you have the C++ tag. unique_ptr makes this exercise so much easier).
#include <memory>
using std::unique_ptr;
#include <openssl/bn.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
#include <cassert>
#define ASSERT assert
using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>;
using EVP_KEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
int main(int argc, char* argv[])
{
int rc;
RSA_ptr rsa(RSA_new(), ::RSA_free);
BN_ptr bn(BN_new(), ::BN_free);
BIO_FILE_ptr pem1(BIO_new_file("rsa-public-1.pem", "w"), ::BIO_free);
BIO_FILE_ptr pem2(BIO_new_file("rsa-public-2.pem", "w"), ::BIO_free);
BIO_FILE_ptr der1(BIO_new_file("rsa-public-1.der", "w"), ::BIO_free);
BIO_FILE_ptr der2(BIO_new_file("rsa-public-2.der", "w"), ::BIO_free);
rc = BN_set_word(bn.get(), RSA_F4);
ASSERT(rc == 1);
// Generate key
rc = RSA_generate_key_ex(rsa.get(), 2048, bn.get(), NULL);
ASSERT(rc == 1);
// Convert RSA key to PKEY
EVP_KEY_ptr pkey(EVP_PKEY_new(), ::EVP_PKEY_free);
rc = EVP_PKEY_set1_RSA(pkey.get(), rsa.get());
ASSERT(rc == 1);
//////////
// Write just the public key in ASN.1/DER
// Load with d2i_RSAPublicKey_bio
rc = i2d_RSAPublicKey_bio(der1.get(), rsa.get());
ASSERT(rc == 1);
// Write just the public key in PEM
// Load with PEM_read_bio_RSAPublicKey
rc = PEM_write_bio_RSAPublicKey(pem1.get(), rsa.get());
ASSERT(rc == 1);
// Write SubjectPublicKeyInfo with OID and public key in ASN.1/DER
// Load with d2i_RSA_PUBKEY_bio
rc = i2d_RSA_PUBKEY_bio(der2.get(), rsa.get());
ASSERT(rc == 1);
// Write SubjectPublicKeyInfo with OID and public key in PEM
// Load with PEM_read_bio_PUBKEY
rc = PEM_write_bio_PUBKEY(pem2.get(), pkey.get());
ASSERT(rc == 1);
return 0;
}
The set1 in EVP_PKEY_set1_RSA bumps the reference count, so you don't get a segfault on a double free.
After executing the program, you get the expected PEM and ASN.1/DER:
$ cat rsa-public-1.pem
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA0cgFv6wEcqoOhPtHdVmX4YFlCwodnSqooeCxFF1XadTS4sZkVJTC
kszHmRqXiXL2NmqnuDQsq6nLd+sNoU5yJJ+W1hwo7UToCyJ/81tS4n6mXvF8oilP
8YudD5QnBdW9LhqttBIN4Gk+Cxun+HG1rSJLGP9yiPPFd7DPiFz0Gd+juyWznWnP
gapDIWEKqANKma3j6b9eopBDWB0XAgU0HQ71MSNbcsPvDd23Ftx0re/7jG53V7Bn
eBy7fQsPmxcn4c74Lz4CvhOr7VdQpeBzNeG2CtkefKWyTk7Vu4FZnAgNd/202XAr
c6GmEQqD2M2zXH/nVZg5oLznECDVQ1x/pwIDAQAB
-----END RSA PUBLIC KEY-----
$ cat rsa-public-2.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0cgFv6wEcqoOhPtHdVmX
4YFlCwodnSqooeCxFF1XadTS4sZkVJTCkszHmRqXiXL2NmqnuDQsq6nLd+sNoU5y
JJ+W1hwo7UToCyJ/81tS4n6mXvF8oilP8YudD5QnBdW9LhqttBIN4Gk+Cxun+HG1
rSJLGP9yiPPFd7DPiFz0Gd+juyWznWnPgapDIWEKqANKma3j6b9eopBDWB0XAgU0
HQ71MSNbcsPvDd23Ftx0re/7jG53V7BneBy7fQsPmxcn4c74Lz4CvhOr7VdQpeBz
NeG2CtkefKWyTk7Vu4FZnAgNd/202XArc6GmEQqD2M2zXH/nVZg5oLznECDVQ1x/
pwIDAQAB
-----END PUBLIC KEY-----
$ dumpasn1 rsa-public-1.der
0 266: SEQUENCE {
4 257: INTEGER
: 00 D1 C8 05 BF AC 04 72 AA 0E 84 FB 47 75 59 97
: E1 81 65 0B 0A 1D 9D 2A A8 A1 E0 B1 14 5D 57 69
: D4 D2 E2 C6 64 54 94 C2 92 CC C7 99 1A 97 89 72
: F6 36 6A A7 B8 34 2C AB A9 CB 77 EB 0D A1 4E 72
: 24 9F 96 D6 1C 28 ED 44 E8 0B 22 7F F3 5B 52 E2
: 7E A6 5E F1 7C A2 29 4F F1 8B 9D 0F 94 27 05 D5
: BD 2E 1A AD B4 12 0D E0 69 3E 0B 1B A7 F8 71 B5
: AD 22 4B 18 FF 72 88 F3 C5 77 B0 CF 88 5C F4 19
: [ Another 129 bytes skipped ]
265 3: INTEGER 65537
: }
0 warnings, 0 errors.
$ dumpasn1 rsa-public-2.der
0 290: SEQUENCE {
4 13: SEQUENCE {
6 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
17 0: NULL
: }
19 271: BIT STRING, encapsulates {
24 266: SEQUENCE {
28 257: INTEGER
: 00 D1 C8 05 BF AC 04 72 AA 0E 84 FB 47 75 59 97
: E1 81 65 0B 0A 1D 9D 2A A8 A1 E0 B1 14 5D 57 69
: D4 D2 E2 C6 64 54 94 C2 92 CC C7 99 1A 97 89 72
: F6 36 6A A7 B8 34 2C AB A9 CB 77 EB 0D A1 4E 72
: 24 9F 96 D6 1C 28 ED 44 E8 0B 22 7F F3 5B 52 E2
: 7E A6 5E F1 7C A2 29 4F F1 8B 9D 0F 94 27 05 D5
: BD 2E 1A AD B4 12 0D E0 69 3E 0B 1B A7 F8 71 B5
: AD 22 4B 18 FF 72 88 F3 C5 77 B0 CF 88 5C F4 19
: [ Another 129 bytes skipped ]
289 3: INTEGER 65537
: }
: }
: }
0 warnings, 0 errors.
Related, see How to generate RSA private key using openssl?. It shows you how to write a RSA public and private key in a number of formats.
I've recently started to work with the telegram api. In the first stage, I made a request to receive auth_key.
This is my c# code :
// auth_key_id in unencrypted message is ZERO
Int64 auth_key_id = 0;
// this is current time stamp that used as message id
Int64 message_id = DateTime.Now.Ticks;
// message type for req_pq is 0x60469778 in big-ending format
byte[] message_type = {120, 151, 70, 96};
// this is data lenght, it determind in run time
Int32 data_lenght;
// data is combined message_type and an int128 bit value called nonce
// nonce create by random
byte[] nonce = new byte[16];
Random rand = new Random(1);
rand.NextBytes(nonce);
// make data
List<byte> dataList = new List<byte>();
dataList.AddRange(message_type);
dataList.AddRange(nonce);
byte[] data = dataList.ToArray();
// make packet
List<byte> packetList = new List<byte>();
packetList.AddRange(BitConverter.GetBytes(auth_key_id));
packetList.AddRange(BitConverter.GetBytes(message_id));
data_lenght = data.Length;
packetList.AddRange(BitConverter.GetBytes(data_lenght));
packetList.AddRange(data);
byte[] packet = packetList.ToArray();
try
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Connect("149.154.167.40", 443);
if (s.Connected)
{
IPEndPoint remote = s.RemoteEndPoint as IPEndPoint;
Console.WriteLine("Connected To : "+remote.Address+":"+remote.Port);
}
int sendLength = s.Send(packet);
Console.WriteLine("Send " +sendLength+" Byte(s)");
byte[] received = new byte[128];
int recLen = s.Receive(received);
Console.WriteLine("Received " + recLen + " Byte(s)");
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
I capture send data by wireshark and get this payload :
0000 10 fe ed f4 8e 97 20 6a 8a 54 28 95 08 00 45 00
0010 00 50 02 a3 40 00 80 06 00 00 c0 a8 01 64 95 9a
0020 a7 28 23 e3 01 bb 0e 4d aa 3b 61 c3 01 b6 50 18
0030 01 01 ff 11 00 00 00 00 00 00 00 00 00 00 af 20
0040 82 e0 b4 08 d3 08 14 00 00 00 78 97 46 60 46 d0
0050 86 82 40 97 e4 a3 95 cf ff 46 69 9c 73 c4
The bold part is my payload and according to telegram documentations it seems correct but i don't get any response from server.
Slight remark: I know nothing about the Telegram API.
First off, you're connecting to 149.154.167.40:443. This is the default port of an HTTPS endpoint, which means your client needs to 'speak' SSL in order to communicate.
Basically what happens is that you connect to the port, and the server waits for a valid SSL handshake. But what you're actually sending is the actual content itself. The server notices you are sending something, but since the amount of bytes received is less than a valid SSL handshake, it keeps on waiting until you send more.
What you want to do instead, is 'speak' this SSL protocol. SSL enabled your sent and/or received content to be encrypted. This can be done by using the SslStream, for example. This is slightly higher level, but it prevents you from implementing an SSL implementation instead.
On the other hand, you can try to connect to port 80, if available, but this disables the encryption. I wouldn't recommend doing so.
Your data.Length is 40 Bytes
You forgot other 12 Bytes :)
You must Capsulate your data before send
-- 04 Bytes -- Length of your data (data.Length + 12) == 52
-- 04 Bytes -- Sequence Number (started from 0) == 0
-- 40 Bytes -- your data
-- 04 Bytes -- CRC32 (Calculate Hash for 48 Bytes)
Now your packet length is 52 Bytes
Now you can send it
After send you will receive Response (96 Bytes)
Now you can go to Step 2 for Authorization
Be sure, I did it a few days ago
Good luck !!!
I basically have the same problem as in this question, but I'm having trouble filling in the apparently trivial parts left out from the accepted answer. I'm doing this in C# with Mono.
I have a CA root certificate, and from that I can get a byte[] holding a public key. I then get an untrusted certificate I need to verify. From what I understand, RSACryptoServiceProvider.VerifyData should do the trick, but first I need to set RSAParameters with the modulus and exponent from the public key.
(Edit: The following repeats some things already apparent from the question I linked to above.)
The piece of code that should do what I need and validate a server's certificate with a root certificate I trust is as follows:
RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
RSAParameters publicKeyParams = new RSAParameters();
publicKeyParams.Modulus = GetPublicKeyModulus();
publicKeyParams.Exponent = GetPublicKeyExponent();
publicKey.ImportParameters(publicKeyParams);
return publicKey.VerifyData(SignedValue(), CryptoConfig.MapNameToOID("SHA1"), Signature());
My problem is the contents of the GetPublicKeyModulus() and GetPublicKeyExponent(). In the accepted answer they are left out as apparently trivial, with just a comment saying the modulus is the value of the first TLV in my public key, and the exponent is the second TLV in the public key. I don't fully understand what that means.
byte[] GetPublicKeyExponent()
{
// The value of the second TLV in your Public Key
}
byte[] GetPublicKeyModulus()
{
// The value of the first TLV in your Public Key
}
byte[] SignedValue()
{
// The first TLV in your Ceritificate
}
byte[] Signature()
{
// The value of the third TLV in your Certificate
}
My question is what do these "first TLV"/"second TLV" exactly mean and how do I get those values from the byte array I have?
From what I understand, TLV stands for type-length-value. So if I have it correct, first bits of the byte array containing the public key have information about how many bits the modulus data is. Using that information I'm supposed to copy that amount of bits from the public key into another array, and set RSAParameters.Modulus to that value. After the modulus in the public key comes the exponent, and I should do the same operation with that. But I can't find information about in how many bits and in what format is the "TL" part of a TLV in the public key data contained.
I found information elsewhere saying the modulus is the first 1024 bits in the public key, and the exponent is the remainder, but that gave me an error about size when copying data between the byte arrays.
At the moment my code that I've been forming based on the accepted answer in the question I linked looks basically like this:
using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
X509Certificate2 trustedRootCertificate = new X509Certificate2(X509Certificate2.CreateFromCertFile(filename));
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
.
.
public bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
.
.
byte[] publicKeyBytes = trustedRootCertificate.GetPublicKey();
byte[] modulusData = // The value of the first TLV in the Public Key??
byte[] exponentData = // The value of the second TLV in the Public Key??
RSAParameters publicKeyParams = new RSAParameters();
publicKeyParams.Modulus = modulusData;
publicKeyParams.Exponent = exponentData;
RSACryptoServiceProvider publicKey = new RSACryptoServiceProvider();
publicKey.ImportParameters(publicKeyParams);
byte[] certificateData = certificate.GetRawCertData();
byte[] signedValue = // The first TLV in the certificate??
byte[] encryptedSignature = // The third TLV in the certificate??
return publicKey.VerifyData(certificateData, HashAlgorithm.Create("SHA1"), encryptedSignature);
}
Or should I be using certificateData (the return value of certificate.GetRawCertData()) in the VerifyData call?
Elsewhere I've found that the encrypted signature part is the last 256 bits in the certificateData, I'm not sure if that's the same as "the third TLV in the certificate". If not, I'd be doing
byte[] certificateData = certificate.GetRawCertData();
byte[] encryptedSignature = new byte[256];
System.Array.Copy(certificateData, certificateData.Length - 256, encryptedSignature, 0, 256);
and then using encryptedSignature as the last parameter in the VerifyData call.
Instead of all this TLV business I've also tried simply
RSACryptoServiceProvider publicKey = trustedRootCertificate.PublicKey.Key as RSACryptoServiceProvider;
as the person in the question I linked to above, but using this the VerifyData call then returned false when I thought it shouldn't. The certificate the application gets from the server has trustedRootCertificate as its root cert, I should be able to do this with that, correct? The root's public key should be able to verify the server's cert?
It's very much possible I just have the very basics of certificate verification wrong from the start. If that's not the case, then my question is how do I get these values
// The value of the second TLV in your Public Key
..
// The value of the first TLV in your Public Key
from the public key of the trusted root cert I have.
Edit: I've also verified that the root certificate loaded from the file and the certificate the app gets from the server are what they're supposed to be by printing out their information, so the problem is not at least in the certificates being wrong. I just don't know how to use them correctly.
Lets say that you encountered with BIT STRING type containing the following Length - Value::
03(T - BIT STRING)
82(Read next 2 Bytes for actual Length)
01 0F(Actual Length of whole BIT STRING)
---BIT STRING Value Starts Here---
00(First Byte Of BIT STRING specifies the number of bits left unused in the final byte of BIT STRING which in this case is 0)
30(T - SEQUENCE)
82(Read next 2 Bytes for actual Length)
01 0A(Actual Length of whole SEQUENCE)
---SEQUENCE Value Starts Here---
02(T - INTEGER)
82(Read next 2 Bytes for actual Length)
01 01(Actual Length of whole INTEGER)
(Value starts from here till "Actual Length of whole INTEGER above")
---INTEGER Value Starts Here---
---Exponent Starts Here---
00 A9 CA B2 A4 CC CD 20 AF 0A
7D 89 AC 87 75 F0 B4 4E F1 DF C1 0F BF 67 61 BD
A3 64 1C DA BB F9 CA 33 AB 84 30 89 58 7E 8C DB
6B DD 36 9E 0F BF D1 EC 78 F2 77 A6 7E 6F 3C BF
93 AF 0D BA 68 F4 6C 94 CA BD 52 2D AB 48 3D F5
B6 D5 5D 5F 1B 02 9F FA 2F 6B 1E A4 F7 A3 9A A6
1A C8 02 E1 7F 4C 52 E3 0E 60 EC 40 1C 7E B9 0D
DE 3F C7 B4 DF 87 BD 5F 7A 6A 31 2E 03 99 81 13
A8 47 20 CE 31 73 0D 57 2D CD 78 34 33 95 12 99
12 B9 DE 68 2F AA E6 E3 C2 8A 8C 2A C3 8B 21 87
66 BD 83 58 57 6F 75 BF 3C AA 26 87 5D CA 10 15
3C 9F 84 EA 54 C1 0A 6E C4 FE C5 4A DD B9 07 11
97 22 7C DB 3E 27 D1 1E 78 EC 9F 31 C9 F1 E6 22
19 DB C4 B3 47 43 9A 1A 5F A0 1E 90 E4 5E F5 EE
7C F1 7D AB 62 01 8F F5 4D 0B DE D0 22 56 A8 95
CD AE 88 76 AE EE BA 0D F3 E4 4D D9 A0 FB 68 A0
AE 14 3B B3 87 C1 BB
-----Exponent Ends Here----
---INTEGER Value Ends Here---
02(T - INTEGER)
03(Actual Length cuz 8th Bit Not Set here, so this byte shows the actual length)
---INTEGER Value Starts Here---
----Mod Starts Here---
01 00 01
---Mod Ends Here---
---INTEGER Value Ends Here---
---SEQUENCE Value Ends Here---
---BIT STRING Value Ends Here---
You might want to read ASN.1 Format
With RSACryptoServiceProvider:
X509Certificate2 certificate = Certificate.CreateFromBase64String(stringCert);
RSACryptoServiceProvider key = certificate.PublicKey.Key as RSACryptoServiceProvider;
if(key != null)
{
RSAParameters parameters = key.ExportParameters(false);
byte[] expoenet = parameters.Exponent;
byte[] modulus = parameters.Modulus;
}
https://social.msdn.microsoft.com/Forums/vstudio/en-US/4282dda1-4803-435a-b63a-65e2d5ac9941/get-modulus-exponent-from-certificate-public-key?forum=netfxbcl
The intention of the code is printing unicode as japanese characters to a file
String s = "\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093";
var Bytes = Encoding.Unicode.GetBytes(s);
string key = Encoding.UTF8.GetString(Encoding.Convert(Encoding.Unicode, Encoding.UTF8, Bytes));
Key is I want to print to file but has the value \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093 Any ideas whats wrong?
What's wrong is that a string (key) has no notion of the bytes used to store it. In this case, your string is:
String:
アップロードするファイルが指定されていません
this is exactly what
"\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093"
means. The expression '\u30a3' looks like 2 Unicode bytes, but it actually just means the character 'ア'.
if you save to a UTF-8 file, the bytes written will be:
UTF-8 bytes
File.WriteAllText("temp.txt", "アップロードするファイルが指定されていません", Encoding.UTF8);
The contents will be (in bytes)
E3 82 A2 E3 83 83 E3 83 97 E3 83 AD E3 83 BC E3 83 89 E3 81 99 E3 82 8B E3 83
95 E3 82 A1 E3 82 A4 E3 83 AB E3 81 8C E6 8C 87 E5 AE 9A E3 81 95 E3 82 8C E3
81 A6 E3 81 84 E3 81 BE E3 81 9B E3 82 93
UTF-16 bytes
File.WriteAllText("temp.txt", "アップロードするファイルが指定されていません", Encoding.Unicode);
The contents will be (in bytes)
A2 30 C3 30 D7 30 ED 30 FC 30 C9 30 59 30 8B 30 D5 30 A1 30 A4 30 EB 30 4C 30
07 63 9A 5B 55 30 8C 30 66 30 44 30 7E 30 5B 30 93 30
One doesn't "convert" Unicode to UTF-8 :-/
Unicode, besides being the parent for the entire set of specifications, can be thought of as "simply" defining code-points/characters and the rules of interaction. The UTF-8 encoding is the specific set of rules to map a sequence of Unicode code-points into a sequence of octets (8-bit bytes).
Try this in LINQPad:
String s = "\u30a2\u30c3\u30d7\u30ed";
s.Dump(); // original string
var bytes = Encoding.UTF8.GetBytes(s);
bytes.Dump(); // see UTF-8 encoded byte sequence
string key = Encoding.UTF8.GetString(bytes);
key.Dump(); // contents restored
The UTF-8 exists only in bytes.
Happy coding.