2
\$\begingroup\$

I need to implement an ISO-mode encryption/decryption library in C# using BouncyCastle. In this mode, users only need to exchange their RSA public keys to securely communicate. Operation is as follows:

For encryption:

  • User 1 generates a random text (x).
  • User 1 encrypts x with User 2's RSA public key (y).
  • User 1 computes the SHA3 hash of x (k).
  • User 1 generates a non-secret payload (p).
  • User 1 encrypts the plaintext with AES-GCM using k and p (c).
  • User 1 sends y and c to User 2.

For decryption:

  • User 2 decrypts y with his private key and obtains x.
  • User 2 computes the SHA3 has of x (k).
  • User 2 decrypts c with k and discards the payload (plaintext).

I've written the following:

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.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System;
using System.IO;
using System.Text;

namespace EncripcionPGP
{
    public class ISOEncryptor
    {
        #region Private constants
        private const int PARAMETERS_STRENGTH = 1024;
        private const int RANDOM_KEY_SIZE = 64;
        private const int PAYLOAD_SIZE = 16;
        private const int NONCE_SIZE = 16;
        private const int AEAD_MAC_SIZE = 128;
        #endregion

        #region Fields
        private AsymmetricCipherKeyPair _keys;
        private SecureRandom random;
        #endregion

        #region Public properties
        public byte[] PublicKey
        {
            get
            {
                var k = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(_keys.Public);
                return k.ToAsn1Object().GetDerEncoded();
            }
        }
        #endregion

        #region Constructors
        public ISOEncryptor()
        {
            random = new SecureRandom();
            var r = new RsaKeyPairGenerator();
            r.Init(new KeyGenerationParameters(random, PARAMETERS_STRENGTH));
            _keys = r.GenerateKeyPair();
        }
        #endregion

        #region Public methods
        public string Encrypt(string plaintext, byte[] targetPublicKey)
        {
            if (string.IsNullOrEmpty(plaintext))
            {
                throw new ArgumentNullException("Plaintext cannot be empty", "plaintext");
            }
            if ((targetPublicKey == null) || (targetPublicKey.Length == 0))
            {
                throw new ArgumentNullException("A public key must be provided", "targetPublicKey");
            }
            var plaintext_bytes = Encoding.UTF8.GetBytes(plaintext);
            var random_key = GenerateRandomKey(RANDOM_KEY_SIZE);
            var hashed_key = SHA3Hash(random_key);
            var payload = GenerateRandomKey(PAYLOAD_SIZE);
            var encryption_key = RSAEncrypt(random_key, targetPublicKey);
            var ciphertext = AESGCMEncrypt(plaintext_bytes, hashed_key, payload);
            var result = new byte[encryption_key.Length + ciphertext.Length];
            encryption_key.CopyTo(result, 0);
            ciphertext.CopyTo(result, encryption_key.Length);
            var output = Convert.ToBase64String(result);
            return output;
        }

        public string Decrypt(string ciphertext)
        {
            if (string.IsNullOrEmpty(ciphertext))
            {
                throw new ArgumentNullException("A ciphertext must be provided", "ciphertext");
            }
            var input = Convert.FromBase64String(ciphertext);
            var encrypted_key = SubArray(input, 0, 128);
            var random_key = RSADecrypt(encrypted_key);
            var hashed_key = SHA3Hash(random_key);
            var ciphertext_bytes = SubArray(input, 128, input.Length - 128);
            var payload = GenerateRandomKey(PAYLOAD_SIZE);
            var plaintext = AESGCMDecrypt(ciphertext_bytes, hashed_key);
            var output = Encoding.UTF8.GetString(plaintext);
            return output;
        }
        #endregion

        #region Internal implementation
        private byte[] RSAEncrypt(byte[] plaintext, byte[] publicKey)
        {
            var imported_key = PublicKeyFactory.CreateKey(publicKey);
            var rsa_engine = new Pkcs1Encoding(new RsaEngine());
            rsa_engine.Init(true, imported_key);
            var ciphertext = rsa_engine.ProcessBlock(plaintext, 0, plaintext.Length);
            return ciphertext;
        }

        private byte[] RSADecrypt(byte[] ciphertext)
        {
            var rsa_engine = new Pkcs1Encoding(new RsaEngine());
            rsa_engine.Init(false, _keys.Private);
            var plaintext = rsa_engine.ProcessBlock(ciphertext, 0, ciphertext.Length);
            return plaintext;
        }

        private byte[] AESGCMEncrypt(byte[] plaintext_bytes, byte[] hashed_key, byte[] payload)
        {
            var nonce = GenerateRandomKey(NONCE_SIZE);
            var aesgcm_engine = new GcmBlockCipher(new AesFastEngine());
            var parameters = new AeadParameters(
                new KeyParameter(hashed_key), AEAD_MAC_SIZE, nonce, payload);
            aesgcm_engine.Init(true, parameters);
            var ciphertext_size = aesgcm_engine.GetOutputSize(plaintext_bytes.Length);
            var ciphertext = new byte[ciphertext_size];
            var ciphertext_length = aesgcm_engine.ProcessBytes(
                plaintext_bytes,
                0,
                plaintext_bytes.Length, ciphertext,
                0);
            aesgcm_engine.DoFinal(ciphertext, ciphertext_length);
            using (var output_stream = new MemoryStream())
            {
                using (var binary_writer = new BinaryWriter(output_stream))
                {
                    binary_writer.Write(payload);
                    binary_writer.Write(nonce);
                    binary_writer.Write(ciphertext);
                }
                return output_stream.ToArray();
            }
        }

        private byte[] AESGCMDecrypt(byte[] ciphertext, byte[] hashed_key)
        {
            using (var cipher_stream = new MemoryStream(ciphertext))
            using (var cipher_reader = new BinaryReader(cipher_stream))
            {
                var payload = cipher_reader.ReadBytes(PAYLOAD_SIZE);
                var nonce = cipher_reader.ReadBytes(NONCE_SIZE);
                var aesgcm_engine = new GcmBlockCipher(new AesFastEngine());
                var parameters = new AeadParameters(
                    new KeyParameter(hashed_key), AEAD_MAC_SIZE, nonce, payload);
                aesgcm_engine.Init(false, parameters);
                var encrypted_text = cipher_reader.ReadBytes(
                    ciphertext.Length - (PAYLOAD_SIZE + NONCE_SIZE));
                var plaintext_size = aesgcm_engine.GetOutputSize(encrypted_text.Length);
                var plaintext = new byte[plaintext_size];
                var bytes_processed = aesgcm_engine.ProcessBytes(
                    encrypted_text,
                    0,
                    encrypted_text.Length,
                    plaintext,
                    0);
                aesgcm_engine.DoFinal(plaintext, bytes_processed);
                return plaintext;
            }
        }
        #endregion

        #region Helpers
        private byte[] GenerateRandomKey(int length)
        {
            var data = new byte[length];
            random.NextBytes(data);
            return data;
        }

        private byte[] SHA3Hash(byte[] textToHash)
        {
            var sha3_engine = new Sha3Digest();
            var output_size = sha3_engine.GetDigestSize();
            var result = new byte[output_size];
            sha3_engine.BlockUpdate(textToHash, 0, textToHash.Length);
            sha3_engine.DoFinal(result, 0);
            return result;
        }

        private byte[] SubArray(byte[] source, int index, int length)
        {
            var result = new byte[length];
            Array.Copy(source, index, result, 0, length);
            return result;
        }
        #endregion
    }
}

Please tell me what can I improve on this code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.