-1

I need to replicate the functionality of the following JAVA code that receives a string with the exponent and modulus of a public key to generate a public key with said parameters and encrypt a string:

package snippet;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.Security;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;

public class Snippet {
    public static void main(String ... strings) {
        try {
            // Needed if you don't have this provider
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            //String and received public key example
            String ReceivedString = "1234";
            String publicRSA = "010001|0097152d7034a8b48383d3dba20c43d049";
            
            EncryptFunc(ReceivedString, publicRSA);
            
            //The result obtained from the ReceivedString and the publicRSA is as follows:
            //Result in hex [1234] -> [777786fe162598689a8dc172ed9418cb]

        } catch (Exception ex) {
            System.out.println("Error: " );
            ex.printStackTrace();
        }
    }
    
    public static String EncryptFunc(String ReceivedString, String clavePublica) throws Exception {
        String result = "";
        //We separate the received public string into exponent and modulus
        //We receive it as "exponent|modulus"
        String[] SplitKey = clavePublica.split("\\|");
        KeyFactory keyFactory = KeyFactory.getInstance("RSA","BC");
        RSAPublicKeySpec ks = new RSAPublicKeySpec(new BigInteger(hex2byte(SplitKey[1])), new BigInteger(hex2byte(SplitKey[0])));
        //With these specs, we generate the public key
        RSAPublicKey pubKey = (RSAPublicKey)keyFactory.generatePublic(ks);
        
        //We instantiate the cypher, with the EncryptFunc and the obtained public key
        Cipher cipher= Cipher.getInstance("RSA/None/NoPadding","BC");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        //We reverse the ReceivedString and encrypt it
        String ReceivedStringReverse = reverse(ReceivedString);
        byte[] cipherText2 = cipher.doFinal(ReceivedStringReverse.getBytes("UTF8"));
        result = byte2hex(cipherText2);
        
        System.out.println("result in hex ["+ReceivedString+"] -> ["+result+"]");
                
        return result;
    }
    
    public static byte[] hex2byte(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                 + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
    
    public static String byte2hex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte aByte : bytes) {
            result.append(String.format("%02x", aByte));
            // upper case
            // result.append(String.format("%02X", aByte));
        }
        return result.toString();
    }
    
    public static String reverse(String source) {
        int i, len = source.length();
        StringBuilder dest = new StringBuilder(len);

        for (i = (len - 1); i >= 0; i--){
            dest.append(source.charAt(i));
        }

        return dest.toString();
    }
}

I've tried several approaches with this one, And I have done some searching here, here, here, here and here.

I Managed to create the public key with the given parameters, but the results are always different when I encrypt the string:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace RSACypherTest
{
    public class Program
    {
        public static RSACryptoServiceProvider rsa;
        static void Main(string[] args)
        {
            string str = "1234";
            string publicRSA = "010001|0097152d7034a8b48383d3dba20c43d049";
            string encrypted = "";
            Console.WriteLine("Original text: " + str);
            encrypted = Encrypt(str, publicRSA);
            Console.WriteLine("Encrypted text: " + encrypted);
            Console.ReadLine();
        }
        public static string Encrypt(string str, string PublicRSA)
        {
            string[] Separated = PublicRSA.Split('|');
            RsaKeyParameters pubParameters = MakeKey(Separated[1], Separated[0], false);
            IAsymmetricBlockCipher eng = new Pkcs1Encoding(new RsaEngine());
            eng.Init(true, pubParameters);
            byte[] plaintext = Encoding.UTF8.GetBytes(Reverse(str));
            byte[] encdata = eng.ProcessBlock(plaintext, 0, plaintext.Length);
            return ByteArrayToString(encdata);
        }
        public static string Reverse(string s)
        {
            char[] charArray = s.ToCharArray();
            Array.Reverse(charArray);
            return new string(charArray);
        }
        public static string ByteArrayToString(byte[] ba)
        {
            return BitConverter.ToString(ba).Replace("-", "");
        }
        public static byte[] StringToByteArray(string hex)
        {
            int NumberChars = hex.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            return bytes;
        }
        private static RsaKeyParameters MakeKey(string modulusHexString, string exponentHexString, bool isPrivateKey)
        {
            var modulus = new BigInteger(modulusHexString, 16);
            var exponent = new BigInteger(exponentHexString, 16);
            return new RsaKeyParameters(isPrivateKey, modulus, exponent);
        }
    }
}

I'm trying to use BouncyCastle because it seems to be the most effcient way of dealing with the key generation and everything. Any help concerning this would be very much appreciated.

Thanks in advance.

Aaron Parrilla
  • 522
  • 3
  • 13
  • Are you for sure that your public key modulus is an actual/correct modulus ? With given exponent and modulus I _would_ get a 128 bit RSA Public key but actual implementations of crypto libs do have a minimum of 512 bit key length so even with BC I could not rebuild the public key for encryption. – Michael Fehr Jul 27 '20 at 15:20
  • b.t.w. Your Java example isn't running due to missing utility classes :-( – Michael Fehr Jul 27 '20 at 15:35
  • @MichaelFehr Sorry, I added the missing classes and the info that we use in the example seems to be correct, at least for the time being. I managed to generate a public key in C# (assumingly, with the given parameters) but every time I encrypt, I get a different result. I've read this could be due to padding and the Public key configuration of Microsoft, but this means the code can't be replicated? – Aaron Parrilla Jul 28 '20 at 09:00

