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.