From e5bed0afcb0a12144fe8d426472c56df5ecfd973 Mon Sep 17 00:00:00 2001 From: David Sparer Date: Mon, 11 Jul 2016 13:39:30 -0600 Subject: [PATCH] Testing out some encryption implementations --- .../Security/AesCryptographyProviderTests.cs | 17 +- mRemoteV1/Security/AesCryptographyProvider.cs | 2 +- .../BouncyCastleCryptographyEngine.cs | 21 +- .../Security/{Security.Crypt.cs => Crypt.cs} | 0 mRemoteV1/Security/Encryptor.cs | 184 ++++++++++++++++++ mRemoteV1/mRemoteV1.csproj | 3 +- 6 files changed, 215 insertions(+), 12 deletions(-) rename mRemoteV1/Security/{Security.Crypt.cs => Crypt.cs} (100%) create mode 100644 mRemoteV1/Security/Encryptor.cs diff --git a/mRemoteNGTests/Security/AesCryptographyProviderTests.cs b/mRemoteNGTests/Security/AesCryptographyProviderTests.cs index e44a111e..00bdfd9d 100644 --- a/mRemoteNGTests/Security/AesCryptographyProviderTests.cs +++ b/mRemoteNGTests/Security/AesCryptographyProviderTests.cs @@ -1,6 +1,9 @@ using System.Security; +using System.Text; using mRemoteNG.Security; using NUnit.Framework; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; namespace mRemoteNGTests.Security @@ -26,7 +29,7 @@ namespace mRemoteNGTests.Security [Test] public void GetBlockSizeReturnsProperValueForAes() { - Assert.That(_aesCryptographyProvider.BlockSize, Is.EqualTo(16)); + Assert.That(_aesCryptographyProvider.BlockSizeInBytes, Is.EqualTo(16)); } [Test] @@ -46,6 +49,16 @@ namespace mRemoteNGTests.Security Assert.That(decryptedCipherText, Is.EqualTo(plainText)); } + [Test] + public void EncryptorTest_DecryptedTextIsEqualToOriginalPlainText() + { + var plainText = "MySecret!"; + var aes = new Encryptor(); + var cipherText = aes.Encrypt(plainText, _encryptionKey); + var decryptedCipherText = aes.Decrypt(cipherText, _encryptionKey); + Assert.That(decryptedCipherText, Is.EqualTo(plainText)); + } + [Test] public void EncryptingTheSameValueReturnsNewCipherTextEachTime() { @@ -55,4 +68,4 @@ namespace mRemoteNGTests.Security Assert.That(cipherText1, Is.Not.EqualTo(cipherText2)); } } -} \ No newline at end of file +} diff --git a/mRemoteV1/Security/AesCryptographyProvider.cs b/mRemoteV1/Security/AesCryptographyProvider.cs index 75c25ee6..9d3b23b3 100644 --- a/mRemoteV1/Security/AesCryptographyProvider.cs +++ b/mRemoteV1/Security/AesCryptographyProvider.cs @@ -10,7 +10,7 @@ namespace mRemoteNG.Security private AesEngine _aesEngine; private readonly Encoding _encoding; - public int BlockSize => _aesEngine.GetBlockSize(); + public int BlockSizeInBytes => _aesEngine.GetBlockSize(); public AesCryptographyProvider() { diff --git a/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs b/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs index f4b6cef0..01941c93 100644 --- a/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs +++ b/mRemoteV1/Security/BouncyCastleCryptographyEngine.cs @@ -49,19 +49,16 @@ namespace mRemoteNG.Security { try { - var inputBytes = Encoding.UTF8.GetBytes(input); - var iv = BuildInitializationVector(); //for the sake of demo - //Set up + var inputStringAsByteArray = Encoding.UTF8.GetBytes(input); var cipher = new PaddedBufferedBlockCipher(_blockCipher); - var keyParam = new KeyParameter(Encoding.Default.GetBytes(key.ConvertToUnsecureString())); - var keyParamWithIv = new ParametersWithIV(keyParam, iv, 0, 16); + var keyParamWithIv = BuildKeyParameterWithIv(key); // Encrypt/Decrypt cipher.Init(forEncrypt, keyParamWithIv); - var outputBytes = new byte[cipher.GetOutputSize(inputBytes.Length)]; - var length = cipher.ProcessBytes(inputBytes, outputBytes, 0); - cipher.DoFinal(outputBytes, length); //Do the final block + var outputBytes = new byte[cipher.GetOutputSize(inputStringAsByteArray.Length)]; + var length = cipher.ProcessBytes(inputStringAsByteArray, outputBytes, 0); + cipher.DoFinal(outputBytes, length); return outputBytes; } catch (CryptoException ex) @@ -70,6 +67,14 @@ namespace mRemoteNG.Security } } + private ParametersWithIV BuildKeyParameterWithIv(SecureString key) + { + var iv = BuildInitializationVector(); + var keyParam = new KeyParameter(Encoding.Default.GetBytes(key.ConvertToUnsecureString())); + var keyParamWithIv = new ParametersWithIV(keyParam, iv, 0, 16); + return keyParamWithIv; + } + private byte[] BuildInitializationVector() { var numberOfBytes = _blockCipher.GetBlockSize(); diff --git a/mRemoteV1/Security/Security.Crypt.cs b/mRemoteV1/Security/Crypt.cs similarity index 100% rename from mRemoteV1/Security/Security.Crypt.cs rename to mRemoteV1/Security/Crypt.cs diff --git a/mRemoteV1/Security/Encryptor.cs b/mRemoteV1/Security/Encryptor.cs new file mode 100644 index 00000000..28841bf7 --- /dev/null +++ b/mRemoteV1/Security/Encryptor.cs @@ -0,0 +1,184 @@ +using System; +using System.Security; +using System.Security.Cryptography; +using System.Text; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Paddings; +using Org.BouncyCastle.Crypto.Parameters; + +namespace mRemoteNG.Security +{ + public sealed class Encryptor : ICryptographyProvider + where TBlockCipher : IBlockCipher, new() + where TDigest : IDigest, new() + { + private Encoding _encoding; + private IBlockCipher _blockCipher; + private BufferedBlockCipher _cipher; + private HMac _mac; + private TDigest _digest; + + public Encryptor() + { + _encoding = Encoding.UTF8; + Init(new Pkcs7Padding()); + _digest = new TDigest(); + } + + public Encryptor(Encoding encoding) + { + _encoding = encoding; + Init(new Pkcs7Padding()); + _digest = new TDigest(); + } + + public Encryptor(Encoding encoding, IBlockCipherPadding padding) + { + _encoding = encoding; + Init(padding); + _digest = new TDigest(); + } + + private void Init(IBlockCipherPadding padding) + { + _blockCipher = new CbcBlockCipher(new TBlockCipher()); + _cipher = new PaddedBufferedBlockCipher(_blockCipher, padding); + } + + private void InitializeMac(string message, SecureString key) + { + var macKey = BuildMacKey(message, key); + _mac = new HMac(_digest); + _mac.Init(new KeyParameter(macKey)); + } + + private byte[] BuildMacKey(string message, SecureString key) + { + var derivativeKey = GetDerivativeKey(key); + return derivativeKey; + } + + private byte[] GetDerivativeKey(SecureString key) + { + var kdfParam = new KdfParameters(_encoding.GetBytes(key.ConvertToUnsecureString()), GenerateIv()); + var kdf = new BaseKdfBytesGenerator(0, _digest); + kdf.Init(kdfParam); + + var outputBytes = new byte[_digest.GetByteLength()]; + kdf.GenerateBytes(outputBytes, 0, _digest.GetByteLength()); + return outputBytes; + } + + public string Encrypt(string plainText, SecureString encryptionKey) + { + var encryptedBytes = EncryptBytes(plainText, encryptionKey); + return Convert.ToBase64String(encryptedBytes); + } + + public byte[] EncryptBytes(string plainText, SecureString encryptionKey) + { + InitializeMac(plainText, encryptionKey); + var input = _encoding.GetBytes(plainText); + var iv = GenerateIv(); + + var encryptionKeyAsByteArray = _encoding.GetBytes(encryptionKey.ConvertToUnsecureString()); + var keyParam = new KeyParameter(encryptionKeyAsByteArray); + var keyParamWithIv = new ParametersWithIV(keyParam, iv); + var cipher = BouncyCastleCrypto(true, input, keyParamWithIv); + var message = CombineArrays(iv, cipher); + + _mac.Reset(); + _mac.BlockUpdate(message, 0, message.Length); + var digest = new byte[_mac.GetUnderlyingDigest().GetDigestSize()]; + _mac.DoFinal(digest, 0); + + var result = CombineArrays(digest, message); + return result; + } + + public byte[] DecryptBytes(byte[] bytes, SecureString decryptionKey) + { + // split the digest into component parts + var digest = new byte[_mac.GetUnderlyingDigest().GetDigestSize()]; + var message = new byte[bytes.Length - digest.Length]; + var iv = new byte[_blockCipher.GetBlockSize()]; + var cipher = new byte[message.Length - iv.Length]; + + Buffer.BlockCopy(bytes, 0, digest, 0, digest.Length); + Buffer.BlockCopy(bytes, digest.Length, message, 0, message.Length); + if (!IsValidHMac(digest, message)) + { + throw new CryptoException(); + } + + Buffer.BlockCopy(message, 0, iv, 0, iv.Length); + Buffer.BlockCopy(message, iv.Length, cipher, 0, cipher.Length); + + var decryptionKeyAsByteArray = _encoding.GetBytes(decryptionKey.ConvertToUnsecureString()); + var keyParam = new KeyParameter(decryptionKeyAsByteArray); + var keyParamWithIv = new ParametersWithIV(keyParam, iv); + var result = BouncyCastleCrypto(false, cipher, keyParamWithIv); + return result; + } + + public string Decrypt(string cipher, SecureString decryptionKey) + { + var cipherTextAsByteArray = Convert.FromBase64String(cipher); + var decryptedBytes = DecryptBytes(cipherTextAsByteArray, decryptionKey); + var decryptedBytesAsEncodedString = _encoding.GetString(decryptedBytes); + return decryptedBytesAsEncodedString; + } + + private bool IsValidHMac(byte[] digest, byte[] message) + { + _mac.Reset(); + _mac.BlockUpdate(message, 0, message.Length); + var computed = new byte[_mac.GetUnderlyingDigest().GetDigestSize()]; + _mac.DoFinal(computed, 0); + return AreEqual(digest, computed); + } + + private static bool AreEqual(byte[] digest, byte[] computed) + { + if (digest.Length != computed.Length) + return false; + + var result = 0; + for (var i = 0; i < digest.Length; i++) + { + // compute equality of all bytes before returning. + // helps prevent timing attacks: + // https://codahale.com/a-lesson-in-timing-attacks/ + result |= digest[i] ^ computed[i]; + } + return result == 0; + } + + private byte[] BouncyCastleCrypto(bool forEncrypt, byte[] input, ICipherParameters parameters) + { + _cipher.Init(forEncrypt, parameters); + return _cipher.DoFinal(input); + } + + private byte[] GenerateIv() + { + using (var provider = new RNGCryptoServiceProvider()) + { + var result = new byte[_blockCipher.GetBlockSize()]; + provider.GetBytes(result); + return result; + } + } + + private static byte[] CombineArrays(byte[] source1, byte[] source2) + { + var result = new byte[source1.Length + source2.Length]; + Buffer.BlockCopy(source1, 0, result, 0, source1.Length); + Buffer.BlockCopy(source2, 0, result, source1.Length, source2.Length); + return result; + } + } +} \ No newline at end of file diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index b029aa8c..0c41b6e1 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -169,6 +169,8 @@ + + @@ -343,7 +345,6 @@ Settings.settings -