Search code examples
javac#x509certificateverify

Authenticating signature created on c# by public key on java


I am new to verification and certificates etc .. I am facing an issue , that I need to sign a message on c# then verify the signature on java , the issue I ma facing that I am unable to load the public key on java on a (PublicKey) object using the Base64 string generated on c# , I used the following code to generate the private and public key on c# side

 CspParameters cspParams = new CspParameters { ProviderType = 1 };
            cspParams.KeyContainerName = "MyKeyContainer";
            RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(1024);

            string publicKey = Convert.ToBase64String(rsaProvider.ExportCspBlob(false));
            string privateKey = Convert.ToBase64String(rsaProvider.ExportCspBlob(true));
            System.Diagnostics.Debug.WriteLine("pub:" + publicKey);
            System.Diagnostics.Debug.WriteLine("pri:" + privateKey);

            Console.WriteLine("Key added to container: \n  {0}", rsaProvider.ToXmlString(true));

then I used the following code to create a public key on Java side :

    X509EncodedKeySpec specc = new X509EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64("BgIAAACkAABSU0ExAAQAAA......"));
            KeyFactory xx = KeyFactory .getInstance("RSA");
            PublicKey ssx=  xx.generatePublic(specc);

note that I copied the base64 public key string from the c# console . When I try to run the code on java side the I get the following exception :

java.security.spec.InvalidKeySpecException: Inappropriate key specification: invalid key format
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(Unknown Source)

I need to find a way to generate private and public key on c# (and generate a .cer file for the public key) to load it on java side , or find a way to load the base64 public key string into a (Publickey) object on java side . please help !


Solution

  • Option 1: Same data, different format.

    The easiest way to transmit a public RSA key from .NET is to check that the public exponent value is { 01 00 01 } and then send the modulus value. On the receiver side you accept the modulus and assert the public exponent.

    RSAParameters keyParams = rsa.ExportParameters(false);
    
    if (!keyParams.Exponent.SequenceEqual(new byte[] { 0x01, 0x00, 0x01 }))
        throw new InvalidOperationException();
    
    Send(keyParams.Modulus);
    

    Then Creating RSA keys from known parameters in Java says you can straightforwardly recover it on the Java side.

    Option 2: Same format, different parser.

    The next option you have is to keep using the CSP blob, but writing a parser in Java. The data is the result of calling CryptExportKey with PUBLICKEYBLOB, making your data layout as described at https://msdn.microsoft.com/en-us/library/ee442238.aspx and https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx#pub_BLOB.

    In summary:

    A header (which you could decide to skip, or just test it for equal to the fixed value(s) that you expect):

    • A byte, value 0x06 (PUBLICKEYBLOB)
    • A byte, value 0x02 (blob v2)
    • A short, value 0x0000 (reserved)
    • An integer (stored as little-endian) identifying the key as RSA (0x0000A400 or 0x00002400)
    • An integer (stored as little-endian) identifying the next segment as an RSA public key (a bit redundant, but technically a different structure now): 0x31415352

    After all that comes the relevant data:

    • The bit-length of the modulus stored as a little-endian unsigned integer. For your 1024-bit example this will be 1024, aka 0x00000400, aka { 00 04 00 00 }(LE).
    • The public exponent, stored as a little-endian unsigned integer. This is almost always 0x00010001 (aka { 01 00 01 00 }), but since it's there you should respect it.
    • The next bitLen/8 bytes represent the modulus value. Since this is the public key that should be "the rest of the bytes in this array".

    Option 3: Build a certificate

    .NET Framework doesn't have this capability built-in (as of the current version, 4.7). You can P/Invoke to CertCreateSelfSignCertificate, but that would involve quite a lot of change (since RSACryptoServiceProvider won't let you get at the key handle, so you'll have to P/Invoke all of that, too).

    You could "bit bang" out the DER-encoded certificate yourself. While fun, this is hard to get right, and probably not a viable path.

    If you can move to .NET Core, the ability to create certificates has been added to .NET Core 2.0 via the CertificateRequest class. For a very simple certificate from your key:

    var certReq = new CertificateRequest(
        "CN=SubjectCN",
        rsaProvider,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1);
    
    // add any extensions you want. I'm not adding any because I said "simple".
    
    DateTimeOffset now = DateTimeOffset.UtcNow;
    X509Certificate2 cert = certReq.CreateSelfSigned(now, now.AddMinutes(90));
    byte[] xfer = cert.RawData;