3
\$\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\$

1 Answer 1

2
\$\begingroup\$

I need to implement an ISO-mode encryption/decryption library in C# using BouncyCastle.

I don't see anything that I can call ISO mode. Are you talking about light intensity maybe? ISO without specifying a specific standard is useless (and it's ISO/IEC for most standards, thank you).

In this mode, users only need to exchange their RSA public keys to securely communicate.

No, they need to trust each other's keys in order to communicate. And they probably want to perform some signing as well to do so, or they may receive data from anybody knowing the public key. Generally for that we use sign-then-encrypt.


About the protocol:

  • You're using a deprecated mode, PKCS#1 v1.5 padding instead of OAEP for encryption, PKCS#1 v1.5 is possibly not secure with regards to padding oracle attacks.
  • You're using a static key size of 1024 bits, which is considered too small nowadays and in 2017, use at least 3072 bit RSA keys.
  • The 64 byte random key followed by SHA-3 doesn't make any sense, just use 16 random bytes. The padding of RSA is already randomized, so your randomization doesn't have any security benefits.
  • The inclusion of a random nonce and random payload (what does that even mean?) is completely unnecessary as the key is already randomized.
  • The GCM nonce is 16 bytes, which is not efficient, it should be 12 bytes / 96 bits for this specific mode.
  • Generating a new key pair in the class doesn't make any sense - these keys should be static keys.
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I may do a code review later, preferably after the protocol is amended to something sensible. It's certainly not PGP though, so the naming is certainly off. \$\endgroup\$ Mar 3, 2020 at 2:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.