2

I am working on an application for Android and iOS which relies on RSA shared keys to create and verify data signatures. So far, I have had success creating and exporting public keys from each platform which are uploaded to a server. The server is able to read the keys from either platform. The keys are exported as an X.509 encoded PEM file. The server reads the keys created by Android using a PKIX encoding. The server reads the keys from iOS using a PKCS encoding. (This was discovered through trial and error).

Android keys are created using an RSA/OAEP/SHA-1 scheme. Keys created by an Android client can be read and loaded by other Android clients, the server, and iOS clients. Android exports and loads keys using a combination of Java and Bouncy Castle. (Written below in C#).

Android Key Export

            X509EncodedKeySpec spec = new X509EncodedKeySpec(_pkey.GetEncoded());
            string pem = string.Empty;

            using (TextWriter writer = new StringWriter())
            {
                PemWriter pw = new PemWriter(writer);

                pw.WriteObject(new PemObject("PUBLIC KEY", spec.GetEncoded()));
                pem = writer.ToString();
            }

            return pem;

Android Key Import

            PemObject spki = new PemReader(new StringReader(cert)).ReadPemObject();
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(spki.Content);
            KeyFactory kf = KeyFactory.GetInstance(KeyProperties.KeyAlgorithmRsa);
            
            _pkey = kf.GeneratePublic(keySpec);

Using these routines, Android produces a PEM encoded public key which resembles the following:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkmaJzUb1E3fl2kPgi/Vi
8wZHKDuGw6RAzO8F9S86qwtwPKFzksKJ8O9AXrRsxe0YCrWX9SGNVSuP4XUoKQtA
QVUfpgsmsAejjgXn+CaS/MKmhFJSZ0f9vegkMmLiQJp3u0+ggXI6fBOCK48n865D
XAsWw/TzhFCWRsmaywwkvxyymRj68pDyU75sjyaefQbbXfrLEzD2YaZVG8rHtO/o
wddPbtKZRMwD1C4nDptNfdMPmlAWk08L5eQQFYBn0EWbDfkyDTi5DYrfGTwRzuo4
HKorltiyP/LfgSL9a/nEh40tJg3Dw2E61RJtEyhA1hJHhM1Uk84Fncii9KHkJYPM
KwIDAQAB
-----END PUBLIC KEY-----

Testing such a key using openssl (openssl rsa -pubin -in temp/author.pkey) produces output such as the following:

writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkmaJzUb1E3fl2kPgi/Vi
8wZHKDuGw6RAzO8F9S86qwtwPKFzksKJ8O9AXrRsxe0YCrWX9SGNVSuP4XUoKQtA
QVUfpgsmsAejjgXn+CaS/MKmhFJSZ0f9vegkMmLiQJp3u0+ggXI6fBOCK48n865D
XAsWw/TzhFCWRsmaywwkvxyymRj68pDyU75sjyaefQbbXfrLEzD2YaZVG8rHtO/o
wddPbtKZRMwD1C4nDptNfdMPmlAWk08L5eQQFYBn0EWbDfkyDTi5DYrfGTwRzuo4
HKorltiyP/LfgSL9a/nEh40tJg3Dw2E61RJtEyhA1hJHhM1Uk84Fncii9KHkJYPM
KwIDAQAB
-----END PUBLIC KEY-----

iOS keys are created using an RSA/OAEP/SHA-256 scheme. Keys created by an iOS client can be read and loaded by other iOS clients and the server, but not by Android clients. iOS exports and loads keys using a combination of Objective-C and Bouncy Castle. (Written below in C#).

iOS Key Export

            var bytes = lookupKey()?.GetPublicKey().GetExternalRepresentation().ToArray();
            string pem = string.Empty;

            using (TextWriter writer = new StringWriter())
            {
                PemWriter pw = new PemWriter(writer);

                pw.WriteObject(new PemObject("PUBLIC KEY", bytes));
                pem = writer.ToString();

            }

            return pem;

iOS Key Import

                byte[] encoded = Convert.FromBase64String(pem);
                string cert = Encoding.UTF8.GetString(encoded);
                string[] parts = cert.Split('\n');
                string b64 = string.Join(string.Empty, parts.Skip(1).Take(parts.Length - 1));
                NSData data = new NSData(b64, NSDataBase64DecodingOptions.IgnoreUnknownCharacters);
            
                _pkey = SecKey.Create(data, SecKeyType.RSA, SecKeyClass.Public, 2048, null, out NSError err);

Using these routines, iOS produces a PEM encoded public key which resembles the following:

-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEApg8H5D/HZE7RcvV1QkZ//HE+D9PNjxZw1Gur5S3l8QN731S28d3l
iMuVmf/4FCDvJ5GLoQKmbvslrL/s2Fc6WzS4cr84psg4dCxZVYrKY65vMMdTIoni
Z0jE6oFnl8+j3wuOA6bAid4wpSK6vIwo+u3N48csQfdj0wEG7QDsNhbj+btGVU8G
/pS3RRcSRlhxhpz+1LALjd0xZ6iXrAn85r39dsEZohIuOC4/+A5EIpUUw12k4+Dy
3qxI7nDiLpJySeE8NV4K9Fk0+4flmMDBNkLMJBT8Vt6DGHtc4ImBnhBLxFt/oSq9
8iW3TwxrRptBZUkdU2U+QXigLUYcj/T+MQIDAQAB
-----END PUBLIC KEY-----

Testing such a key using openssl (openssl rsa -pubin -in temp/author.pkey) produces output such as the following:

unable to load Public Key
140230339794240:error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag:../crypto/asn1/tasn_dec.c:1149:
140230339794240:error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error:../crypto/asn1/tasn_dec.c:309:Type=X509_ALGOR
140230339794240:error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error:../crypto/asn1/tasn_dec.c:646:Field=algor, Type=X509_PUBKEY
140230339794240:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:../crypto/pem/pem_oth.c:33:

I noticed how the public key export from iOS has one line fewer than the key exported from Android. I believe that iOS exports public keys using an encoding slightly different from an X.509 certificate. I found a document regarding PKCS #1 specifications from a separate SO thread, but the thread offered no direction on what to do with the information provided. https://www.rfc-editor.org/rfc/rfc3447#page-6

I have very little experience with cryptography in general, so I may be using some of the terms incorrectly and am clearly missing something which may be obvious to someone with more experience. I do not know if there is something that can be done to convert a PKCS encoded key into a PKIX one (or if that is even my problem). Can anyone offer any clues as to what I should try here?

s.napier
  • 49
  • 1
  • 4
  • There's a some terminology that is being slightly misused here, which is adding to the confusion. *PKIX encoding vs PKCS encoding* I think what you mean here is SubjectPublicKeyInfo (SPKI) public key encoding versus PKCS#1 RSA public key encoding. *...created using an RSA/OAEP/SHA-1 scheme...* OAEP/SHA-1 are references to a padding scheme used during RSA encryption. Key creation/generation does not involve OAEP or SHA-1. It's just RSA. – President James K. Polk Oct 08 '21 at 22:56
  • 2
    *...iOS produces a PEM encoded public key which resembles the following....* That key is a PKCS#1 RSA public key, and it's PEM encoding **should NOT** begin with `-----BEGIN PUBLIC KEY-----`, it should begin with `-----BEGIN RSA PUBLIC KEY-----`. Is there no way for the iOS code to be modified to produce an SPKI public key? Because that's what you need. If not then Bouncycastle can parse the key if you give the correct header. – President James K. Polk Oct 08 '21 at 23:00
  • 1
    Ah, I don't have to put it into the ASN.1 decoder anymore. Shame on that API though, they don't specify anything about key encoding. Well it is Microsoft, what can you expect. They don't document, period. – Maarten Bodewes Oct 08 '21 at 23:06
  • Thank you @PresidentJamesK.Polk. I think it is pretty clear that my nomenclature could use a little work. You are correct, I think, regarding the padding scheme. I have tried manually replacing `-----BEGIN PUBLIC KEY-----` with `-----BEGIN RSA PUBLIC KEY-----` in the outputted file, and testing that with openssl. I get the following error message. `unable to load Public Key 140698173051520:error:0909006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:745:Expecting: PUBLIC KEY` Could there be something else I am not understanding? – s.napier Oct 09 '21 at 01:25
  • 1
    For that format (PKCS#1 RSA public key) you need the `-RSAPublicKey_in` option instead of the `-pubin` optin. – President James K. Polk Oct 09 '21 at 01:41
  • From what I can tell, there is very little that I can do to "direct" iOS in how keys are output. I was only able to find one published method for exporting the public key. Someone more familiar with Swift or Objective C may have another suggestion. I have not found any references online so far. – s.napier Oct 09 '21 at 01:43
  • @PresidentJamesK.Polk That's brilliant, thank you! Using -RSAPublicKey_in, I was able to validate the key with openssl. That at least assures that the export is good. Hopefully, all that is needed is to alter the header and footer of the PEM when the file is written. With any luck, Bouncy Castle will be able to pick up on this change in Android, and everything will start to work smoothly. I will update you after testing. Thank you tremendously! – s.napier Oct 09 '21 at 01:48
  • Unfortunately, changing the header and footer of the file was not enough to make Java / Bouncy Castle understand the key's specification. The comment regarding SPKI did tip me off to something, though. I found this thread https://stackoverflow.com/questions/45131935/export-an-elliptic-curve-key-from-ios-to-work-with-openssl, which in turn led me to this whitepaper https://www.rfc-editor.org/rfc/rfc5480. It looks like I still have more research to do. I think I am getting closer to a solution, though. – s.napier Oct 09 '21 at 19:11

1 Answers1

0

I am confident that there is an iOS-oriented fix for what I am attempting. I got a little lost digging through the resource materials regarding ASN.1 and the various public key encodings. The methods published in the Swift API / SDK documents regarding public key encryption do not make it apparent how to get at the N or E of the public key, though I am certain they could be derived if I understood the technology a little better.

For the time being, I have resorted to leveraging a server process to do a one-way translation from PKCS-1 to SPKI for keys created by iOS clients. GoLang has fully published and well documented routines for handling this, and they are remarkably simple to implement. (The following has been simplified for brevity).

pBlock, _ := pem.Decode(pkcs1) // input ASN.1 PKCS-1 in PEM format
key, _ := x509.ParsePKCS1PublicKey(pBlock.Bytes) // get RSA public key

pkix, _ := x509.MarshalPKIXPublicKey(key) // encode the RSA key using SPKI

pkixPem := pem.EncodeToMemory(&pem.Block{  
    Type:  "PUBLIC KEY",
    Bytes: pkix,
}) // export ASN.1 PKIX in PEM format

Involving the server is not ideal, but it is a one-way conversion, and produces a PEM that both clients (Android and iOS) are able to use from then on. I was able to implement the logic at a point in which iOS is already calling the server using an RSA public key, so no additional round trips were needed.

All-in-all, this is a usable solution for my scenario. It would be nice to know of a routine that can be used in native Swift / Objective-C, at least academically.

Thank you to everyone for your input. It helped me in getting as far as I did.

s.napier
  • 49
  • 1
  • 4