Mar 21 2010

Hybrid Cryptosystem

Category: CryptographyMike Lovell @ 12:11 pm

When using ‘asymmetric’ (public-private key) encryption, there are a few limitations:

  • Slow Speed - Symmetric (key) encryption is far quicker
  • Single Recipient – You can only encrypt to a single public key

However we can combine ‘asymmetric’ and ‘symmetric’ cryposystems to create a hybrid system which will allow us to harness the speed of ‘symmetric’encryption and allow multiple recipients by simply encrypting the shared ‘symmetric key’ to each public ‘asymmetric key’ we want to use.

So lets create a simple format for a ‘hybrid cryptosystem‘.  We’re going to be using ‘RSA‘ for our ‘asymmetric encryption‘, and AES (Rijndael) for our ‘symmetric encryption‘.


1 Byte    Recepient count: 1-255 recepients allowed
4 Bytes   Recepient 1 data: Size (Int32)
20 Bytes  Recepient 1 data: SHA1 hash of public key
n Bytes   Recepient 1 data: Shared key/password (encrypted to public key)
...
4 Bytes   Recepient n data: Size (Int32)
20 Bytes  Recepient n data: SHA1 hash of public key
n Bytes   Recepient n data: Shared key/password (encrypted to public key)

n Bytes   Encrypted data: Encrypted using AES (Rijndael) and shared key/password

 

