3

I've seen a lot of questions, none really provide good answers, or if they try, they assume the one asking the question isn't asking what he actually IS asking. And just for the record, I am NOT asking about signing, encryption or PEM files!

In my case, I'll be receiving a small file of encrypted data. It has been encrypted using the private key of an RSA key pair.

The public key I have available is a DER file in the ASN.1 format, it is not a certificate as such, and it is not signed.

In Java, this is easy, in c# it is a nightmare from what I can tell. X509Certificate2 claims "Object not found", when I try to parse it the byte array of the key.

The keys were generated with the following openSSL commands:

>openssl genrsa -out private_key.pem 2048

Generating RSA private key, 2048 bit long modulus
...........+++
....................................................................................................
...........................+++
e is 65537 (0x10001)

>openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt

>openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der
writing RSA key

Edit: The code I'm trying:

private byte[] ReadBytesFromStream(string streamName)
{
    using (Stream stream = this.GetType().Assembly.GetManifestResourceStream(streamName))
    {
        byte[] result = new byte[stream.Length];
        stream.Read(result, 0, (int) stream.Length);
        return result;
    }
}

public void LoadPublicKey()
{
    byte[] key = ReadBytesFromStream("my.namespace.Resources.public_key.der");
    X509Certificate2 myCertificate;
    try
    {
        myCertificate = new X509Certificate2(key);
    }
    catch (Exception e)
    {
        throw new CryptographicException(String.Format("Unable to open key file. {0}", e));
    } 
}

The ReadBytesFromStream method does return the correct byte stream, the public_key.der file is an embedded resource.

A.Grandt
  • 2,242
  • 2
  • 20
  • 21
  • Please show the `C#` code you are trying. – John Alexiou Apr 24 '12 at 14:08
  • Upon exhaustive googeling, I've come to the conclution that using Public RSA keys to decrypt messages have two drawbacks, apart from MS not letting you do it. RSA can only handle contend that is the same size as it's modulus/key. In this case with 2048, that limit is 245 characters. – A.Grandt Apr 25 '12 at 14:53
  • What needs to be done when encrypting with the private key is in fact signing, as anyone potentially can read the content anyway, what you need to do is use Signing to ensure that the content is 1) Coming from the owner of the private key, and 2) is unmodified. – A.Grandt Apr 25 '12 at 15:00
  • To solve my own question, I am planning on going at it a little differently. I'll be Base64 encoding the content (Setup data and license key) sign that, and Base64 encode the binary signature as well, and put those two strings in two lines in a file. On Windows I'll reverse this and use the signature to verify the content. This is basically the best approach, as I can easily get the XML making up the content this way, and have it verified before implementing it. – A.Grandt Apr 25 '12 at 15:00
  • It is common practice to encrypt a variable size message with a symmetric algorithm like triple DES, and then use RSA to transfer the key for the decryption. The message itself is not encrypted with RSA due to the size limitation you mentioned. – John Alexiou Apr 25 '12 at 15:08
  • Here's a related question that deals with just the key, a `CspParameters` and `RSACryptoServiceProvider`: [Load ASN.1/DER encoded RSA keypair in C#](http://stackoverflow.com/q/42175485/608639). I point it out because so many Stack overflow answers tell you do things with certificates or use BouncyCastle when all you are doing is trying to load a key. Also note... the pain point is due to .Net and their use of XML encoding from [RFC 3275](https://www.ietf.org/rfc/rfc3275.txt). .Net does not accept ASN.1/DER or PEM encoded keys. – jww Feb 11 '17 at 14:42

1 Answers1

1

Just to polish this one off. The code below is still a bit messy though, but it does seem to be what I could get to work.

The reason we can't use public keys to decrypt a message in Windows is explained here: http://www.derkeiler.com/Newsgroups/microsoft.public.dotnet.security/2004-05/0270.html

Next, you need the public key modulus and exponent in the c# code, somewhere, be it in a file or embedded is really not the matter here. I do use Base64 to wrap up the binary keys though. I gave up on generating a key that Windows would actually import. Besides, for what I need, the public key will be an embedded resource.

The last problem I had were that the modulus generated from the key weren't working on Windows (c#). This StackOverflow post did hold the key to solve that one, the modulus in Windows can not contain leading zeroes. Android in-app billing Verification of Receipt in Dot Net(C#)

The modulus generated from Java didn't work on c#. Use the stripLeadingZeros code from that post to create a working Public key modulus:

  private static byte[] stripLeadingZeros(byte[] a) {
    int lastZero = -1;
    for (int i = 0; i < a.length; i++) {
      if (a[i] == 0) {
        lastZero = i;
      }
      else {
        break;
      }
    }
    lastZero++;
    byte[] result = new byte[a.length - lastZero];
    System.arraycopy(a, lastZero, result, 0, result.length);
    return result;
  }

For starters, this is how I generated the key files:

$ openssl genrsa -out private_key.pem 2048

$ openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt

$ openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der

The Java Code:

package com.example.signing;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import jcifs.util.Base64;

public class SigningExample {
    private static String PRIVATE_KEY_FILE = "private_key.der";
    private static String PUBLIC_KEY_FILE  = "public_key.der";

    /**
     * @param args
     */
    public static void main(String[] args) {
        SigningExample.sign();
    }

