如何在底层.NET Stream上创建双向加密流?

时间:2011-07-16 12:28:38

标签: .net security cryptography

我有一个Stream,它通过不安全的通道发送和接收数据。我有一个预先共享的秘密,即通道的两个端点都已经拥有(例如,密码)。

我想使用秘密和原始的不安全流构建新流。我遇到的两个问题是:

  • CryptoStream是单向的:只读或只写。我可以在原始Stream上创建两个流(读取和写入流),但这是不可接受的。我是否必须编写包装流来获取我想要的内容? (即,单个读/写流)

  • 据说CryptoStream在块中工作,并且在块完成之前可能不会向底层流写入任何内容。理想情况下,我想写任意数量的数据,并立即将其发送到基础流(加密)。

有没有简单的方法来实现这一目标?我知道SslStream,但它是针对私钥/公钥和证书而不是预先共享的秘密而定制的。

2 个答案:

答案 0 :(得分:5)

我怀疑你两年后会回来接受一个答案,但是我只是做了你所问的问题,我认为这是一个相当普遍的问题所以我发布这个是为了其他可能遇到的问题这个问题。

我将GregS的信息纳入我的实施中。为了您的特定目的,您可以将Initialize方法作为构造函数,删除net& diffie-hellman代码,并将您的预共享密钥分配给Aes对象(而不是生成的密钥)。

请注意我使用的是AES256,尽管它与AES128具有相似的强度(如果你的密钥因执行不良而相关,我的知识并不是我的知识)。如果你不相信NIST知道NSA是否在搞乱他们的规格,那就不要使用AES。

此外,这是一个起点!我正在解决在.NET中通过NetworkStream发送加密数据的常见问题!

在十二行或更少的行中,没有进一步的麻烦:

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;

namespace FullDuplexCrypto
{
    class CryptoNetworkStream : Stream
    {
        public CryptoNetworkStream(IPAddress address, int port)
        {
            Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
            socket.Connect(address, port);
            //socket.NoDelay = true;
            Initialize(new NetworkStream(socket, true));
        }

        public CryptoNetworkStream(Socket socket)
        {
            Initialize(new NetworkStream(socket, true));
        }

        private void Initialize(Stream stream)
        {
            underlyer = stream;

            using(ECDiffieHellmanCng dh = new ECDiffieHellmanCng())
            {
                dh.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
                dh.HashAlgorithm = CngAlgorithm.Sha256;
                byte[] buffer = dh.PublicKey.ToByteArray();
                underlyer.Write(buffer, 0, buffer.Length);
                underlyer.Read(buffer, 0, buffer.Length);

                using(Aes aes = Aes.Create())
                {
                    aes.KeySize = 256;
                    aes.Key = dh.DeriveKeyMaterial(CngKey.Import(buffer, CngKeyBlobFormat.EccPublicBlob));
                    aes.FeedbackSize = 8;
                    aes.Mode = CipherMode.CFB;

                    underlyer.Write(aes.IV, 0, aes.IV.Length);
                    encrypter = new CryptoStream(underlyer, aes.CreateEncryptor(), CryptoStreamMode.Write);

                    underlyer.Read(aes.IV, 0, aes.IV.Length);
                    decrypter = new CryptoStream(underlyer, aes.CreateDecryptor(), CryptoStreamMode.Read);
                }
            }
        }

        private Stream underlyer;
        private Stream encrypter;
        private Stream decrypter;

        public override bool CanRead { get { return decrypter.CanRead; } }
        public override bool CanWrite { get { return encrypter.CanWrite; } }
        public override bool CanSeek { get { return underlyer.CanSeek; } }
        public override long Length { get { return underlyer.Length; } }
        public override long Position { get { return underlyer.Position; } set { underlyer.Position = value; } }

        public override void Flush()
        {
            encrypter.Flush();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return decrypter.Read(buffer, offset, count);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            encrypter.Write(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return underlyer.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            underlyer.SetLength(value);
        }

        private bool isDisposed = false;

        protected override void Dispose(bool isDisposing)
        {
            if(!isDisposed)
            {
                if(isDisposing)
                {
                    // Release managed resources.
                    encrypter.Dispose();
                    decrypter.Dispose();
                    underlyer.Dispose();

                }
                // Release unmanaged resources.

                isDisposed = true;
            }
            base.Dispose(isDisposing);
        }
    }
}

答案 1 :(得分:2)

您需要正确传输和读取IV,但您可以在CFB模式下使用分组密码(例如AES),反馈大小为8位,以消除“阻塞”限制。我认为你必须编写自己的加密流来获得你想要的双向行为。

在流的加密端生成随机IV并首先传输它。在解密端,首先从流中读取IV字节,然后用它初始化密码变换,然后通过加密变换传递从流中读取的剩余字节。

如果您愿意使用Bouncycastle C# library,您可以使用SrpTlsClient类通过预共享密钥获得TLS / SSL中所有安全工程和分析的好处。该类在TLS中实现SRP ciphersuites

编辑:

没关系SRP TLS,Bouncycastle库只有协议的客户端。太糟糕了。