Now we have our format defined, we can implement a simple C# application that follows this.  First we’re going to implement a ‘CryptoProvider‘ class.  This is going to handle both ‘asymmetric‘ and ‘symmetric‘ encryption and decryption, key generation, hash generation and random byte generation.  Our key/password is going to be randomly generated and 48 bytes long.  The first 16 will be the ‘IV‘ vector, the last 32 will be the ‘key‘.

   1:  // Author: Mike Lovell (mike.lovell@gotinker.com)
   2:   
   3:  class CryptoProvider
   4:  {
   5:      private    RSACryptoServiceProvider    rsaProvider;
   6:   
   7:   
   8:      public CryptoProvider(int keySize)
   9:      {
  10:          rsaProvider = new RSACryptoServiceProvider(keySize);    // Generate new key
  11:      }
  12:   
  13:   
  14:      public CryptoProvider(string xmlKey)
  15:      {
  16:          rsaProvider = new RSACryptoServiceProvider();
  17:   
  18:          rsaProvider.FromXmlString(xmlKey);    // Import XML Key
  19:      }
  20:   
  21:   
  22:      public string ToXmlString(bool includePrivateKey)
  23:      {
  24:          return rsaProvider.ToXmlString(includePrivateKey);    // Export XML Key
  25:      }
  26:   
  27:   
  28:      public byte[] Hash()
  29:      {
  30:          var sha1 = new SHA1Managed();
  31:   
  32:          // Hash from the modulus
  33:          return sha1.ComputeHash(rsaProvider.ExportParameters(false).Modulus);
  34:      }
  35:   
  36:   
  37:      public byte[] Encrypt(byte[] data)
  38:      {
  39:          return rsaProvider.Encrypt(data, false);
  40:      }
  41:   
  42:   
  43:      public byte[] Decrypt(byte[] data)
  44:      {
  45:          return rsaProvider.Decrypt(data, false);
  46:      }
  47:   
  48:   
  49:      public static byte[] RandomSequence(int size)
  50:      {
  51:          var random        = new byte[size];
  52:          var rngProvider    = new RNGCryptoServiceProvider();
  53:  
  54:          rngProvider.GetBytes(random);
  55:   
  56:          return random;
  57:      }
  58:   
  59:   
  60:      public static byte[] Encrypt(byte[] data, byte[] password)
  61:      {
  62:          var rijndael    = Rijndael.Create();
  63:   
  64:          rijndael.KeySize    = 256;    // Maximum key size
  65:          rijndael.IV            = SubBytes(password, 0, 16);    // First 16 bytes is vector
  66:          rijndael.Key        = TrimBytes(SubBytes(password, 16, password.Length - 16), 32);    // Last 32 is key
  67:          rijndael.Mode        = CipherMode.CBC;
  68:   
  69:          var stream            = new MemoryStream();
  70:          var cryptoStream    = new CryptoStream(stream, rijndael.CreateEncryptor(), CryptoStreamMode.Write);
  71:   
  72:          cryptoStream.Write(data, 0, data.Length);
  73:          cryptoStream.FlushFinalBlock();
  74:   
  75:          cryptoStream.Close();
  76:   
  77:          var encryptedData = stream.ToArray();
  78:   
  79:          stream.Close();
  80:   
  81:          return encryptedData;
  82:      }
  83:   
  84:   
  85:      public static byte[] Decrypt(byte[] data, byte[] password)
  86:      {
  87:          var rijndael    = Rijndael.Create();
  88:   
  89:          rijndael.KeySize    = 256;    // Maximum key size
  90:          rijndael.IV            = SubBytes(password, 0, 16);    // First 16 bytes is vector
  91:          rijndael.Key        = TrimBytes(SubBytes(password, 16, password.Length - 16), 32);    // Last 32 is key
  92:          rijndael.Mode        = CipherMode.CBC;
  93:   
  94:          var stream            = new MemoryStream(data);
  95:          var cryptoStream    = new CryptoStream(stream, rijndael.CreateDecryptor(), CryptoStreamMode.Read);
  96:   
  97:          var    length            = cryptoStream.Read(data, 0, data.Length);
  98:          var    decryptedData    = new byte[length];
  99:   
 100:          stream.Read(decryptedData, 0, length);
 101:   
 102:          cryptoStream.Close();
 103:          stream.Close();
 104:   
 105:          Buffer.BlockCopy(data, 0, decryptedData, 0, length);
 106:   
 107:          return decryptedData;
 108:      }
 109:   
 110:   
 111:      public byte[] DecryptPasswordAndData(byte[] data)
 112:      {
 113:          var    buffer            = new MemoryStream(data);
 114:          var    keyCount        = buffer.ReadByte();    // First byte is key count
 115:          var    thisHash        = Convert.ToBase64String(this.Hash());
 116:          var    password        = (byte[])null;
 117:   
 118:          if (keyCount < 1) throw new Exception("No keys found");
 119:  
 120:          var    keyLengthBuffer    = new byte[4];
 121:  
 122:          for (int i=0; i < keyCount; i++)
 123:          {
 124:              // First 4 bytes are Int32 of encrypted hash/password size
 125:              buffer.Read(keyLengthBuffer, 0, 4);
 126:   
 127:              var    keyLength    = BitConverter.ToInt32(keyLengthBuffer, 0);
 128:              var    keyHash        = new byte[20];    // First 20 bytes is SHA1 hash
 129:              var    keyPassword    = new byte[keyLength - 20];    // Remaining byes are encrypted password
 130:   
 131:              buffer.Read(keyHash, 0, 20);    // Read first 20 bytes (SHA1 Hash)
 132:              buffer.Read(keyPassword, 0, keyPassword.Length);    // Read remaining bytes (password)
 133:   
 134:              if (Convert.ToBase64String(keyHash) == thisHash)
 135:              {
 136:                  password = Decrypt(keyPassword);    // Decrypt password
 137:              }
 138:          }
 139:   
 140:          if (password == null) throw new Exception("Data not encrypted to your key");
 141:   
 142:          // remaining bytes are encrypted data
 143:          var encryptedData     = new byte[buffer.Length - buffer.Position];
 144:   
 145:          // Read remaining bytes
 146:          buffer.Read(encryptedData, 0, encryptedData.Length);
 147:   
 148:          buffer.Close();
 149:   
 150:          return CryptoProvider.Decrypt(encryptedData, password);    // Decrypt data
 151:      }
 152:   
 153:   
 154:      private static byte[] SubBytes(byte[] source, int offset, int length)
 155:      {
 156:          var    subData    = new byte[length - offset];
 157:   
 158:          Buffer.BlockCopy(source, offset, subData, 0, length - offset);
 159:   
 160:          return subData;
 161:      }
 162:   
 163:   
 164:      private static byte[] TrimBytes(byte[] source, int maxLength)
 165:      {
 166:          if (source.Length > maxLength)
 167:          {
 168:              var trimmedData = new byte[maxLength];
 169:   
 170:              Buffer.BlockCopy(source, 0, trimmedData, 0, maxLength);
 171:   
 172:              return trimmedData;
 173:          }
 174:   
 175:          return source;
 176:      }
 177:  }
 178:   