    private static void sign() {
        try {
            String text = new String(SigningExample.loadFromFile("message.xml"));
            String message = Base64.encode(text.getBytes());

            RSAPrivateKey privateKey = PrivateRSAKeyReader.getKeyFile(SigningExample.PRIVATE_KEY_FILE);
            RSAPublicKey publicKey = PublicRSAKeyReader.getKeyFile(SigningExample.PUBLIC_KEY_FILE);

            byte[] modulusBytes = publicKey.getModulus().toByteArray();
            byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();

            modulusBytes = SigningExample.stripLeadingZeros(modulusBytes);

            String modulusB64 = Base64.encode(modulusBytes);
            String exponentB64 = Base64.encode(exponentBytes);

            System.out.println("Copy these two into your c# code:");
            System.out.println("DotNet Modulus : " + modulusB64);
            System.out.println("DotNet Exponent: " + exponentB64);

            // Testing
            byte[] signature = SigningExample.sign(message.getBytes(), privateKey, "SHA1withRSA");

            String signedMessage = message + "\n" + Base64.encode(signature);

            SigningExample.saveToBase64File("message.signed", signedMessage.getBytes());

            System.out.println("\nMessage  :\n" + signedMessage);

            String[] newkey = new String(SigningExample.loadFromBase64File("message.signed")).split("\\n");
            System.out.println("Verified : " + SigningExample.verify(newkey[0].getBytes(), publicKey, "SHA1withRSA", Base64.decode(newkey[1])));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static byte[] stripLeadingZeros(byte[] a) {
        int lastZero = -1;
        for (int i = 0; i < a.length; i++) {
            if (a[i] == 0) {
                lastZero = i;
            } else {
                break;
            }
        }
        lastZero++;
        byte[] result = new byte[a.length - lastZero];
        System.arraycopy(a, lastZero, result, 0, result.length);
        return result;
    }

    private static byte[] sign(byte[] data, PrivateKey prvKey,
            String sigAlg) throws Exception {
        Signature sig = Signature.getInstance(sigAlg);
        sig.initSign(prvKey);
        sig.update(data, 0, data.length);
        return sig.sign();
    }

    private static boolean verify(byte[] data, PublicKey pubKey,
            String sigAlg, byte[] sigbytes) throws Exception {
        Signature sig = Signature.getInstance(sigAlg);
        sig.initVerify(pubKey);
        sig.update(data, 0, data.length);
        return sig.verify(sigbytes);
    }

    public static void saveToBase64File(String fileName, byte[] data) throws IOException {
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileName));

        try {
            String b64 = Base64.encode(data);
            int lineLength = 64;
            int idx = 0;
            int len = b64.length();

            while (len - idx >= lineLength) {
                out.write(b64.substring(idx, idx + lineLength).getBytes());
                out.write('\n');
                idx += lineLength;
            }
            out.write(b64.substring(idx, len).getBytes());
        } catch (Exception e) {
            throw new IOException("Unexpected error", e);
        } finally {
            out.close();
        }
    }

    public static byte[] loadFromBase64File(String fileName) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(fileName));

        try {
            StringBuffer sb = new StringBuffer();

            String buffer;

            while ((buffer = br.readLine()) != null) {
                sb.append(buffer);
            }

            return Base64.decode(sb.toString());
        } catch (Exception e) {
            throw new IOException("Unexpected error", e);
        } finally {
            br.close();
        }
    }

    public static void saveToFile(String fileName, byte[] data) throws IOException {
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileName));

        try {
            out.write(data);
        } catch (Exception e) {
            throw new IOException("Unexpected error", e);
        } finally {
            out.close();
        }
    }

    public static byte[] loadFromFile(String fileName) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(fileName));

        try {
            StringBuffer sb = new StringBuffer();

            String buffer;

            while ((buffer = br.readLine()) != null) {
                sb.append(buffer);
            }

            return sb.toString().getBytes();
        } catch (Exception e) {
            throw new IOException("Unexpected error", e);
        } finally {
            br.close();
        }
    }
}

class PrivateRSAKeyReader {

    public static RSAPrivateKey getKeyFile(String filename) throws Exception {

        File f = new File(filename);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        return PrivateRSAKeyReader.generateKey(keyBytes);
    }

    public static RSAPrivateKey getKey(String base64) throws Exception {
        byte[] keyBytes = Base64.decode(base64);

        return PrivateRSAKeyReader.generateKey(keyBytes);
    }

    private static RSAPrivateKey generateKey(byte[] keyBytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(spec);
    }
}

class PublicRSAKeyReader {

    public static RSAPublicKey getKeyFile(String filename) throws Exception {

        File f = new File(filename);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        return PublicRSAKeyReader.generateKey(keyBytes);
    }

    public static RSAPublicKey getKey(String base64) throws Exception {
        byte[] keyBytes = Base64.decode(base64);

        return PublicRSAKeyReader.generateKey(keyBytes);
    }

    private static RSAPublicKey generateKey(byte[] keyBytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(spec);
    }
}

In c# this code fragment should do the tricks:

String signedMessage = ""; // load Base64 coded the message generated in Java here.

byte[] message = Convert.FromBase64String(signedMessage);
String messageString = Encoding.ASCII.GetString(message);

String[] lines = Regex.Split(messageString, "\n");

byte[] content = Convert.FromBase64String(lines[0]); // first line of the message were the content
byte[] signature = Convert.FromBase64String(lines[1]); // second line were the signature

RSACryptoServiceProvider rsaObj = new RSACryptoServiceProvider(2048);

//Create a new instance of the RSAParameters structure.
RSAParameters rsaPars = new RSAParameters();

rsaPars.Modulus = Convert.FromBase64String("insert your modulus revealed in the Java example here");
rsaPars.Exponent = Convert.FromBase64String("AQAB"); // Exponent. Should always be this.

// Import key parameters into RSA.
rsaObj.ImportParameters(rsaPars);

// verify the message
Console.WriteLine(rsaObj.VerifyData(Encoding.ASCII.GetBytes(lines[0]), "SHA1", signature));

The code is using :

using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

I freely admit that packing the final message into another layer of Base64 is a little overkill and should be changed.

Community
  • 1
  • 1
A.Grandt
  • 2,242
  • 2
  • 20
  • 21