1

I'm developing a Xamarin mobile app that uses public/private key encryption, signing and verifying for both iOS and Android. The public key created on iOS and Android is being sent to a .net service endpoint where the service tries to import the public key to verify the signatures.

In Xamarin.iOS I'm creating the public key as below

public async Task<string> CreateRandomKeyPair()
        {
            using (var access = new SecAccessControl(SecAccessible.WhenUnlockedThisDeviceOnly, SecAccessControlCreateFlags.BiometryAny))
            {
                var keyParameters = new SecKeyGenerationParameters
                {
                    KeyType = SecKeyType.RSA,
                    KeySizeInBits = 2048,
                    Label = AppInfo.PackageName,
                    TokenID = SecTokenID.None,
                    PrivateKeyAttrs = new SecKeyParameters
                    {
                        IsPermanent = true,
                        ApplicationTag = NSData.FromString(AppInfo.PackageName, NSStringEncoding.UTF8),
                        AccessControl = access,
                        CanSign = true,
                        CanVerify = true
                    }
                };

                var privateKey = SecKey.CreateRandomKey(keyParameters.Dictionary, out NSError nsError);
                var publicKey = privateKey.GetPublicKey();
                NSData keyData = publicKey.GetExternalRepresentation();
                var publicKeyString = keyData.GetBase64EncodedString(NSDataBase64EncodingOptions.None);
                return publicKeyString;
            }

In Xamarin.Android, I'm creating the public key as below

public async Task<string> CreateRandomKeyPair()
        {
            KeyPairGenerator _keyPairGenerator = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, "AndroidKeyStore");

            _keyPairGenerator.Initialize(new KeyGenParameterSpec.Builder("MyKeys", KeyStorePurpose.Sign | KeyStorePurpose.Verify)
                .SetDigests(KeyProperties.DigestSha256)
                .SetUserAuthenticationRequired(true)
                .SetSignaturePaddings(KeyProperties.SignaturePaddingRsaPkcs1)
                .SetKeySize(2048).Build());
            KeyPair keyPair = _keyPairGenerator.GenerateKeyPair();
            IPublicKey publicKey = keyPair.Public;
            byte[] publicKeyBytes = publicKey.GetEncoded();
            string publicKeyString = Base64.EncodeToString(publicKeyBytes, Base64Flags.NoWrap);
            return publicKeyString;
        }

In a C#.net console app, I'm trying to import the public key as below

using System;
using System.Security.Cryptography;

namespace AndroidPubkey
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var rsa = RSA.Create(2048);
                //Android generated public key
                byte[] pubKey = Convert.FromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwgF3bLq/hnNHi3sQoB5C/xusj23ruksW74fCwHMNXxJpsghfqMbhu1E8p2NwxjSkXzDW+QI7DYc8aX9FJruKnvDGswaYdK7vvd4GZmxmYUNPRK6i8cNq/o9mwsAVpv4MmjImY0fSZLjAxW0SXhAZ6iFclOlWkyFo5DmvovBiF4i3CrWpdyHYoVEujRQJ7Rq0q9JuE2lCSNDTIPQYxC9Slw18r5PYjgIgHYmDJvkNlr7wGDl3vlbtHPpUUmRJXCMiuXW7RCp8vasA/2f12MpyTzgSCj/gGb4sPUVvYNQ1n4hU6r3kyz22EW3ZUy5RPPjRn5Hu2CXHMHOeJaB3ZkBoTwIDAQAB");
                //iOS generated public key
                //byte[] pubKey = Convert.FromBase64String("MIIBCgKCAQEAttcTCPsrCic/jC2PGYQUZ41JVn0SD54ZMj01zq5ik72mq1UsQKVb2pwj2lk4ZkZ+nCWU9qk2DddQ9jemE5lWlBVgzh0udyLXpVKESq3YP6DAWcFb45rERryEuYm6steQ5voo62grwLyi8uYTNRuSlUCGfKd/x3tcHm6Mx46P4zSoKv2ykpW6MTgbaTm6D/6/NNA7Qis5+4B/g2eAWJT/rZh6VRf895EQhMvRA1dtCaHqmv95tSJjaiIlRohO4WYJ8bBWfI1z66pMCWFvig5D7Git+pv/A8xCyupBxhkiJDfKS42npuhAcaRbt/QIKUiDWssrlwKyqcNfJghHY12BBwIDAQAB");
                rsa.ImportRSAPublicKey(pubKey, out int bytes);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

while the public key generated on Xamarin.iOS is successfully importing into C#.net and I'm able to perform verifying a signature, the public key generated on Xamarin.Android is failing to import in C#.net and giving me the exception message, "Unknown format in import." with below stacktrace