Then lets create a collection of ‘CryptoProvider‘ which will allow us to encrypt our data to multiple public keys.

 179:   
 180:  class CryptoProviderCollection : List<CryptoProvider>
 181:  {
 182:      public byte[] EncryptPasswordAndData(byte[] data)
 183:      {
 184:          if (this.Count < 1 || this.Count > 255) throw new Exception("Too few or too many recepients");
 185:   
 186:          var password    = CryptoProvider.RandomSequence(16 + 32);    // Random password
 187:                                                                      // First 16 = Vector
 188:                                                                      // Last 32 = Key
 189:          var stream        = new MemoryStream();
 190:  
 191:          stream.WriteByte((byte)this.Count); // First byte is recepient count
 192:   
 193:          var encryptedPasswords    =
 194:              (
 195:                  from    item
 196:                  in        this
 197:                  select    CombineBytes
 198:                      (
 199:                          item.Hash(),            // Public key hash
 200:                          item.Encrypt(password)    // Password encrypted for recipient
 201:                      )
 202:              );
 203:   
 204:   
 205:          foreach (var encryptedPassword in encryptedPasswords)
 206:          {    // For each recepient
 207:   
 208:              // Store encrypted password length as Int32 (first 4 bytes)
 209:              stream.Write(BitConverter.GetBytes(encryptedPassword.Length), 0, 4);
 210:              // Store encrypted password
 211:              stream.Write(encryptedPassword, 0,  encryptedPassword.Length);
 212:          }
 213:   
 214:          var encryptedData = CryptoProvider.Encrypt(data, password);    // Encrypt data
 215:   
 216:          stream.Write(encryptedData, 0, encryptedData.Length);        // Store
 217:   
 218:          var encryptedBytes = stream.ToArray();
 219:   
 220:          stream.Close();
 221:   
 222:          return encryptedBytes;
 223:      }
 224:   
 225:   
 226:      private byte[] CombineBytes(byte[] dataA, byte[] dataB)
 227:      {
 228:          var combinedData = new byte[dataA.Length + dataB.Length];
 229:   
 230:          Buffer.BlockCopy(dataA, 0, combinedData, 0, dataA.Length);
 231:          Buffer.BlockCopy(dataB, 0, combinedData, dataA.Length, dataB.Length);
 232:   
 233:          return combinedData;
 234:      }
 235:  }
 236:   

Now lets see if it works.  We’ll create 3 ‘RSA‘ keys then encrypt the data for the first two recipients ONLY, just to see if the 3rd cannot decrypt the data (which should be the case).

 237:   
 238:  class Program
 239:  {
 240:      static void Main(string[] args)
 241:      {
 242:          var    key1    = new CryptoProvider(1024);    // Make a new key
 243:          var    key2    = new CryptoProvider(2048);    // ..
 244:          var    key3    = new CryptoProvider(1024);    // ..
 245:   
 246:          var col    = new CryptoProviderCollection();
 247:  
 248:          col.Add(key1);    // Add only key1 and key2
 249:          col.Add(key2);    // to the collection
 250:   
 251:          // Encrypt data and random password for key1 and key2
 252:          var encryptedData    = col.EncryptPasswordAndData(Encoding.UTF8.GetBytes("Test Encrypted Data"));
 253:  
 254:          // Decrypt using key1
 255:          Console.WriteLine(Encoding.UTF8.GetString(key1.DecryptPasswordAndData(encryptedData)));
 256:          // Decrypt using key2
 257:          Console.WriteLine(Encoding.UTF8.GetString(key2.DecryptPasswordAndData(encryptedData)));
 258:  
 259:          try
 260:          {
 261:              // Decrypt using key3, that we DIDN'T use for encryption, this will fail
 262:              Console.WriteLine(Encoding.UTF8.GetString(key3.DecryptPasswordAndData(encryptedData)));
 263:          }
 264:          catch (Exception e)
 265:          {
 266:              Console.WriteLine("ERROR: {0}",  e.Message.ToString());
 267:          }
 268:   
 269:          Console.ReadLine();
 270:      }
 271:  }

And the result you should see is the following:

Test Encrypted Data
Test Encrypted Data
ERROR: Data not encrypted to your key

You can expand the classes to carry out ‘exception handling‘, and build a robust ‘hybrid cryptosystem‘ from this basic implementation.

Download Visual Studio 2010 Project (8.33k)

Tags: , , , , , , , , , ,