【问题标题】:How to create a bidirectional encrypted stream on top of an underlying .NET Stream?如何在底层 .NET 流之上创建双向加密流?
【发布时间】:2011-07-16 12:28:38
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 为什么创建两个流是不可接受的?在这种情况下,我通常想为这里发生的两个“会话”设置不同的加密密钥(例如,使用 HASH(passphrase + '||' + sender name) 作为密钥)
  • @Damien 因为我在这个之上还有更多流分层,我不想复制所有流。
  • 如果您需要支持读取和写入(但可能不查找)的单个流对象的语义,但将这些操作推迟到已传递给它的“ReadStream”或“WriteStream”在它的构造中,应该只需要一页左右的代码来编写。因此,您可以使用 Crypto 类在通道的两侧添加加密,然后使用其他类将其作为单个流呈现给更高层。

标签: .net security cryptography


【解决方案1】:

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

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

请注意,我使用的是 AES256,尽管它已被损害为与 AES128 具有相似的强度(如果您的密钥由于实施不当而相关,我不知道)。如果您不相信 NIST 知道 NSA 是否在弄乱他们的规范,那么请不要使用 AES。

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

112 行或更少,废话不多说:

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);
        }
    }
}

【讨论】:

  • ~CryptoNetworkStream().Dispose() 不好。当你的类的终结器运行时,你不能保证 3 个内部流还没有被终结(GC 终结器作为堆而不是队列工作)。如果在外部流上显式调用,则应更改为标准 dispose 模式,并且仅在 3 个内部流上调用 .Dispose()
  • 当然...已更新以反映 MS 的处置模式。通常我会将流视为非托管资源,但您建议仅在明确完成时才处置它们。
  • 还是不对,因为你的类继承自抽象基类 Stream 并且因为该类已经实现了 dispose 模式,所以 Dispose()~CryptoNetworkStream() 都不应该在你的类(您还需要显式覆盖void Dispoese(bool) 并在最后调用基类的版本)。我已经更正了。
  • 当我发表第一条评论时,我没有意识到你从Stream继承的地方忽略了我以前的无知:(
  • 不用担心。我对其进行了编辑以反映其在本地机器上的当前状态,并更加突出显示处置模式。随意检查我现在有什么东西。
【解决方案2】:

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

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

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

编辑:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-28
    • 2020-03-03
    • 2011-07-23
    • 2015-08-27
    • 2018-03-05
    • 2011-06-09
    • 2013-10-24
    • 2019-04-08
    相关资源
    最近更新 更多