2 Answers2

1

This is not the answer to your question but may help you in understanding RSA encryption.

I setup a sample encryption program in C# and used your given public key (converted the BigInteger modulus & exponent to Base64 values and further just wrote the XML-String representation of the public to use this key for encryption. The keylength is good for a length of maximum 5 byte data.

When running the encryption 5 times you will receive different encodedData (here in Base64 encoding) each run. So it's the expected behavior of the RSA encryption.

As C# allows me to "build" a short key it is not possible to generate a fresh keypair of such length and I doubt that Bouncy Castle would do (but here on SO there are many colleagues with a much better understanding of BC :-).

If you would like the program you can use the following external link to the program: https://jdoodle.com/ia/40.

Result:

load a pre created public key publicKeyXML2: lxUtcDSotIOD09uiDEPQSQ==AQAB

encryptedData in Base64: JIFfO7HXCvdi0nSxKb0eLA==
encryptedData in Base64: dvtRw0U0KtT/pDJZW2X0FA==
encryptedData in Base64: CqJJKZevO6jWH6DQ1dnkhQ==
encryptedData in Base64: G7cL6BBwxysItvD/Rg0PuA==
encryptedData in Base64: HcfZJITu/PzN84WgI8yc6g==

code:

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

class RSACSPSample
{

    static void Main()
    {
        try
        {
            //Create byte arrays to hold original, encrypted, and decrypted data.
            byte[] dataToEncrypt = System.Text.Encoding.UTF8.GetBytes("1234");
            byte[] encryptedData;

            //Create a new instance of RSACryptoServiceProvider to generate
            //public and private key data.
            using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
            {
                Console.WriteLine("load a pre created public key");
                string publicKeyXML = "<RSAKeyValue><Modulus>AJcVLXA0qLSDg9PbogxD0Ek=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
                RSA.FromXmlString(publicKeyXML);
                string publicKeyXML2 = RSA.ToXmlString(false);
                Console.WriteLine("publicKeyXML2: " + publicKeyXML2);
                Console.WriteLine();

                //Pass the data to ENCRYPT, the public key information 
                //(using RSACryptoServiceProvider.ExportParameters(false),
                //and a boolean flag specifying no OAEP padding.
                for (int i = 0; i < 5; i++)
                {
                    encryptedData = RSAEncrypt(dataToEncrypt, RSA.ExportParameters(false), false);
                    string encryptedDataBase64 = Convert.ToBase64String(encryptedData);
                    Console.WriteLine("encryptedData in Base64: " + encryptedDataBase64);
                }

            }
        }
        catch (ArgumentNullException)
        {
            //Catch this exception in case the encryption did
            //not succeed.
            Console.WriteLine("Encryption failed.");
        }
    }

    public static byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
    {
        try
        {
            byte[] encryptedData;
            //Create a new instance of RSACryptoServiceProvider.
            using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
            {

                //Import the RSA Key information. This only needs
                //toinclude the public key information.
                RSA.ImportParameters(RSAKeyInfo);

                //Encrypt the passed byte array and specify OAEP padding.  
                //OAEP padding is only available on Microsoft Windows XP or
                //later.  
                encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
            }
            return encryptedData;
        }
        //Catch and display a CryptographicException  
        //to the console.
        catch (CryptographicException e)
        {
            Console.WriteLine(e.Message);

            return null;
        }
    }
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
0

While I won't mark my own answer as the correct one, I've found that there's the possibility to recreate the entire functionality of the java code mentioned in my question.

As Michael Fehr mentions in his answer, Its absolutely logical that any encryption method will try to avoid creating repeating or predictable patterns, as this answer perfectly describes.

Since in this particular situation the aim is to replicate the java code functionality, and said functionality revolves around getting the same results when encrypting a string with a given public key, we can use the answer in this post to generate a pice of code like the following:

    private static string EncryptMessage(string str, string publicRSA)
    {
        string[] Separated = publicRSA.Split('|');
        RsaKeyParameters pubParameters = MakeKey(Separated[1], Separated[0], false);

        var eng = new RsaEngine();
        eng.Init(true, pubParameters);

        string x = Reverse(str);
        byte[] plaintext = Encoding.UTF8.GetBytes(x);

        var encdata = ByteArrayToString(eng.ProcessBlock(plaintext, 0, plaintext.Length));

        return encdata;
    }

    private static RsaKeyParameters MakeKey(string modulusHexString, string exponentHexString, bool isPrivateKey)
    {
        byte[] mod = StringToByteArray(modulusHexString);
        byte[] exp = StringToByteArray(exponentHexString);
        var modulus = new BigInteger(mod);
        var exponent = new BigInteger(exp);
        return new RsaKeyParameters(isPrivateKey, modulus, exponent);
    }

To recap:

  1. As Michael Fehr says, it is not only normal but expected of a crypyography engine to NOT generate repeatable/predictable patterns
  2. To deliver on the previous point, they add random "padding" to the messages
  3. It's possible (but not recommended) to use BouncyCastle to generate a No-padding engine, emulating the functionality of Java code such as this Cipher rsa = Cipher.getInstance("RSA/ECB/nopadding");
Aaron Parrilla
  • 522
  • 3
  • 13