at Interop.AppleCrypto.ImportEphemeralKey(ReadOnlySpan 1 keyBlob, Boolean hasPrivateKey) at System.Security.Cryptography.RSAImplementation.RSASecurityTransforms.ImportSubjectPublicKeyInfo(ReadOnlySpan 1 source, Int32& bytesRead) at System.Security.Cryptography.RSAImplementation.RSASecurityTransforms.ImportRSAPublicKey(ReadOnlySpan 1 source, Int32& bytesRead) at AndroidPubkey.Program.Main(String[] args) in /Users/developer/Projects/AndroidPubkey/AndroidPubkey/Program.cs:line 17

Note: I'm running the console app on Mac OS. I see that the public key string created on Android is longer than the one created on iOS.

Please help me in understanding the issue here and also creating RSA keys that are cross-platform compatible.

  • 2
    The Android generated key has the X.509/SPKI format, which must be imported with `RSA.ImportSubjectPublicKeyInfo()`. The iOS generated key has PKCS#1 format, which must be imported with `RSA.ImportRSAPublicKey()`. – Topaco Jul 16 '21 at 18:41
  • @user9014097 thanks for the comment. Does Android always generate the key with X.509/SPKI format or Can I make changes to key generation parameters so that it gives me PKCS#1 format. Is that possible? – kiran kumar Jul 16 '21 at 19:32
  • Java exports the public key as X.509/SPKI. But the key can be converted to PKCS#1 with plain Java or, more comfortable, with BouncyCastle, e.g. [here](https://stackoverflow.com/a/29892431/16317602) and [here](https://stackoverflow.com/a/31689918/16317602). This is also possible under Android. I am not familiar with Xamarin, but since it provides access to Java and Android APIs, it might be possible in an analogous way. – Topaco Jul 16 '21 at 21:22

1 Answers1

0

following @Topaco suggestion from comments, I could fix the issue as below. I'm answering here so that others looking for an answer can benefit from it. I've added a condition on public-key byte array length to be 294 for android else it is imported as iOS generated public-key.

using System;
using System.Security.Cryptography;

namespace AndroidPubkey
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var rsa = RSA.Create(2048);
                 //Android generate public key
                byte[] pubKey = Convert.FromBase64String("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgov1p4sCDlCxRaGq4Am3sDOffwtP4iVdmIzXDqELyA251UBBAbZeyC/wCrVllkNAS644nFHDbkPko2EZWIMY0Rl9t/x37GV2gAnJjQ8HLR/WnxqsHiD83qczCqkHwr/AiT4bdAJyiFMwWn20fXfiiksxP0dtmvzYFz0igONj1/PRuzVnblTDIvgP5Okq1EvKC1UjRV8QDTPqzxFbTzi1YAsL02n1zEsnYH3+96syPHi9TE9r9/vk1h/M+q3p+gS0nwpz5Dz7+DQAvOYMkCsNU/IjU6GZwQNwMfh2s59UuYFppckj9gI8LGlBq2QVCAZj9U9LEGawj4BggOGmiRPXFQIDAQAB");
                //iOS generated public key
                //byte[] pubKey = Convert.FromBase64String("MIIBCgKCAQEAttcTCPsrCic/jC2PGYQUZ41JVn0SD54ZMj01zq5ik72mq1UsQKVb2pwj2lk4ZkZ+nCWU9qk2DddQ9jemE5lWlBVgzh0udyLXpVKESq3YP6DAWcFb45rERryEuYm6steQ5voo62grwLyi8uYTNRuSlUCGfKd/x3tcHm6Mx46P4zSoKv2ykpW6MTgbaTm6D/6/NNA7Qis5+4B/g2eAWJT/rZh6VRf895EQhMvRA1dtCaHqmv95tSJjaiIlRohO4WYJ8bBWfI1z66pMCWFvig5D7Git+pv/A8xCyupBxhkiJDfKS42npuhAcaRbt/QIKUiDWssrlwKyqcNfJghHY12BBwIDAQAB");
                if (pubKey.Length == 294)
                {
                    rsa.ImportSubjectPublicKeyInfo(pubKey, out int bytes);
                }
                else
                {
                    rsa.ImportRSAPublicKey(pubKey, out int bytes);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

  • `ImportSubjectPublicKeyInfo` seems to not be available for .NET STD 2.1 (Xamarin). [doc](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsa.importsubjectpublickeyinfo?view=net-5.0) and also [issue](https://github.com/mono/mono/issues/18842) – glihm Oct 08 '21 at 20:06