Public key encryption with RSACryptoServiceProvider
Asked Answered
D

2

18

I have been over an article at CodeProject a for a while that explains how to encrypt and decrypt using the RSA provider:

RSA Private Key Encryption

While the old version from 2009 was buggy, the new 2012 version (with System.Numerics.BigInteger support) seems more reliable. What this version lacks though is a way to encrypt with a public key and decrypt using the private key.

So, I tried it myself but get garbage when I decrypt. I'm not familiar with the RSA provider, so I'm in the dark here. It's hard to find more info on how this is supposed to work.

Does anyone see what is wrong with this? The following is ENcryption with a PUBLIC key:

// Add 4 byte padding to the data, and convert to BigInteger struct
BigInteger numData = GetBig( AddPadding( data ) );
RSAParameters rsaParams = rsa.ExportParameters( false );
//BigInteger D = GetBig( rsaParams.D ); //only for private key
BigInteger Exponent = GetBig( rsaParams.Exponent );
BigInteger Modulus = GetBig( rsaParams.Modulus );
BigInteger encData = BigInteger.ModPow( numData, Exponent, Modulus );    
return encData.ToByteArray();

Do I use the big "D" from the provider when I do this? Probably not since it's the public key which doesn't have the "D".

Then the counterpart (DEcrypting using the PRIVATE key):

BigInteger numEncData = new BigInteger( cipherData );

RSAParameters rsaParams = rsa.ExportParameters( true );
BigInteger D = GetBig( rsaParams.D );
//BigInteger Exponent = GetBig( rsaParams.Exponent );
BigInteger Modulus = GetBig( rsaParams.Modulus );

BigInteger decData = BigInteger.ModPow( numEncData, D, Modulus );

byte[] data = decData.ToByteArray();
byte[] result = new byte[ data.Length - 1 ];
Array.Copy( data, result, result.Length );
result = RemovePadding( result );

Array.Reverse( result );
return result;

Do I need the "D" or the Exponent here?

Obviously I need the crypto to work both ways private-public public-private. Any help is much appreciated!

Dogy answered 29/3, 2013 at 11:55 Comment(0)
M
13

here is an example for you:

    public static void rsaPlayground()
    {
        byte[] data = new byte[] { 1, 2, 3, 4, 5 };
        RSACryptoServiceProvider csp = new RSACryptoServiceProvider();//make a new csp with a new keypair
        var pub_key = csp.ExportParameters(false); // export public key
        var priv_key = csp.ExportParameters(true); // export private key

        var encData = csp.Encrypt(data, false); // encrypt with PKCS#1_V1.5 Padding
        var decBytes = MyRSAImpl.plainDecryptPriv(encData, priv_key); //decrypt with own BigInteger based implementation
        var decData = decBytes.SkipWhile(x => x != 0).Skip(1).ToArray();//strip PKCS#1_V1.5 padding

    }

    public class MyRSAImpl 
    {

        private static byte[] rsaOperation(byte[] data, BigInteger exp, BigInteger mod)
        {
            BigInteger bData = new BigInteger(
                data    //our data block
                .Reverse()  //BigInteger has another byte order
                .Concat(new byte[] { 0 }) // append 0 so we are allways handling positive numbers
                .ToArray() // constructor wants an array
            );
            return 
                BigInteger.ModPow(bData, exp, mod) // the RSA operation itself
                .ToByteArray() //make bytes from BigInteger
                .Reverse() // back to "normal" byte order
                .ToArray(); // return as byte array

            /*
             * 
             * A few words on Padding:
             * 
             * you will want to strip padding after decryption or apply before encryption 
             * 
             */
        }

        public static byte[] plainEncryptPriv(byte[] data, RSAParameters key) 
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.privExponent, myKey.Modulus);
        }
        public static byte[] plainEncryptPub(byte[] data, RSAParameters key)
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.pubExponent, myKey.Modulus);
        }
        public static byte[] plainDecryptPriv(byte[] data, RSAParameters key)
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.privExponent, myKey.Modulus);
        }
        public static byte[] plainDecryptPub(byte[] data, RSAParameters key)
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.pubExponent, myKey.Modulus);
        }

    }

    public class MyRSAParams
    {
        public static MyRSAParams fromRSAParameters(RSAParameters key)
        {
            var ret = new MyRSAParams();
            ret.Modulus = new BigInteger(key.Modulus.Reverse().Concat(new byte[] { 0 }).ToArray());
            ret.privExponent = new BigInteger(key.D.Reverse().Concat(new byte[] { 0 }).ToArray());
            ret.pubExponent = new BigInteger(key.Exponent.Reverse().Concat(new byte[] { 0 }).ToArray());

            return ret;
        }
        public BigInteger Modulus;
        public BigInteger privExponent;
        public BigInteger pubExponent;
    }
