HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Public key chunked encryption with C# method to decrypt

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
encryptionmethodpublicwithchunkeddecryptkey

Problem

I have written a method in PHP which breaks up larger than the byte size of the key of the certificate used to encrypt, and a method in c# which will decrypt said chunks using the corresponding private key.

What I would like to be reviewed is the C# decrypt() function to improve the performance of decrypting each chunk.

In my tests using StopWatch this part takes about 3 seconds to decrypt all chunks in the test string, and store them back as a string. If I change the line

decrypted = decrypted + Encoding.UTF8.GetString( rsa.Decrypt( buffer, false ) );


to

byte[] tmp = rsa.Decrypt( buffer, false );


... the performance increases a bit to 2.8 seconds.

Generate private and public key pair (run this code)

GenerateKeyPair(true);

// generate public and private key
function GenerateKeyPair($display = false) {
    $config =  array(
        "digest_alg" => "sha512",
        "private_key_bits" => 4096,
        "private_key_type" => OPENSSL_KEYTYPE_RSA
    );

    $ssl = openssl_pkey_new( $config );
    openssl_pkey_export($ssl, $privKey);
    $pubKey = openssl_pkey_get_details($ssl)['key'];

    if($display == true) {
        // display keys in textareas
        echo '
              ' . $privKey . '
              ' . $pubKey . '
        ';
    }

    // return keys as an array
    return array(
        'private' => $privKey,
        'public'  => $pubKey
    );
}


Save the results to private.txt and public.txt

PHP: Encrypt using Public key (public.txt)

```
// replace this line with the one from http://pastebin.com/6Q2Zb3j6
$output = '';

echo encrypt($data, 'public.txt');

// encrypt string using public key
function encrypt($string, $publickey, $chunkPadding = 16) {

$encrypted = '';

// load public key
$key = file_get_contents($publickey);

$pub_key = openssl_pkey_get_public($key);
$keyData = openssl_pkey_get_details($pub_key);

$chunksize = ($keyData['bits'] / 8) - $chunkPadding;

openssl_free_key( $pub

Solution

Replace all this junk:

byte[] encryptedBytes = Enumerable.Range( 0, encrypted.Length -1 )
                     .Where( x => x % 2 == 0 )
                     .Select( x => Convert.ToByte( encrypted.Substring( x, 2 ), 16 ) )
                     .ToArray();

        byte[] buffer = new byte[( rsa.KeySize / 8 )]; // the number of bytes to decrypt at a time
        int bytesRead = 0;

        using ( Stream stream = new MemoryStream( encryptedBytes ) ) {
            while ( (bytesRead = stream.Read( buffer, 0, buffer.Length )) > 0 ) {
                decrypted = decrypted + Encoding.UTF8.GetString( rsa.Decrypt( buffer, false ) );
            }
        }


with

using System.Runtime.Remoting.Metadata.W3cXsd2001;
byte[] encryptedBytes = SoapHexBinary.Parse(encrypted);
byte[] decryptedBytes = new byte[encryptedBytes.Length];
int blockSize = rsa.KeySize >> 3;
int blockCount = 1 + (encryptedBytes.Length - 1) / blockSize; 
Parallel.For(0, blockCount, (i) => {
    var offset = i * blockSize;
    var buffer = new byte[Math.Min(blockSize, encryptedBytes.Length - offset)];
    Buffer.BlockCopy(encryptedBytes, offset, buffer, 0, buffer.Length);
    Buffer.BlockCopy(rsa.Decrypt(buffer, false), 0, decryptedBytes, offset, buffer.Length);
});


We use a highly-optimized builtin class for converting hexadecimal description of a byte array into that byte array. Much simpler, much faster.

Then, we skip the MemoryStream entirely, since extra wrappers can only slow things down, and streams force us into sequential operation.

Finally, we use Parallel.For to process each block, passing it through rsa.Decrypt, and storing the output at the same array index the input came from.

The result's no longer a string, you can turn it into one after the end of the parallel for if you need to, but generally byte array is better for strong arbitrary data anyway.

If your block operation changes the size (how does that work? and how did you figure out how much to stick into each block on the encryption side?) then

int blockSize = rsa.KeySize >> 3;
int blockCount = 1 + (encryptedBytes.Length - 1) / blockSize; 
var decryptedChunks = new byte[][blockCount];
Parallel.For(0, blockCount, (i) => {
    var offset = i * blockSize;
    var buffer = new byte[Math.Min(blockSize, encryptedBytes.Length - offset)];
    Buffer.BlockCopy(encryptedBytes, offset, buffer, 0, buffer.Length);
    decryptedChunks[i] = rsa.Decrypt(buffer, false);
});
var decryptedBytes = decryptedChunks.SelectMany(x => x);


Note that the parallelization will only work if your rsa.Decrypt method is thread-safe/stateless. If it's not, find one that is.

Code Snippets

byte[] encryptedBytes = Enumerable.Range( 0, encrypted.Length -1 )
                     .Where( x => x % 2 == 0 )
                     .Select( x => Convert.ToByte( encrypted.Substring( x, 2 ), 16 ) )
                     .ToArray();

        byte[] buffer = new byte[( rsa.KeySize / 8 )]; // the number of bytes to decrypt at a time
        int bytesRead = 0;

        using ( Stream stream = new MemoryStream( encryptedBytes ) ) {
            while ( (bytesRead = stream.Read( buffer, 0, buffer.Length )) > 0 ) {
                decrypted = decrypted + Encoding.UTF8.GetString( rsa.Decrypt( buffer, false ) );
            }
        }
using System.Runtime.Remoting.Metadata.W3cXsd2001;
byte[] encryptedBytes = SoapHexBinary.Parse(encrypted);
byte[] decryptedBytes = new byte[encryptedBytes.Length];
int blockSize = rsa.KeySize >> 3;
int blockCount = 1 + (encryptedBytes.Length - 1) / blockSize; 
Parallel.For(0, blockCount, (i) => {
    var offset = i * blockSize;
    var buffer = new byte[Math.Min(blockSize, encryptedBytes.Length - offset)];
    Buffer.BlockCopy(encryptedBytes, offset, buffer, 0, buffer.Length);
    Buffer.BlockCopy(rsa.Decrypt(buffer, false), 0, decryptedBytes, offset, buffer.Length);
});
int blockSize = rsa.KeySize >> 3;
int blockCount = 1 + (encryptedBytes.Length - 1) / blockSize; 
var decryptedChunks = new byte[][blockCount];
Parallel.For(0, blockCount, (i) => {
    var offset = i * blockSize;
    var buffer = new byte[Math.Min(blockSize, encryptedBytes.Length - offset)];
    Buffer.BlockCopy(encryptedBytes, offset, buffer, 0, buffer.Length);
    decryptedChunks[i] = rsa.Decrypt(buffer, false);
});
var decryptedBytes = decryptedChunks.SelectMany(x => x);

Context

StackExchange Code Review Q#151376, answer score: 8

Revisions (0)

No revisions yet.