Mn answered 29/3, 2013 at 15:57 Comment(7)
the 'D' element is not found in public key. it is only set when using a private key for decryption, and thus your example will fail unless the private key is distributed with the application.Oidium
uhm ... it may be a fact that you can't decrypt with the private key, when you don't have it ... yes...what does that have to do with a calculation example that just shows how to do the math behind RSA?Mn
Incorrect, you can decrypt data that was encrypted using the private key using the public key ONLY in the decrypt process. Your example above requires the private key no matter if decrypting using public or private for decryption. Please take a look at your sample, and you will understand. For all, you require BOTH public and private to make any of your decrypt methods work, which is wrong.Oidium
Specifically MyRSAParams.fromRSAParameters looks for private and public exponent. -- the D value.Oidium
sigh ... so ... your point is that the constructor doesn't respect public keys? ...write your own ... this is an example that shows how to do the math and how to read the key components ... no free copy&paste production code with waranties... feel free to poste your own answer ...Mn
True magic! I wonder why this is after quite some googling the only answer to this use case. It seems to be a common problem: provide some information that only I can provide (distributor, safe) and don't trust the other side (client, unsafe). A hash is no good for this, since the client could always get around the check, so the only way would be to have a mechanism to encrypt with private key and decrypt with public key, so that client cannot generate secrets of his own in the future.Fokine
Also, it seems like x == decrypt(encrypt(x)) == encrypt(decrypt(x)). Perfect!Fokine
B
23

Take this encode/decode example

        byte[] toEncryptData = Encoding.ASCII.GetBytes("hello world");

        //Generate keys
        RSACryptoServiceProvider rsaGenKeys = new RSACryptoServiceProvider();
        string privateXml = rsaGenKeys.ToXmlString(true);
        string publicXml = rsaGenKeys.ToXmlString(false);

        //Encode with public key
        RSACryptoServiceProvider rsaPublic = new RSACryptoServiceProvider();
        rsaPublic.FromXmlString(publicXml);
        byte[] encryptedRSA = rsaPublic.Encrypt(toEncryptData, false);
        string EncryptedResult = Encoding.Default.GetString(encryptedRSA);


        //Decode with private key
        var rsaPrivate = new RSACryptoServiceProvider();
        rsaPrivate.FromXmlString(privateXml);
        byte[] decryptedRSA = rsaPrivate.Decrypt(encryptedRSA, false);
        string originalResult = Encoding.Default.GetString(decryptedRSA);
Bearskin answered 20/7, 2013 at 8:38 Comment(7)
The original question was asking for examples of both directions. Your code, shows how to do decryption with private key only, and not how to decrypt using the public key.Oidium
In case of RSA, it is not necessary to decrypt with public key. Except you mean rsa signing.Bearskin
No, I mean decrypt using public key. For example, in php, using openssl_private_encrypt() -- keeping the private key PRIVATE on the server as it should be. Now in the "client", this would need to be decrypted using the public key (example clients would be a program written in c# and "signed" with AuthentiCode). Convenient to have the public cert embedded in the program (part of the sign process). Now, picture this the other way --- epic failure. And rolling a hash that you need to parse to "verify" the data --- so easily hacked.Oidium
I will state this though, it's not your fault that the rest of the world went nuts and feels distributing private keys to client applications is the smart way to go -- or even better, using self-signed private keys on the client side and storing a tonne of public keys on the server --- which are easily mimicked, and are 100% crap for anything of a real secure nature such as licensing software.Oidium
Maybe my answer was hasty. I think RSACryptoServiceProvider has deliberate restriction to avoid possible mistakes with private encrypt/public decrypt. And for signing we can use SignData/VerifyData.Bearskin
Not really sure why Microsoft buggers decrypt when using crypto, but thankfully, it is (fairly) trivial to use BigInteger in combination with crypto export params to make this happen. Granted there is a use for public encryption, private decryption, such as a program you distro'd to clients, that needs to send data to the server. Generally speaking, from the beginning of pub/priv keys, it has been private key for sender of data, and public key for recipient (to decrypt). Not sure when or how people got this confused. Even thawte still mentions this way.Oidium
Why need to generate key again? because we already have private_component and private exponent? Sorry, I am still in the dark.Granger
M
13

here is an example for you:

    public static void rsaPlayground()
    {
        byte[] data = new byte[] { 1, 2, 3, 4, 5 };
        RSACryptoServiceProvider csp = new RSACryptoServiceProvider();//make a new csp with a new keypair
        var pub_key = csp.ExportParameters(false); // export public key
        var priv_key = csp.ExportParameters(true); // export private key

        var encData = csp.Encrypt(data, false); // encrypt with PKCS#1_V1.5 Padding
        var decBytes = MyRSAImpl.plainDecryptPriv(encData, priv_key); //decrypt with own BigInteger based implementation
        var decData = decBytes.SkipWhile(x => x != 0).Skip(1).ToArray();//strip PKCS#1_V1.5 padding

    }

    public class MyRSAImpl 
    {

        private static byte[] rsaOperation(byte[] data, BigInteger exp, BigInteger mod)
        {
            BigInteger bData = new BigInteger(
                data    //our data block
                .Reverse()  //BigInteger has another byte order
                .Concat(new byte[] { 0 }) // append 0 so we are allways handling positive numbers
                .ToArray() // constructor wants an array
            );
            return 
                BigInteger.ModPow(bData, exp, mod) // the RSA operation itself
                .ToByteArray() //make bytes from BigInteger
                .Reverse() // back to "normal" byte order
                .ToArray(); // return as byte array

            /*
             * 
             * A few words on Padding:
             * 
             * you will want to strip padding after decryption or apply before encryption 
             * 
             */
        }

        public static byte[] plainEncryptPriv(byte[] data, RSAParameters key) 
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.privExponent, myKey.Modulus);
        }
        public static byte[] plainEncryptPub(byte[] data, RSAParameters key)
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.pubExponent, myKey.Modulus);
        }
        public static byte[] plainDecryptPriv(byte[] data, RSAParameters key)
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.privExponent, myKey.Modulus);
        }
        public static byte[] plainDecryptPub(byte[] data, RSAParameters key)
        {
            MyRSAParams myKey = MyRSAParams.fromRSAParameters(key);
            return rsaOperation(data, myKey.pubExponent, myKey.Modulus);
        }

    }

    public class MyRSAParams
    {
        public static MyRSAParams fromRSAParameters(RSAParameters key)
        {
            var ret = new MyRSAParams();
            ret.Modulus = new BigInteger(key.Modulus.Reverse().Concat(new byte[] { 0 }).ToArray());
            ret.privExponent = new BigInteger(key.D.Reverse().Concat(new byte[] { 0 }).ToArray());
            ret.pubExponent = new BigInteger(key.Exponent.Reverse().Concat(new byte[] { 0 }).ToArray());

            return ret;
        }
        public BigInteger Modulus;
        public BigInteger privExponent;
        public BigInteger pubExponent;
    }
Mn answered 29/3, 2013 at 15:57 Comment(7)
the 'D' element is not found in public key. it is only set when using a private key for decryption, and thus your example will fail unless the private key is distributed with the application.Oidium
uhm ... it may be a fact that you can't decrypt with the private key, when you don't have it ... yes...what does that have to do with a calculation example that just shows how to do the math behind RSA?Mn
Incorrect, you can decrypt data that was encrypted using the private key using the public key ONLY in the decrypt process. Your example above requires the private key no matter if decrypting using public or private for decryption. Please take a look at your sample, and you will understand. For all, you require BOTH public and private to make any of your decrypt methods work, which is wrong.Oidium
Specifically MyRSAParams.fromRSAParameters looks for private and public exponent. -- the D value.Oidium
sigh ... so ... your point is that the constructor doesn't respect public keys? ...write your own ... this is an example that shows how to do the math and how to read the key components ... no free copy&paste production code with waranties... feel free to poste your own answer ...Mn
True magic! I wonder why this is after quite some googling the only answer to this use case. It seems to be a common problem: provide some information that only I can provide (distributor, safe) and don't trust the other side (client, unsafe). A hash is no good for this, since the client could always get around the check, so the only way would be to have a mechanism to encrypt with private key and decrypt with public key, so that client cannot generate secrets of his own in the future.Fokine
Also, it seems like x == decrypt(encrypt(x)) == encrypt(decrypt(x)). Perfect!Fokine

© 2022 - 2024 — McMap. All rights reserved.