Runtime: 密码的通用低级原语(AES-GCM 是第一个)

创建于 2017-08-29  ·  143评论  ·  资料来源: dotnet/runtime

基本原理

一般需要许多密码来进行加密。 今天的接口和类的混合变得有点脱节。 此外,不支持 AEAD 样式密码,因为它们需要能够提供额外的身份验证信息。 当前的设计也容易分配,并且由于返回数组而难以避免。

提议的 API

将由具体类实现的通用抽象基类。 这将允许扩展,并且通过使用类而不是静态方法,我们能够创建扩展方法以及在调用之间保持状态。 API 应该允许类的回收以允许较低的分配(不需要每次都需要一个新实例,并且可以捕获非托管键)。 由于跟踪的资源通常是非托管的,因此该类应实现 IDisposable

public abstract class Cipher : IDisposable
{
    public virtual int TagSize { get; }
    public virtual int IVSize { get; }
    public virtual int BlockSize { get; }
    public virtual bool SupportsAssociatedData { get; }

    public abstract void Init(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
    public abstract void Init(ReadOnlySpan<byte> iv);
    public abstract int Update(ReadOnlySpan<byte> input, Span<byte> output);
    public abstract int Finish(ReadOnlySpan<byte> input, Span<byte> output);
    public abstract void AddAssociatedData(ReadOnlySpan<byte> associatedData);
    public abstract int GetTag(Span<byte> span);
    public abstract void SetTag(ReadOnlySpan<byte> tagSpan);
}

示例用法

(输入/输出源是一个神话般的基于跨度的流,如 IO 源)

using (var cipher = new AesGcmCipher(bitsize: 256))
{
    cipher.Init(myKey, nonce);
    while (!inputSource.EOF)
    {
        var inputSpan = inputSource.ReadSpan(cipher.BlockSize);
        cipher.Update(inputSpan);
        outputSource.Write(inputSpan);
    }
    cipher.AddAssociatedData(extraInformation);
    cipher.Finish(finalBlockData);
    cipher.GetTag(tagData);
}

API 行为

  1. 如果在完成之前调用 get 标记,则应抛出 [异常类型?] 并且应将内部状态设置为无效
  2. 如果标签在完成解密时无效,则应该抛出异常
  3. 调用完成后,将抛出对 Init 方法之一以外的任何内容的调用
  4. 一旦 Init 被调用,第二个没有“完成”的调用将抛出
  5. 如果类型期望提供一个键(一个直接的“new'd”实例),如果初始“Init”调用只有一个 IV,它将抛出
  6. 如果类型是从基于存储的密钥生成的,并且您尝试通过 Init 更改密钥,而不仅仅是它会抛出的 IV
  7. 如果在 dispose 或 Init 之前没有调用 get 标记,是否应该抛出异常? 阻止用户不小心收集标签?

参考 dotnet/corefx#7023

更新

  1. 将 nonce 更改为 IV。
  2. 添加了行为部分
  3. 从完成和更新中删除了单个输入/输出跨度案例,它们可以只是扩展方法
  4. 按照@bartonjs的建议,将多个跨度更改为 readonlyspan
  5. 删除了重置,应该使用带 IV 的初始化
api-suggestion area-System.Security

最有用的评论

@bartonjs ,您实际上是在忽略所有技术和逻辑分析,而是使用 Go 和 libsodium 项目的日期作为实际分析的弱代理?

不,我正在使用专业密码学家的建议,他们说这是非常危险的,我们应该避免使用流式传输 AEAD。 然后我使用了来自 CNG 团队的信息:“很多人说他们在理论上想要它,但实际上几乎没有人这样做”(我不知道其中有多少是遥测与来自现场援助请求的轶事)。 其他图书馆采取一次性路线的事实只是_加强_决定。

为什么到目前为止在 GitHub 上展示的需求不足?

已经提到了一些场景。 处理碎片化的缓冲区可能会通过接受ReadOnlySequence来解决,如果看起来有足够的场景来保证使 API 复杂化而不是让调用者进行数据重组。

大文件是个问题,但大文件已经是个问题了,因为 GCM 的截止值接近 64GB,这“不是那么大”(好吧,它相当大,但不是“哇,那是大”)它曾经是)。 内存映射文件将允许使用 Spans(最多 2^31-1)而不需要 2GB 的 RAM。 所以我们已经从最大值中减少了几位……无论如何,这可能会随着时间的推移而发生。

您确实意识到,为 AEAD 决定非流接口会排除所有此类实现,对吗?

我越来越相信@GrabYourPitchforks是正确的(https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891)可能没有一个明智的统一界面。 GCM _requiring_ 一个 nonce/IV 和 SIV _forbidding_ 这意味着 AEAD 模式/算法的初始化已经需要了解将要发生的事情......对于 AEAD 并没有真正的“抽象”概念。 SIV 规定了“标签”的去向。 GCM/CCM 没有。 根据规范,SIV 是标签优先的。

SIV 在拥有所有数据之前无法开始加密。 所以它的流加密要么会抛出(这意味着你必须知道不要调用它)或缓冲区(这可能会导致 n^2 操作时间)。 在知道长度之前,CCM 无法启动; 但是 CNG 不允许对长度进行预加密提示,所以它在同一条船上。

我们不应该设计一个新组件,默认情况下做错事比做对事更容易。 流式解密使得连接 Stream 类变得非常容易和诱人(你建议使用 CryptoStream 这样做),这使得在验证标签之前很容易获得数据验证错误,这几乎完全抵消了 AE 的好处. ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "等等,这不是合法的 XML..." => 自适应密文 oracle) .

说到重点……客户需求。

不幸的是,我一生中听过太多次:对不起,您不是我们心目中的客户。 我承认,也许您知道如何安全地进行 GCM。 您知道在标签验证之前只流式传输到易失性文件/缓冲区/等。 您知道 nonce 管理意味着什么,并且您知道出错的风险。 您知道要注意流大小并在 2^36-64 字节后切换到新的 GCM 段。 你知道,在这一切都说了又做了之后,如果你把这些事情弄错了,那就是你的错误。

另一方面,我心目中的客户是知道“我必须加密这个”的人,因为他们的老板告诉他们这样做。 他们知道,在搜索如何进行加密时,一些教程说“始终使用 AE”并提到 GCM。 然后他们找到了使用 CryptoStream 的“.NET 中的加密”教程。 然后他们连接管道,不知道他们刚刚做了与选择 SSLv2 相同的事情……理论上勾选了一个框,但实际上并没有。 而当他们这样做时,_那个_错误属于每个知道得更好的人,但让错误的事情太容易做。

所有143条评论

对提议的 API 的快速判断反馈(试图提供帮助):

  • Span<T>不在NetStandard2中。
  • “Nonce”是非常特定于实现的 - 即。 GCM 的气味。 然而,即使是 GCM 文档(例如 NIST SP800-38D)也将其称为“IV”,在 GCM 的情况下,它恰好是一个随机数。 但是,在其他 AEAD 实现的情况下,IV 不必是随机数(例如,在 CBC+HMAC 下重复 IV 不是灾难性的)。
  • 流式传输 AEAD 应该与CryptoStream无缝协作,或者提供它自己的“AEADCryptoStream”,它与 CryptoStream 一样容易流入/流出。
  • 应该允许 AEAD API 实现基于 AAD(关联数据)进行内部密钥派生。 仅将 AAD 用于标签计算/验证的限制性太强,无法使用更强大的 AEAD 模型。
  • “Get*”方法应该返回一些东西(GetTag)。 如果它们是无效的,它们一定是在设置一些东西/改变状态。
  • 在“完成”之前尝试获取标签可能是个坏主意,因此“IsFinished”可能会有所帮助。
  • 设计ICryptoTransform的人考虑了重用、多块支持和不同大小的输入/输出块大小。 这些担忧没有被捕捉到。

作为 AEAD API 健全性的证明,这种提议的 API 的第一个实现不应该是 AES-GCM,而是带有 HMAC 标签的经典/默认 AES-CBC。 原因很简单,现在任何人都可以使用简单、现有、知名的 .NET 类构建 AES-CBC+HMAC AEAD 实现。 让我们先让一个无聊的旧 [AES-CBC+HMAC] 处理新的 AEAD API,因为这对每个人来说都很容易进行 MVP 和试驾。

nonce/IV 命名问题是我尚未决定的事情,对 IV 的更改感到满意,所以会改变。

至于返回某些东西的 Get 方法,这避免了任何分配。 可能存在返回某些内容的重载 Get()。 也许它需要更改命名,但我非常赞同整个 API 需要基本上免费分配的想法。

至于流等,我并不太在意,因为它们是更高级别的 API,可以很容易地从较低级别的原语构造。

不允许在完成之前获取标签,但是您应该知道何时调用完成,所以我不确定它是否应该在 API 中,但它应该是一个定义的行为,所以我更新了 API 设计以包含一个行为部分,以便我们可以捕获任何其他想到的。

至于哪种密码,我不认为任何特定的密码应该是唯一的目标,为了证明一个新的通用 API 它需要适合一个数字。 AES GCM 和 CBC 都应包括在内。

(所有关于主题的反馈好坏总是有帮助的!)

  • 类,还是接口?
  • 如果有的话,当前的 SymmetricAlgorithm 类如何与之交互?
  • 这将如何用于持久密钥,例如 TripleDESCng 和 AesCng 可以做什么?
  • 其中许多 Span 看起来可能是 ReadOnlySpan。

@Drawaes感谢您在此 API 上取得成功。 一些想法:

  1. 标签生成和验证是这个 API 的一个非常重要的部分,因为滥用标签会破坏整个目的。 如果可能的话,我希望看到初始化和完成操作中内置的标签,以确保它们不会被意外忽略。 这可能意味着加密和解密不应该使用相同的初始化和完成方法。
  2. 我对在解密期间输出块在到达结束之前有复杂的感觉,因为在检查标签之前数据是不可信的(在处理完所有数据之前无法完成)。 我们需要非常仔细地评估这种权衡。
  3. 是否需要重置? 应该完成只是重置? 我们在增量哈希上这样做(但它们不需要新的 IV)

@bartonjs

  1. 类,正如在 BCL 中经常看到的那样,带有一个接口,您以后无法在不破坏所有内容的情况下对其进行扩展。 接口就像是一只终生的小狗……除非可以将默认接口方法视为该问题的解决方案。
    此外,来自抽象类型的密封类实际上更快(截至目前),因为 jitt 现在可以对方法进行虚拟化......所以它基本上是免费的。 接口调度不太好(仍然很好只是不太好)
  2. 我不知道你希望它如何工作? 我对当前的东西没什么兴趣,因为它太令人困惑了,我会直接修补所有合理的现代算法(将 3DES 留在其他类中:) 但我没有所有答案,所以你对此有任何进一步的想法吗?
  3. 持久键应该很容易。 在关键方法或持久性存储上创建扩展方法。
MyKeyStore.GetCipher();

它没有初始化。 它是一次性的,因此任何参考都可以通过正常的一次性模式丢弃。 如果他们试图设置键抛出一个无效的操作异常。

是的,当我不在手机上时,我将调整只读跨度。

@morganbr没问题...我只想看到它发生比什么都重要;)

  1. 你能给出一个关于你如何看待它的代码片段吗? 不确定它是否有效,但代码总能带来清晰
  2. 这很不幸,但你真的必须尽早吐出积木。 使用 hmac 和散列,你没有,但你没有临时数据,只是状态。 因此,在这种情况下,您将不得不缓冲未知数量的数据。 让我们看一下管道示例和 TLS。 我们可以写入 16k 的明文,但今天的管道缓冲区大小为 4k 页。 所以我们充其量想要加密/解密 4*4k。 直到最后你才给我答案,你需要分配内部内存来存储所有这些,然后我假设当我得到结果时把它扔掉? 或者你会擦它。 如果我解密 10mb 并且您在我现在不得不担心潜在内存使用之后保留该内存怎么办。
  3. 初始化/重置的事情不是 100%(不是你的想法,我当前的 API 形状)它不适合我,所以我愿意接受新的建议!

我对当前的东西没什么兴趣,因为它太令人困惑了,我会直接修补所有合理的现代算法(将 3DES 留在其他类中:)

问题是像 EnvelopedCms(或 EncryptedXml)这样的容器格式可能需要与 3DES-CBC、AES-CBC 等一起使用。有人想要解密用 ECIES/AES-256-CBC-PKCS7/HMAC-SHA-2 加密的东西-256 可能不会觉得他们在做陈旧而粗俗的事情。

如果它只应该用于 AE,那么这应该反映在名称中的某个地方。 现在它是通用的“密码”(我曾经/现在,在某个时候,会坐下来看字典/词汇表,看看是否有“操作模式下的加密算法”这个词,因为我认为“密码”==“算法”,因此是“Aes”)。

:) 我只是指出这不是我的主题领域,也不是我感兴趣的领域,所以我愿意在这个话题上听从你和社区的意见,我还没有考虑到这方面的含义。


在快速浏览完这些之后,一个选项是允许他们获取“密码”的实例或任何它所谓的类。 这可能不会在第一波中完成,但可以很快跟进。 如果 API 非常高效,那么我认为他们没有理由在内部做自己的事情,而这正是这个 API 的用例。

作为命名的一个侧边栏......我必须承认它是一个艰难的
Openssl =密码
红宝石 = 密码
Go = 带有用于 AEAD 等的鸭式接口的密码包
Java = 密码

现在我完全赞成与众不同,但是……有一种趋势。 如果有更好的东西是可能的,那很酷。

可能是“BlockModeCipher”……?

我做了一些更改,如果确定了更好的名称,我将更改命名。

当我开始尝试回答问题时,我意识到 API 已经缺少加密/解密区分,因此在您的示例中,它不知道是加密还是解密数据。 把它放进去可能会增加一些清晰度。

我可以想象 API 可以通过几种方式强制使用正确的标签(基于这是一个 AEAD API 的假设,而不仅仅是对称加密,因为我们已经有了 SymmetricAlgorithm/ICryptoTransform/CryptoStream)。 不要将这些视为规定,只是作为强制标记的示例。
按方法:

class Cipher
{
   void InitializeEncryption(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
   // Ensures decryptors get a tag
   void InitializeDecryption(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> tag);
   // Ensure encryptors produce a tag
    void FinishEncryption(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> tag);
   // Throws if tag didn't verify
   void FinishDecryption(ReadOnlySpan<byte> input, Span<byte> output);
   // Update and properties are unchanged, but GetTag and SetTag are gone
}

按班级:

class Cipher
{
    // Has properties and update, but Initialize and Finish aren't present
}
class Encryptor : Cipher
{
    void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv);
    void Finish(ReadOnlySpan<byte> input, Span<byte> output, Span<byte> tag);
}
class Decryptor : Cipher
{
   void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> tag);
   // Throws if tag didn't verify
   void Finish(ReadOnlySpan<byte> input, Span<byte> output);
}
class AesGCMEncryptor : Encryptor {}
class AesGCMDecryptor : Decryptor {}
}

也就是说,如果它在解密时没有缓冲,那么确保解密实际上完成并检查标签是否可行? 更新可以以某种方式找出是时候检查了吗? 这是 Dispose 应该做的事情吗? (Dispose 有点危险,因为您在处理对象时可能已经信任了数据)

至于命名,我们的先例是 SymmetricAlgorithm 和 AsymmetricAlgorithm。 如果这是针对 AEAD,一些想法可能是 AuthenticatedSymmetricAlgorithm 或 AuthenticatedEncryptionAlgorithm。

一些想法和 API 想法:

public interface IAEADConfig
{
    // size of the input block (plaintext)
    int BlockSize { get; }

    // size of the output per input-block;
    // typically a multiple of BlockSize or equal to BlockSize.
    int FeedbackSize { get; }

    // IV size; CAESAR completition uses a fixed-length IV
    int IVSize { get; }

    // CAESAR competition uses a fixed-length key
    int KeySize { get; }

    // CAESAR competition states that typical AEAD ciphers have a constant gap between plaintext length
    // and ciphertext length, but the requirement is to have a constant *limit* on the gap.
    int MaxTagSize { get; }

    // allows for AE-only algorithms
    bool IsAdditionalDataSupported { get; }
}

public interface ICryptoAEADTransform : ICryptoTransform
{
    // new AEAD-specific ICryptoTransform interface will allow CryptoStream implementation
    // to distinguish AEAD transforms.
    // AEAD decryptor transforms should throw on auth failure, but current CryptoStream
    // logic swallows exceptions.
    // Alternatively, we can create a new AEAD_Auth_Failed exception class, and
    // CryptoTransform is modified to catch that specific exception.
}

public interface IAEADAlgorithm : IDisposable, IAEADConfig
{
    // separates object creation from initialization/keying; allows for unkeyed factories
    void Initialize(ArraySegment<byte> key);

    void Encrypt(
        ArraySegment<byte> iv, // readonly; covered by authentication
        ArraySegment<byte> plaintext, // readonly; covered by authentication
        ref ArraySegment<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
        ArraySegment<byte> additionalData = default(ArraySegment<byte>) // readonly; optional; covered by authentication
        ); // no failures expected under normal operation - abnormal failures will throw

    bool Decrypt(
        ArraySegment<byte> iv, // readonly
        ArraySegment<byte> ciphertext, // readonly
        ref ArraySegment<byte> plaintext, // must be of at least [ciphertext_length - MaxTagSize] length.
        ArraySegment<byte> additionalData = default(ArraySegment<byte>), // readonly; optional
        bool isAuthenticateOnly = false // supports Authentication-only mode
        );// auth failures expected under normal operation - return false on auth failure; throw on abnormal failure; true on success

    /*  Notes:
        * Array.LongLength should be used instead of Array.Length to accomodate byte arrays longer than 2^32.
        * Ciphertext/Plaintext produced by Encrypt()/Decrypt() must be determined *only* by method inputs (combined with a key).
          - (ie. if randomness or other hidden inputs are needed, they must be a part of iv)
        * Encrypt()/Decrypt() are allowed to write to ciphertext/plaintext segments under all conditions (failure/success/abnormal)
          - some implementations might be more stringent than others, and ex. not leak decrypted plaintext on auth failures
    */

    ICryptoAEADTransform CreateEncryptor(
        ArraySegment<byte> key,
        ArraySegment<byte> iv,
        ArraySegment<byte> additionalData = default(ArraySegment<byte>)
        );

    ICryptoAEADTransform CreateDecryptor(
        ArraySegment<byte> key,
        ArraySegment<byte> iv,
        ArraySegment<byte> additionalData = default(ArraySegment<byte>)
        );

    // Streaming AEAD can be done with good-old CryptoStream
    // (possibly modified to be AEAD-aware).
}

@sdrapkin ,感谢您的贡献。 标签应该放在你的 API 中的什么位置? 它们是密文的隐含部分吗? 如果是这样,这意味着某些算法实际上没有的协议。 我想了解人们可能关心哪些协议,以了解隐式标签放置是否可以接受,或者是否需要单独携带。

@morganbr我也注意到了加密/解密问题,但今晚没有时间修复,对您的设计感到非常高兴。 我更喜欢类的方法,因为它允许更好的积极回收(键和 IV 的缓冲区可以加起来)。

至于处置前的检查。 不幸的是,不可能告诉手术结束。

由于前面提到的版本问题,我想说@sdrapkin接口是不行的。 除非我们将来依赖默认植入。 接口调度也较慢..数组段也是我们的,因为跨度是更通用的较低原语。 但是,如果以后有需求,可以为 arraysegment 添加扩展方法。

你的一些属性很有趣,所以当我在电脑上而不是在手机上时会更新。

全方位的好评!

@morganbr标签是密文的一部分。 这是在CAESAR API (包括 AES-GCM)之后建模的。

@Drawaes我只使用接口来说明想法 - 我对静态方法/类非常满意。 跨度不存在。 我不在乎可能会或可能不会发生什么——它不在 NetStandard2 中,也不在严肃项目实际使用的普通 .NET 中(是的,是的,我知道它在 Core 中,但 Core 是一个玩具目前)。 我很乐意亲自考虑 Span当我看到它时 - 直到那时 ArraySegment是实际发布的最接近的 NetStandard API。

我将深入了解有用的 CAESAR API。

至于 Span,我相信它在 2.1 的时间范围内发布,希望这与该 API 的第一个实现发布的时间相同(或者至少可能的最早时间)。

如果您查看当前的预发布 nuget 包,它支持的 .net 标准 1.0,并且没有计划在发布时更改它。

也许@stephentoub可以确认,正如我们所说,他正在努力在整个框架中添加基于 Span 的 API。

(用于 Span 的 Nuget)[https://www.nuget.org/packages/System.Memory/4.4.0-preview2-25405-01]

所以我会说它是全新 API 的唯一真正选择。 然后可以添加扩展方法等来获取 ArraySegment,如果您这样选择并且如果它足够有用,则可以将其添加到框架中,但是将 ArraySegment 转换为 Span 是微不足道的,但另一种方式需要复制数据。

我在上面的那个 API 中看到的问题是,它对任何“分块”数据的性能都是一场灾难。 例如网络流量,如果单个经过身份验证的块被拆分为来自现有流的多个读取,我需要将其全部缓冲到单个 [插入数据结构] 并一次加密/解密。 失败了所有尝试对该数据进行任何类型的零复制。

诸如 Pipelines 提供的网络框架设法避免了几乎所有的副本,但是如果它们在这个 API 中遇到任何类型的加密,那么所有这些都消失了。

单独的配置对象(或包)实际上已经参与了最近关于我一直拥有的另一个 API 的讨论。 我原则上并不反对它,因为如果它在未来增长,在主对象上拥有大量属性可能会变得一团糟。

我想到了几件事。

  • 当前的提案将 TagSize 称为 out 值(嗯,一个 get-only-property)。 但是对于 GCM 和 CCM 来说,它都是加密的输入(并且可以从解密中推导出来,因为您已经提供了实际的标签)。
  • 该提案假设输入和输出可以同时发生,并且是零散的。

    • IIRC CCM 不能进行流加密(明文的长度是算法第一步的输入)。

    • 填充模式延迟解密(至少一个)块,因为在调用 Final 之前,它们不知道是否有更多数据到来/当前块是否需要删除填充

  • 一些算法可能会认为 AD 元素在操作开始时是必需的,使其更像是一个 Init/ctor 参数,而不是后期绑定关联。

我不知道容器格式(EnvelopedCms、EncryptedXml)是否需要提取密钥,或者是否只是由他们来生成并记住它(只要他们需要写下来)。

(显然我昨天没有点击“评论”按钮,所以在 1910Z “我做了一些更改”之后它不会确认任何内容)

真正的标签大小需要是可变的。 同意。

如果我们现在只看加密,以简化用例。 你是对的,有些密码不会返回任何东西,更少或更多。 如果您没有提供足够大的缓冲区会发生什么,这是一个普遍的问题。

在使用 span 的新 TextEncoding 接口上,有一个建议是让返回类型为枚举来定义是否有足够的空间来输出,而实际上将大小写在“out”参数中。 这是一种可能。

在 CCM 的情况下,我只想说它什么都不返回,并且必须在内部缓冲,直到你调用完成,此时它会想要转储整个批次。 如果您将所有数据都放在一个块中(在这种情况下可能会有更好的名称),那么没有什么可以阻止您将完成作为您的第一个调用。 或者,如果您尝试更新这些密码,则可能会抛出。 例如,如果您尝试在 CCM 上继续,CNG 会返回一个无效的大小错误。

至于什么时候设置了tag进行解密,往往要读完整个数据包才知道,如果我们以TLS为例,我们可能要读取8*2k的网络数据包才能到达最后的tag 16k 块。 所以我们现在必须在开始解密之前缓冲整个 16k 并且没有机会重叠(我并不是说这将用于 TLS 只是因为 IO 绑定过程对于这些类型的密码很常见,无论是磁盘还是网络)。

@Drawaes重新。 分块流和缓冲限制:
你必须选择你的战斗。 您将无法创建一个与 AE 世界中每一个美好​​目标相一致的统一 API - 并且有很多这样的目标。 前任。 Inferno中有一个不错的分块 AEAD 流,但无论如何它都不是标准,而且这样的标准也不存在。 在更高的层次上,目标是“安全通道”(参见thisthisthis )。

但是,我们现在需要考虑得更小一些。 分块/缓冲区限制甚至不在标准化工作的雷达上(“ AEADs with large plaintexts ”部分)..

加密/解密操作基本上是关于转换的。 这些转换需要缓冲区,并且不是就地的(输出缓冲区必须大于输入缓冲区 - 至少对于加密转换)。

RFC 5116可能也很有趣。

@Drawaes ,有趣的是你提出了 TLS。 我认为 SSLStream(如果它使用这个 API)不能将任何未经身份验证的结果返回给应用程序,因为应用程序将没有任何方法来保护自己。

当然,但那是 SSLStreams 问题。 我已经在管道之上构建了这个确切的东西的原型(在协议级别调用 CNG 和 OpenSSL 的加密位的托管 TLS)。 逻辑非常简单,加密数据进来,在适当的位置解密缓冲区,附加到出站并重复,直到你到达标签。 在标签调用完成...

如果它抛出关闭管道。 如果它没有抛出,那么刷新允许下一个阶段在同一个线程上或通过调度工作。

我的概念证明还没有为黄金时段做好准备,但是通过使用它并避免大量复制等,它显示出非常不错的性能提升;)

任何网络的问题是管道中的东西开始分配自己的缓冲区,而不是尽可能多地使用已经在系统中移动的缓冲区。

OpenSsl 的 crypt 和 CNG 有同样的方法 Update,Update,Finish。 Finish 可以像讨论的那样输出标签。 更新必须是块大小(对于 CNG),对于 OpenSsl,它会做最少的缓冲来达到块大小。

由于它们是原语,我不确定我们是否会期望它们具有更高级别的功能。 如果我们要设计一个“用户”级别的 API 而不是构建这些 API 的原语,那么我会争辩说,密钥生成、IV 构建和整个经过身份验证的块都应该实现,所以我想这取决于这个 API 的目标级别到底是什么。

错的按钮

@blowdart ,他对随机数管理有一些有趣的想法。

因此,nonce 管理基本上是一个用户问题,并且特定于他们的设置。

所以......让它成为一个要求。 您必须插入 nonce 管理......并且不提供默认实现,或者根本不提供任何实现。 这个,而不是简单的

cipher.Init(myKey, nonce);

迫使用户做出了解风险的特定手势。

@blowdart的想法可能有助于解决随机数管理问题和算法之间的差异。 我同意不具有内置实现可能很重要,以确保用户了解 nonce 管理是他们需要解决的问题。 这样的东西看起来怎么样?

interface INonceProvider
{
    public void GetNextNonce(Span<byte> writeNonceHere);
}

class AesGcmCipher : Cipher
{
    public AesGcmCipher(ReadOnlySpan<byte> key, INonceProvider nonceProvider);
}

// Enables platform-specific hardware keys
class AesGcmCng : Cipher
{
    public AesGcmCng(CngKey key, INonceProvider nonceProvider);
}

// Example of AEAD that might not need a nonce
class AesCBCHmac : Cipher
{
    public AesCBC(ReadOnlySpan<byte> key)
}

class Cipher
{
    // As above, but doesn't take keys, IVs, or nonces
}

但是 INonceProvider 的意义何在? 它只是一个额外的接口/类型,如果 Init 只需要一个 none 并且需要在开始任何块之前调用,那么如果没有额外的/接口,那不是同样的事情吗?

我也不是加密专家,但 AES 不需要 IV(这不是随机数,但需要由用户提供?)

它只是一个额外的接口/类型

这就是重点。 它本质上说 _nonce management_ 是一个问题,而不仅仅是传入一个归零甚至随机的字节数组。 如果人们将GetNextNonce解释为“返回与上次不同的东西”,它也可能有助于防止无意的重用。

对于没有随机数管理问题的算法(如 AES SIV 或 AES+CBC+HMAC),不需要它也很有帮助。

确切的 IV/nonce 要求因模式而异。 例如:

  • AES ECB 不需要 nonce 或 IV
  • AES GCM 需要一个 96 位随机数,该随机数不得重复使用,否则密钥的安全性会中断。 只要不重复使用随机数,低熵就可以了。
  • AES CBC 需要一个 128 位的 IV,它必须是随机的。 如果 IV 重复,它只显示之前是否发送过相同的消息。
  • AES SIV 不需要显式 IV,因为它是从其他输入派生的。

AES CBC 需要 IV 对吗? 那么你会有一个 InitializationVectorProvider 吗? 它不是随机数,而是
nonce like 和重用最后一个块导致了 tls 攻击,因为可以预测 iv。 您明确不能对 CBC 使用顺序随机数。

是的,但是 IV 不是 nonce,所以你不能使用术语 nomce provider

我并不是要暗示 AES CBC 不需要 IV——它确实需要。 我只是想推测一些从其他数据中推导出 IV 的方案。

当然,我想我的观点是我通常喜欢它......我可以汇集提供者;)但要么将其称为 iv 提供者,要么有 2 个接口以明确意图

@morganbr INonceProvider工厂传递给密码构造函数是一个糟糕的设计。 它完全忽略了_nonce_ 本身并不存在的事实:“_...used once_”约束具有_context_。 在 CTR 和 GCM(使用 CTR)模式的情况下,_nonce_ 的 _context_ 是 _key_。 IE。 _nonce provider_ 必须返回一个在特定 _key_ 的上下文中仅使用一次的 nonce。

由于您提议的 API 中的INonceProvider不是密钥感知的,因此它无法生成正确的随机数(除了通过随机性,这不是随机数,即使位空间足够大以使统计随机性起作用安全)。

我不完全确定这个讨论线程的目标是什么。 讨论了各种 Authenticated-Encryption 设计思想……好的。 已经内置在 .NET Core 中的 Authenticated Encryption 接口怎么样——特别是在它的 ASP.NET API 中? 有IAuthenticatedEncryptor等。所有这些功能都已经实现、可扩展,并作为 .NET Core 的一部分提供。 我并不是说 DataProtection 加密是完美的,但是否计划忽略它们? 改变他们? 同化还是重构?

DataProtection 加密由@GrabYourPitchforks (Levi Broderick) 构建。 他知道主题,他的意见/输入/反馈对这个社区最有价值。 我和任何人一样喜欢以加密为主题的娱乐,但如果有人想认真对待加密 API 设计,那么应该聘请已经在 MS 团队中的实际专家。

@sdrapkin ,需要知道关键的随机数提供者是一个好点。 我想知道是否有合理的方法来修改这些 API 来强制执行。

DataProtection 是一个很好的 API,但它是一个更高级别的构造。 它封装了密钥生成和管理、IV 和输出协议。 如果有人需要在不同协议的实现中使用(比如说)GCM,DataProtection 无法做到这一点。

.NET 加密团队包括@bartonjs@blowdart和我自己。 当然,如果@GrabYourPitchforks想插话,他非常欢迎。

我同意@morganbr的观点,因为这应该是一个低级的原语(实际上它在标题中这么说)。 虽然数据保护等被设计为直接在用户代码中使用并减少了自己的能力,但我看到这个原语的方式是允许框架和库在一个共同的基础上构建更高级别的结构。

考虑到这一点,如果在提供密钥的任何时候都必须提供提供程序,那么提供程序就可以了。 它确实让它有点混乱,让我解释一下使用 TLS(众所周知,它使用 AES 块模式来处理网络流量)。

我得到一个“框架”(互联网的 MTU ~1500 可能超过 2 + TU)。 它包含随机数(或剩余 4 个字节的随机数的一部分“隐藏”)然后我必须在外壳“提供程序”上设置此值,然后调用解密并完成我的解密缓冲区循环以获得单个纯文本.

如果你对此感到满意,我可以忍受它。 我热衷于推动这一进程,如此热衷于将上面的设计更新为我们可以达成共识的东西。

感谢您分叉讨论,获得一些空闲时间来讨论这个问题。 @Drawaes ,您能否确认/更新顶帖作为这个不断发展的对话的黄金标准/目标? 如果没有,你能更新它吗?

我看到当前的提案有一个致命的问题,然后是过于健谈的其他问题。

// current proposed usage
using (var cipher = new AesGcmCipher(bitsize: 256))
{
    cipher.Init(myKey, nonce);
    while (!inputSource.EOF)
    {
        var inputSpan = inputSource.ReadSpan(cipher.BlockSize);
        cipher.Update(inputSpan);
        outputSource.Write(inputSpan);
    }
    cipher.AddAssociatedData(extraInformation); // <= fatal, one can't just do this
    cipher.Finish(finalBlockData);
    cipher.GetTag(tagData);
}

如果您查看真正的 AEAD 原语,隐私数据和经过身份验证的数据是混合锁步的。 有关 Auth Data 1 和 CipherText1,请参见此内容。 这当然会持续多个块,而不仅仅是 1。highlighted

既然全世界都是模因,无法抗拒,对不起:)
Can't resist

此外,API 似乎与 new、init、update 等有关。我建议这个程序员的模型

// proposed, see detailed comments below
using (var cipher = new AesGcmCipher(myKey, iv, aad)) // 1
{
    // 2
    while (!inputSource.EOF) 
    {
        var inputSpan = inputSource.ReadSpan(16411); // 3
        var outSpan = cipher.Encrypt(inputSpan); // 4
        outputSource.Write(outSpan); 
    }    
    var tag = cipher.Finish(finalBlockData); // 5
}
  1. 通常 AAD << 纯文本,所以我看到cipher.Init(mykey, nonce, aad);整个 AAD 作为缓冲区传递,然后密码在其余的潜在千兆字节 + 流上进行处理。 (例如BCryptEncrypts 的CipherModeInfo 参数)。 另外,myKey 的大小已经确定了 AES128、192、256,不需要其他参数。
  2. 如果调用者希望重用现有类、现有 AES 常量并在 AES 密钥相同的情况下跳过 AES 子密钥生成,则 Init 将成为可选 API
  3. cipher 的 API 应该像大多数其他加密 API 甚至现有的 .NET API 一样保护调用者免受块大小管理内部的影响。 调用者关心为其用例优化的缓冲区大小,例如通过 16K+ 缓冲区的网络 IO)。 使用大于 16K 的素数进行演示以测试实现假设
  4. inputSpan 是只读的。 并输入。 所以需要一个outSpan
  5. Update() 加密还是解密? 只需使用 Encrypt 和 Decrypt 接口来匹配开发人员的心智模型。 tag也是此时最重要的所需数据,返回那个。

实际上更进一步,为什么不只是

using (var cipher = new AesGcmCipher(myKey, iv, aad))
{
    var tag = cipher.EncryptFinal(inputSpan, outputSpan);
}

另外,请远离INonceProvider之类的。 加密原语不需要这个,只需坚持byte[] iv(我最喜欢的小数据)或Span (所谓的新酷但太多抽象恕我直言)。 Nonce 提供程序在上面的一层运行,它的结果可能就是这里看到的iv

使原语变得如此原始的问题是人们只会错误地使用它们。 通过提供者,我们至少可以在使用它们时强加一些想法。

我们一般谈论的是 AEAD,其中 GCM 是特定的。 所以首先,一般情况( iv )应该驱动设计,而不是具体情况( nonce )。

其次,仅仅从byte[] ivGetNextNonce(Span<byte> writeNonceHere)是如何真正解决随机数问题的? 您只是更改了问题的名称/标签,同时使问题变得比应有的复杂。

第三,既然我们正在研究iv保护政策,我们是否也应该研究关键保护政策? 密钥分配策略如何? 这些显然是更高层次的担忧。

最后,nonce 在更高层的使用中非常依赖情境。 您不希望有一个脆弱的架构,其中跨层关注点耦合在一起。

坦率地说,如果我们可以隐藏原语,除非有人做出手势说我知道我在做什么,我会推动这一点。 但我们不能。 那里有太多糟糕的加密实现,因为人们虽然“哦,这是可用的,我会使用它”。 看看 AES 本身,我将只使用它而不使用 HMAC。

我希望看到 API 在默认情况下是安全的,如果这意味着更多的痛苦,那么坦率地说,我完全赞成。 99% 的开发人员在加密方面不知道他们在做什么,让 1% 的开发人员更容易做到这一点应该是次要的。

跨度不存在。 我不在乎可能会或可能不会发生什么——它不在 NetStandard2 中

@sdrapkin @Drawaes指出Span<T>是 .NET Standard 1.0 ,因此可以在任何框架上使用。 它也比ArraySegment<T>更安全,因为它只允许您访问引用的实际窗口; 而不是整个数组。

ReadOnlySpan<T>防止修改该窗口; 再次与数组段不同,其中传递的任何内容都可以修改和/或保留对传递数组的引用。

Span 应该是同步 apis 的通用方法(事实上,使用 Span 的 api 还可以处理 stackalloc'd、本机内存以及数组;是锦上添花)

IE
使用 ArraySegment 只读是通过文档建议的; 并且不会阻止越界读取/修改

void Encrypt(
    ArraySegment<byte> iv, // readonly; covered by authentication
    ArraySegment<byte> plaintext, // readonly; covered by authentication
    ref ArraySegment<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
    ArraySegment<byte> additionalData = default(ArraySegment<byte>) // readonly; optional; covered by authentication
    );

然而,对于 Span,只读是由 api 强制执行的; 以及被阻止的数组的越界读取

void Encrypt(
    ReadOnlySpan<byte> iv, // covered by authentication
    ReadOnlySpan<byte> plaintext, // covered by authentication
    Span<byte> ciphertext, // must be of at least [plaintext_length + MaxTagSize] length. iv is not part of ciphertext.
    ReadOnlySpan<byte> additionalData = ReadOnlySpan<byte>.Empty) // optional; covered by authentication
    );

它用参数更好地传达意图; 并且在越界读/写方面更不容易出错。

@benaadams @Drawaes从未说过Span<T>在 NetStandard 中(任何已发布的 NetStandard )。 他所说的是(1)同意Span<T>不在任何已发布的 NetStandard 中; (2) Span<T>将是_“在 2.1 时间范围内发货”_。

然而,对于这个特定的 Github 问题,(只读) Span<T>的讨论现在是乱七八糟的——没有明确说明要设计的 API 的范围或目的。

我们要么使用原始的低级原始 AEAD API(例如,类似于 CAESAR):

  • 优点:非常适合 AES-GCM/CCM,来自良好来源(NIST、RFC)的现有测试向量。 @sidshetye会很高兴。 @blowdart会思考_“让原语变得如此原始”_,但最终会看到阴阳,因为原语是原始的,没有办法保护它们。
  • 缺点:专家用户(众所周知的 1%)会负责任地使用低级 API,而其他非专家用户(99%)会滥用它来编写损坏的 .NET 软件,而这些软件将负责绝大多数 .NET CVE,这将极大地加深 .NET 是一个不安全平台的看法。

或者我们使用高级不可滥用或抗性 AEAD API:

  • 优点:99% 的非专家用户会继续犯错误,但至少在 AEAD 代码中不会。 @blowdart的 _“我希望看到 API 在默认情况下是安全的”_ 在生态系统中产生了深刻的共鸣,安全、繁荣和良好的因果报应降临在所有人身上。 许多好的 API 设计和实现已经可用。
  • 缺点:没有标准。 没有测试向量。 关于 AEAD 是否是针对高级在线流 API 的正确目标尚未达成共识(剧透:不是 - 请参阅Rogaway 的论文)。

或者,我们两者都做。 或者我们进入分析瘫痪状态,还不如现在关闭这个问题。

我强烈认为,作为核心语言的一部分,加密需要有一个坚实的、低级的基础 API。 一旦你有了这些,创建高级 API 或“训练轮”API 就可以由核心或社区快速桥接。 但我挑战任何人优雅地做相反的事情。 加上主题是“密码的一般低级原语”!

@Drawaes是否有一个时间表来收敛和解决这个问题? 除了此类 GitHub 警报之外,是否有让非 Microsoft 人员参与的计划? 就像一个 30 分钟的电话会议? 我试图远离兔子洞,但我们打赌 .NET 核心加密将处于一定的成熟度和稳定性水平......因此可以对此类讨论进行分类。

我们仍在关注并努力解决这个问题。 我们已经会见了 Microsoft 密码学委员会(一组研究人员和其他专家,他们建议 Microsoft 使用密码学), @bartonjs将很快分享更多信息。

基于少量数据流涂鸦和加密委员会的建议,我们提出了以下建议。 我们的模型是 GCM、CCM、SIV 和 CBC+HMAC(请注意,我们现在不是在谈论 SIV 或 CBC+HMAC,只是我们想证明形状)。

```C#
公共接口 INonceProvider
{
只读跨度GetNextNonce(int nonceSize);
}

公共抽象类 AuthenticatedEncryptor : IDisposable
{
公共 int NonceOrIVSizeInBits { 获取; }
公共 int TagSizeInBits { 获取; }
公共布尔 SupportsAssociatedData { 获取; }
公共只读跨度LastNonceOrIV { 得到; }
公共只读跨度最后标签 { 获取; }

protected AuthenticatedEncryptor(
    int tagSizeInBits,
    bool supportsAssociatedData,
    int nonceOrIVSizeInBits) => throw null;

protected abstract bool TryEncrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData,
    Span<byte> encryptedData,
    out int bytesWritten,
    Span<byte> tag,
    Span<byte> nonceOrIVUsed);

public abstract void GetEncryptedSizeRange(
    int dataLength,
    out int minEncryptedLength,
    out int maxEncryptedLength);

public bool TryEncrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData,
    Span<byte> encryptedData,
    out int bytesWritten) => throw null;

public byte[] Encrypt(
    ReadOnlySpan<byte> data,
    ReadOnlySpan<byte> associatedData) => throw null;

// some variant of the Dispose pattern here.

}

公共密封类 AesGcmEncryptor : AuthenticatedEncryptor
{
公共 AesGcmEncryptor(ReadOnlySpankeySize, INonceProvider nonceProvider)
:基数(128,真,96)
{
}
}

公共密封类 AesCcmEncryptor : AuthenticatedEncryptor
{
公共 AesCcmEncryptor(
只读跨度钥匙,
int nonceSizeInBits,
INonceProvider nonceProvider,
int tagSizeInBits)
: 基数(tagSizeInBits, true, nonceSizeInBits)
{
根据算法规范验证 nonceSize 和 tagSize;
}
}

公共抽象类 AuthenticatedDecryptor : IDisposable
{
公共抽象布尔 TryDecrypt(
只读跨度标签,
只读跨度随机数OrIV,
只读跨度加密数据,
只读跨度关联数据,
跨度数据,
out int bytesWritten);

public abstract void GetEncryptedSizeRange(
    int encryptedDataLength,
    out int minDecryptedLength,
    out int maxDecryptedLength);

public byte[] Decrypt(
    ReadOnlySpan<byte> tag,
    ReadOnlySpan<byte> nonceOrIV,
    ReadOnlySpan<byte> encryptedData,
    ReadOnlySpan<byte> associatedData) => throw null;

// some variant of the Dispose pattern here.

}

公共密封类 AesGcmDecryptor : AuthenticatedDecryptor
{
公共 AesGcmDecryptor(ReadOnlySpan键)=> 抛出空值;
}

公共密封类 AesCcmDecryptor : AuthenticatedDecryptor
{
公共 AesCcmDecryptor(ReadOnlySpan键)=> 抛出空值;
}
```

该提议消除了数据流。 在这一点上,我们真的没有很大的灵活性。 现实世界的需求(低)与相关风险(GCM 极高)或不可能(CCM)相结合意味着它已经消失了。

该提案使用外部化的 nonce 源进行加密。 我们不会有这个接口的任何公共实现。 每个应用程序/协议都应该自己将密钥绑定到上下文,以便它可以适当地输入内容。 虽然对 TryEncrypt 的每次调用只会对 GetNextNonce 进行一次调用,但不能保证特定的 TryEncrypt 一定会成功,因此仍然需要由应用程序来了解这是否意味着它应该重试随机数。 对于 CBC+HMAC,我们将创建一个新接口 IIVProvider,以避免混淆术语。 对于 SIV,IV 是构造的,因此没有可接受的参数; 并且根据规范,随机数(使用时)似乎只是被视为关联数据的一部分。 因此,至少 SIV 表明将 nonceOrIV 作为 TryEncrypt 的参数并不普遍适用。

TryDecrypt 肯定会抛出无效标签。 如果目标太小,它只会返回 false(根据 Try-methods 的规则)

绝对开放以供反馈的事情:

  • 大小应该以位(如规范的重要部分)或字节为单位(因为无论如何只有 %8 值是合法的,而且我们总是要划分,规范的某些部分谈论诸如以字节为单位的随机数大小之类的东西)?
  • 参数名称和顺序。
  • LastTag/LastNonceOrIV 属性。 (使它们在公共 TryEncrypt 上成为(可写)跨度仅意味着存在三个可能太小的缓冲区,通过使它们位于“Try”更清晰的一侧;基类可以承诺它将永远不要提供太短的缓冲区。)。
  • 提供一个 AE 算法,这对它不起作用。
  • 是否应将associatedData移动到末尾并默认为ReadOnlySpan<byte>.Empty

    • 还是重载了它省略了它?

  • 有没有人想表达对byte[] -returning 方法的爱或恨? (Low-allocation可以通过Span方式实现,这只是为了方便)
  • 尺寸范围方法在最后被固定了。

    • 他们的目的是

    • 如果目标跨度小于 min,则立即返回 false。

    • byte[] -returning 方法将分配一个最大的缓冲区,然后根据需要 Array.Resize 它。

    • 是的,对于 GCM 和 CCM min=max=input.Length ,但对于 CBC+HMAC 或 SIV 不是这样

    • 是否有需要考虑关联数据长度的算法?

绝对是字节 - 而不是位。
不知道密钥的 Nonce 提供程序是一个大错误。

不知道密钥的 Nonce 提供程序是一个大错误。

你可以随心所欲地编写你的 nonce 提供者。 我们不提供任何东西。

确定性清理/ IDisposable怎么样?

确定性清理/IDisposable 怎么样?

好决定。 将其添加到 AuthenticatedEncryptor/AuthenticatedDecryptor。 我认为他们不应该在 nonce 提供者上探测可处置性,调用者可以只堆叠 using 语句。

INonceProvider概念/目的对我来说毫无意义(与其他人相呼应)。 让原语是原始的 - 以与传递密钥相同的方式传递随机数(即作为字节 - 无论声明如何)。 没有 AE/AEAD 规范强制使用一种算法来生成/派生随机数 - 这是更高层的责任(至少在 let-primitives-be-primitive 模型中)。

没有流媒体? 真的吗? 在核心基础级别强制从 AES-GCM 等流密码中删除流的理由是什么?

例如,您的加密委员会建议我们审查的这两个最近的场景是什么?

  1. 客户有 10-30GB 的大型医疗保健文件。 核心仅在两台机器之间看到数据流,因此它是一个传递流。 显然,每个 10GB 文件都会发布一个新密钥,但您刚刚使每个这样的工作流程都变得无用。 您现在希望我们 a) 缓冲数据(内存,无管道) b) 执行加密(管道中的所有机器现在都处于空闲状态!) c) 写出数据(在 a 和 b 之后写入的第一个字节为 100%完毕) ? 请告诉我你在开玩笑。 你们故意将“加密是一种负担”放回游戏中。

  2. 物理安全单元具有多个 4K 流,这些流也针对静态场景进行了加密。 新的密钥发行发生在 15GB 边界。 你建议缓冲整个剪辑?

我没有看到来自社区的任何意见,人们实际构建现实世界的软件,要求删除流媒体支持。 但随后团队从社区对话中消失了,在内部挤成一团,然后又回来了一些没人要求的东西,一些东西会扼杀真正的应用程序并再次强调“加密既慢又昂贵,跳过它?”

您可以提供EncryptEncryptFinal来支持这两个选项,而不是将您的决定强加给整个生态系统。

优雅的设计消除了复杂性,而不是控制。

在核心基础级别强制从 AES-GCM 等流密码中删除流的理由是什么?

我认为这就像

该提议消除了数据流。 在这一点上,我们真的没有很大的灵活性。 现实世界的需求(低)与相关风险(GCM 极高)或不可能(CCM)相结合意味着它已经消失了。

GCM 有太多糟糕的时刻允许密钥恢复。 如果攻击者可以选择密文并观察标签验证之前的流输出,他们就可以恢复密钥。 (或者其中一位密码分析家告诉我)。 实际上,如果在标签验证之前的任何时候都可以观察到任何 GCM 处理的数据,那么密钥就会受到损害。

我很确定加密委员会会建议不要在第一种情况下使用 GCM,而是使用 CBC+HMAC。

如果您的第二种情况是 4k 帧,并且您正在加密每个 4k 帧,那么这适用于此模型。 在您取回字节之前,每个 4k + nonce + 标签帧都会被解密和验证,因此您永远不会泄露密钥流/密钥。

为了比较:我目前正在开发这个“让原语成为原始”的加密 API。 是我的认证加密课程。

对我来说,能够独立于密钥谈论加密原语是很有用的。 例如,我经常想将一个特定的原语插入一个适用于任何 AEAD 算法的方法中,并将密钥的生成等留给该方法。 因此有一个AeadAlgorithm类和一个单独的Key类。

另一个已经防止了几个错误的非常有用的事情是使用不同的类型来表示不同形状的数据,例如KeyNonce ,而不是使用普通的byte[]Span<byte>来表示所有内容。


AeadAlgorithm API(点击展开)

public abstract class AeadAlgorithm : Algorithm
{
    public int KeySize { get; }

    public int NonceSize { get; }

    public int TagSize { get; }

    public byte[] Decrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext)

    public void Decrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        Span<byte> plaintext)

    public byte[] Encrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> plaintext)

    public void Encrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> plaintext,
        Span<byte> ciphertext)

    public bool TryDecrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        out byte[] plaintext)

    public bool TryDecrypt(
        Key key,
        Nonce nonce,
        ReadOnlySpan<byte> associatedData,
        ReadOnlySpan<byte> ciphertext,
        Span<byte> plaintext)
}

@bartonjs他/她是正确的,您需要依靠程序在身份验证之前不输出。 因此,例如,如果您尚未进行身份验证(或尚未进行身份验证),您可以控制块的输入,因此知道输出并从那里向后工作......

例如,中间人攻击可以将已知块注入 cbc 流并执行经典的位翻转攻击。

不知道如何解决大块数据问题,这要归功于用串行随机数或类似的东西对它们进行分块......ala TLS。

好吧,让我重新表述一下,但仅限于网络小型案例,这对于通用库来说是不够的。

本着开放的精神,是否可以透露 Microsoft 密码学审查委员会的成员(最好是审查该主题的特定成员的评论/意见)? Brian LaMacchia还有谁?

_使用逆向心理学:_

我很高兴流媒体 AEAD 出来了。 这意味着对于普通乔来说, Inferno仍然是唯一实用的基于CryptoStream的流式 AEAD。 谢谢 MS Crypto 审查委员会!

基于@ektrah的评论,他(她?)的方法是由我之前引用过的RFC 5116驱动的。 RFC 5116 中有许多值得注意的引用:

3.1。 对 Nonce 生成的要求
对于安全性而言,nonce 的构造方式必须遵循以下要求:对于经过身份验证的加密操作的每次调用,对于密钥的任何固定值,每个 nonce 值都是不同的。
...

  1. AEAD算法规范要求
    每个 AEAD 算法必须接受长度在 N_MIN 和 N_MAX 八位字节之间的任何 nonce,其中 N_MIN 和 N_MAX 的值特定于该算法。 N_MAX 和 N_MIN 的值可以相等。 每个算法都应该接受一个长度为十二 (12) 个八位字节的随机数。 如下所述的随机或有状态算法可以有一个 N_MAX 值为零。
    ...
    Authenticated Encryption 算法可以包含或使用随机源,例如,用于生成包含在密文输出中的内部初始化向量。 这种 AEAD 算法称为随机化; 但请注意,只有加密是随机的,而解密始终是确定性的。 随机算法的 N_MAX 值可能等于 0。

Authenticated Encryption 算法可以包含在加密操作调用之间维护的内部状态信息,例如,以允许构造不同的值,这些值被算法用作内部随机数。 这种 AEAD 算法称为有状态算法。 即使应用程序输入零长度随机数,算法也可以使用此方法提供良好的安全性。 一个有状态的算法可以有一个等于 0 的 N_MAX 值。

一个可能值得探索的想法是零长度/空 Nonce 的传递,这甚至可能是默认值。 这种“特殊” Nonce 值的传递将随机化实际的 Nonce 值,该值将作为 Encrypt 的输出提供。

如果INonceProvider因为“原因”而保留,另一个想法是添加一个Reset()调用,每次AuthenticatedEncryptor重新设置密钥时都会触发该调用。 另一方面,如果计划是永远不会重新加密AuthenticatedEncryptor实例,如果我们想构建流式块加密 API(例如块 = 网络数据包),这将破坏 GC,并且每个块都必须是使用不同的密钥加密(例如 Netflix MSL协议、Inferno 等)。 特别是对于我们想要维护一个 AEAD 引擎池并从该池中借用实例来执行 enc/dec 的并行 enc/dec 操作。 让我们给GC一些爱:)

在我看来,加密原语的唯一目的是实现设计良好的更高级别的安全协议。 每个这样的协议都坚持以自己的方式生成随机数。 例如:

  • TLS 1.2 遵循 RFC 5116 的建议,将 4 字节的 IV 与 8 字节的计数器连接起来,
  • TLS 1.3 xor 是一个用 12 字节 IV 填充到 12 字节的 8 字节计数器,
  • 对于 AES-GCM,Noise 使用以大端字节顺序填充到 12 个字节的 8 字节计数器,对于 ChaCha/Poly,使用以小端字节顺序填充到 12 个字节的 8 字节计数器。

GCM 对于典型随机数大小(96 位)的随机随机数来说太脆弱了。 而且我不知道任何实际支持随机随机数的安全协议。

对提供加密原语的更多 API 的需求并不多。 99.9% 的开发人员需要安全相关场景的高级秘诀:在数据库中存储密码、加密静态文件、安全传输软件更新等。

但是,这种高级配方的 API 很少见。 唯一可用的 API 通常只有 HTTPS 和加密原语,这迫使开发人员推出自己的安全协议。 IMO 的解决方案不是在设计用于处理原语的 API 方面投入大量精力。 它是高级配方的 API。

感谢大家的反馈! 几个问题:

  1. 虽然流式解密可能会发生灾难性的失败,但流式加密可能是可行的。 流式加密(以及非流式选项)但只有非流式解密听起来更有用吗? 如果是,有几个问题需要解决:
    一个。 某些算法(CCM、SIV)实际上并不支持流式传输。 我们应该将流式加密放在基类上并缓冲流式输入还是从派生类中抛出?
    湾。 由于实施限制,流式 AAD 可能是不可能的,但不同的算法在不同的时间需要它(有些在一开始就需要它,有些直到最后才需要它)。 我们应该预先要求它还是有一种在单个算法允许的情况下添加它的方法?
  1. 我们对 INonceProvider 的改进持开放态度,只要用户需要编写代码来生成新的 nonce。 有没有人有另一个建议的形状?

1. a = 我认为不及早警告用户可能是个问题。 想象一下上面某人的场景,一个 10gb 的文件。 他们认为他们正在流式传输,然后某个时候另一个开发人员更改了密码,接下来代码在返回值之前缓冲 10gb(或尝试)。

  1. b = 再次使用“流式传输”或网络概念,例如 AES GCM 等,直到解密结束才能获得 AAD 信息。 至于加密,我还没有看到您没有预先获得数据的情况。 所以我会说至少对于加密你应该在一开始就需要它,解密更复杂。
  1. 我认为这确实不是问题,通过接口或直接为随机数提供“字节”既不存在也不存在。 两种方式都可以达到同样的效果,我只是觉得对于原始人来说更丑陋,但如果它能让人们晚上睡得更好,我并不强烈反对。 我将把它作为一个完成的交易删除,然后继续处理其他问题。

关于审议过程

@bartonjs :如果没有社区参与的闭门决定是一个有效的理由,我们可能会争论一整天,但我们会跑题,所以我会顺其自然。 再加上没有更丰富的面对面或实时通讯,我不想让那里的任何人感到不安。

关于流媒体

1. '流媒体意味着没有 AES-GCM 安全' 论点

具体来说,steaming => 在标签验证之前将解密的数据返回给调用者 => 没有安全性。 这不健全。 @bartonjs声称“选择密文 => 观察输出 => 恢复密钥”,而@drawaes声称“控制块的输入 => 因此知道输出 => “从那里工作”

好吧,在 AES-GCM 中,标签唯一做的就是完整性验证(防篡改)。 它对隐私的影响为 0。 事实上,如果您从 AES-GCM 中删除 GCM/GHASH 标签处理,您只需获得 AES-CTR 模式。 正是这种结构处理了隐私方面。 并且 CTR 可以进行位翻转,但不会以你们两个断言的任何方式(恢复密钥或明文)“破坏”,因为这意味着基本的 AES 原语受到损害。 如果您的密码分析员(是谁?)知道我们其他人不知道的事情,他/她应该发布它。 唯一可能的是,被攻击者可以翻转第 N 位并知道明文的第 N 位被翻转 - 但他们永远不知道实际的明文是什么。

所以

1) 明文隐私始终被强制执行
2) 完整性验证被简单地推迟(直到流结束)和
3) 没有密钥被泄露。

对于以流媒体为基础的产品和系统,您现在至少可以设计一个折衷方案,即暂时从 AEAD 降级到常规 AES 加密 - 然后在标签验证后升回到 AEAD。 这解锁了几个创新概念来拥抱安全性,而不是“你想缓冲所有这些——你疯了吗?我们不能做加密!”。

这一切都是因为您只想实现EncryptFinal而不是EncryptEncryptFinal (或等价物)。

2. 不特定于 GCM!

现在,AES-GCM 不再是拥有大量“糟糕时刻”的神奇野兽。 它只是 AES-CTR + GHASH(如果可以的话,是一种哈希)。 与隐私相关的 Nonce 考虑继承自 CTR 模式,而与完整性相关的标签考虑来自规范中允许的可变标签大小。 尽管如此,AES-CTR + GHASH 与 AES-CBC + HMAC-SHA256 之类的算法非常相似,因为第一个算法处理隐私,第二个处理完整性。 在 AES-CBC + HMAC-SHA256 中,密文中的位翻转将破坏解密文本中的相应块(与 CTR 不同),并且还会确定性地翻转以下解密明文块中的位(如 CTR)。 同样,攻击者不知道生成的明文将是什么 - 只是那些位被翻转(如 CTR)。 最后,完整性检查 (HMAC-SHA256) 将捕获它。 但只处理最后一个字节(如 GHASH)。

因此,如果您保留所有解密数据直到完整性正常的论点确实很好 - 它应该始终如一地应用。 因此,从 AES-CBC 路径出来的所有数据也应该被缓冲(在库内部),直到 HMAC-SHA256 通过。 这基本上意味着在 .NET 上,甚至没有流数据可以从 AEAD 的进步中受益。 .NET 强制流数据降级。 在不加密或常规加密之间进行选择。 没有AEAD。 在缓冲在技术上不切实际的情况下,架构师至少应该可以选择警告最终用户“无人机镜头可能已损坏”,而不是“看不到你”。

3. 这是我们拥有的最好的

数据越来越大,安全性需要更强。 流媒体也是设计师必须接受的现实。 直到世界设计出真正集成的 AEAD 算法,它可以在本地检测腐败中途篡改,我们一直坚持加密 + 身份验证作为搭档。 真正的 AEAD 原语正在研究中,但我们现在刚刚获得了加密 + 身份验证。

我不太关心“AES-GCM”,而是关心一种可以支持流工作负载的快速、流行的 AEAD 算法——在数据丰富、超连接的世界中非常普遍。

4.使用AES-CBC-HMAC,使用(插入解决方法)

Crypto Board 建议不要在第一种情况下使用 GCM,而是使用 CBC+HMAC。

撇开上面提到的一切,甚至场景的细节不谈——这表明 AES-CBC-HMAC 不是免费的。 它比 AES-GCM 慢约 3 倍,因为 AES-CBC 加密是不可并行的,并且可以通过 PCLMULQDQ 指令加速 GHASH。 因此,如果您使用 AES-GCM 的速度为 1GB/秒,那么您现在使用 AES-CBC-HMAC 的速度将达到约 300MB/秒。 这再次犯下了“加密货币让你慢下来,跳过它”的心态——安全人员努力与之抗争的心态。

加密每个 4k 帧

视频编解码器应该突然做加密? 或者加密层现在必须理解视频编解码器? 它只是数据安全层的一个比特流。 它是视频/基因组数据/图像/专有格式等的事实不应该成为安全层的关注点。 整体解决方案不应混合核心职责。

随机数

NIST 允许长度超过 96 位的随机 IV。 请参阅NIST 800-38D中的第 8.2.2 节。 这里没有什么新鲜事,nonce 要求来自 CTR 模式。 这在大多数流密码中也是相当标准的。 我不明白对随机数的突然恐惧 - 它一直是number used once 。 尽管如此,尽管INonce的争论导致界面笨拙,但至少它并没有消除像 no-stream-for-you 强加这样的创新。 如果我们能够获得 AEAD 安全性 + 流式工作负载创新,我会在任何一天向INonce让步。 我讨厌将诸如流媒体之类的基本事物称为创新-但这就是我担心我们会倒退的地方。

我很想被证明是错误的

我只是一个在工作了一整天后放弃和孩子们一起看电影的人来打字。 我累了,可能是错的。 但至少有一个基于事实的开放社区对话,而不是轶事或“委员会原因”或其他一些巫术。 我从事促进安全 .NET 和 Azure 创新的业务。 我认为我们有一致的目标。

说到社区对话...

我们可以进行社区 Skype 通话吗? 像这样表达一个复杂的话题会变成一堵巨大的文字墙。 请问好看吗?

请不要打 Skype 电话——这就是“闭门会议”的定义,没有可供社区使用的记录。 Github 问题是各方拥有民事记录话语的正确工具(忽略 MS-comment-removal 的先例)。

MS Crypto Review Board 可能也打了一个 Skype 电话。 这不是参与此线程的 MS 人员的错 - 他们可能对 MS Crypto Review Board 的象牙塔(无论/谁)的访问和说服力非常有限。

关于流式 AEAD:

字节大小流 _encryption_ 对于 MAC-last 模式(如 GCM、CTR+HMAC)是可能的,但对于 MAC-first 模式(如 CCM)则不可能。 字节大小的流 _decryption_ 从根本上是泄漏的,因此没有人考虑。 CBC+HMAC 也可以使用块大小的流式传输_加密_,但这不会改变任何事情。 IE。 流式 AEAD 的字节大小或块大小方法存在缺陷。

块大小的流 _encryption_ 和 _decryption_ 工作得很好,但它们有两个限制:

  • 它们需要缓冲(超出块大小)。 如果缓冲受到控制/限制(例如 Inferno),或者留给上层(调用层)处理,这可以由库/API 完成。 无论哪种方式都有效。

  • 分块流式传输 AEAD 未标准化。 前任。 nacl-stream ,Inferno,MS 自己的 DataProtection,自己做。

这只是迄今为止讨论中的每个人都已经知道的内容的摘要。

@sdrapkin ,为了确保我理解正确,您是否可以使用此 API 提供流式加密,但没有流式解密?

@sdrapkin好吧,人类实时头脑风暴肯定是有益的,记录保存问题可以通过会议记录来解决。 回到技术方面,虽然分块适用于流式解密,但这并不是低级别的安全原语。 这是一个自定义协议。 还有一个像你提到的非标准的。

@摩根布

_你对这个提供流加密但没有流解密的 API 满意吗?_

不,我不是。 如果这样的 API 可用,则很容易创建一个大小为任何缓冲区解密都无法解密的流加密密文(内存不足)。

^^^^ 到目前为止,还没有太多的共识,但我认为我们都可以同意,无论采用何种方式,不对称的 API 都会是一场灾难。 既来自“嘿是我认为我会依赖的流解密方法,因为有加密方法”,也因为上面的@sdrapkin评论。

@Drawaes同意。 非对称 enc/dec API 会很糟糕。

有更新的人吗?

显然我把一些攻击混为一谈。

流密码(AES-CTR 和 AES-GCM 是)的固有弱点允许选择密文攻击以允许任意明文恢复。 对选择密文攻击的防御是认证; 所以 AES-GCM 是免疫的……除非你正在做流式解密,并且你可以从旁道观察中识别出明文是什么。 例如,如果解密数据作为 XML 处理,如果除空格或 < 以外的字符位于解密数据的开头,它将很快失败。 所以这就是“流解密重新引入了流密码设计的问题”(您可能已经注意到,.NET 没有任何问题)。

在寻找密钥恢复的来源时,有诸如Authentication之类的文件GCM (Ferguson/Microsoft) 中的弱点,但这是基于短标签大小恢复身份验证密钥(这是 Windows 实现仅允许 96 位标签的部分原因)。 关于为什么流式 GCM 是危险的,我可能被告知其他身份验证密钥恢复向量。

较早的评论中@sdrapkin指出“字节大小的流式解密从根本上是泄漏的,因此没有人考虑过。......流式 AEAD 的字节大小或块大小的方法是有缺陷的。”。 再加上 CCM(和 SIV)无法进行流式加密,并且有一个流式传输而不是另一个流式传输会很奇怪,这表明我们回到了仅使用一次性加密和解密。

所以看来我们又回到了我的上一个 API 提案(https://github.com/dotnet/corefx/issues/23629#issuecomment-329202845)。 除非我在休假期间设法忘记了其他未解决的问题。

欢迎回来@bartonjs

我要睡一会儿,但很短暂:

  1. 我们之前在这个线程上将协议设计与原始设计混为一谈。 我只想说选择密文攻击是协议设计问题,而不是原始问题。

  2. 流式 AEAD 解密至少可以让你拥有隐私,然后在最后一个字节立即升级为隐私 + 真实性。 如果没有 AEAD 上的流媒体支持(即仅限传统隐私),您将永久限制人们获得较低的、仅限隐私的保证。

如果技术优势不足,或者您(理所当然地)怀疑我的论点的权威性,我将尝试外部权威途径。 您应该知道您的实际底层实现在流模式下支持 AEAD(包括 AES GCM)。 Windows 核心操作系统 ( bcrypt ) 允许通过BCryptEncryptBCryptDecrypt函数流式传输 GCM。 见dwFlags那里。 或用户代码示例。 或 Microsoft 编写的CLR 包装器。 或者该实施已在今年早些时候通过了NIST FIP-140-2 认证。 或者 Microsoft 和 NIST 都在 AES 实施方面花费了大量资源,并在此处此处对其进行了认证。 尽管有这一切,但没有人指责原语。 .NET Core 突然出现并强加它自己的密码论来淡化强大的底层实现是毫无意义的。 尤其是当可以同时支持流式传输和单发时,非常简单。

更多的? 好吧,上面的内容对于 OpenSSL 来说是正确的,即使使用他们的“更新”的 evp API。

BouncyCastle 也是如此。

Java 密码体系结构也是如此。

干杯!
席德

@sidshetye ++10 如果密码板如此关注,为什么他们让 Windows CNG 这样做?

如果您检查 Microsoft 的 NIST FIPS-140-2 AES 验证(例如 # 4064 ),您会注意到以下内容:

AES-GCM:

  • 纯文本长度:0、8、1016、1024
  • AAD 长度:0、8、1016、1024

AES-CCM:

  • 纯文本长度:0-32
  • AAD 长度:0-65536

流式传输没有验证。 我什至不确定 NIST 是否会检查那个 ex。 不应允许 AES-GCM 实现加密超过 64Gb 的明文(GCM 的另一个荒谬的限制)。

我并不热衷于流式传输,因为我的使用不应该超过 16k,但是碎片化的缓冲区会很好,并且根本不会造成任何风险(我实际上怀疑 cng 将它的接口设置为正是为了这个目的)......例如,我希望能够传入多个跨度或类似的(例如链表)并让它一次解密。 如果它解密到一个连续的缓冲区,那一切都很好。

所以我想在“流媒体风格”API 上移动隐蔽的加密板现在是不行的,所以让我们继续做一个单一的 API。 如果以后有足够多的人表示需要,总有扩展 API 的空间

@sdrapkin的重点是流 API 已经过 NIST 实验室和 MSFT 的广泛审查。 每个经过验证的构建都在 80,000 美元到 50,000 美元之间,MSFT(以及 OpenSSL 和 Oracle 以及其他加密货币重量级人物)已经投入大量资金来验证这些 API 和实现超过 10 年。 让我们不要被测试计划的特定纯文本大小分心,因为我相信 .NET 将支持 0、8、1016、1024 以外的大小,无论是流式传输还是一次性。 关键是所有那些经过实战考验的 API(字面意思是武器支持系统),在所有这些平台上都支持加密原始 API 级别的流式 AEAD。 不幸的是,到目前为止,所有反对它的论点都是应用程序或协议级别的问题,被认为是加密原语级别的伪问题。

我完全赞成“让最好的想法获胜”,但除非 .net 核心加密团队(MSFT 或社区)有一些突破性的发现,否则我只是看不到到目前为止,来自所有不同组织的每个人都在做加密是错误的他们是对的。

PS:我知道我们在这里存在分歧,但我们都想要对平台和客户最好的东西。

@Drawaes除非今天定义的 AEAD 接口(不一定是实现)支持流 API 表面,否则我看不出人们如何在没有两个接口或自定义接口的情况下扩展它。 那将是一场灾难。 我希望这个讨论会导致一个面向未来的接口(或者至少,反映已经存在多年的其他 AEAD 接口!)。

我倾向于同意。 但是这个问题进展得很快,当这种情况发生时,我们很可能会遇到关键点,要么它无法进入 2.1,要么它必须在没有时间解决问题的情况下被彻底解决。 老实说,我已经回到了我的旧包装器,只是将它们改造为 2.0 ;)

我们有一些用于JavaOpenSSLC# Bouncy CastleCLR Security的参考 API。 坦率地说,他们中的任何一个都可以长期使用,我希望 C# 拥有类似于Java 的“Java 加密体系结构”的东西,其中所有加密实现都针对一个完善的接口,允许在不影响用户代码的情况下交换加密库。

回到这里,我认为最好将 .NET Core 的ICryptoTransform接口扩展为

public interface IAuthenticatedCryptoTransform : ICryptoTransform 
{
    bool CanChainBlocks { get; }
    byte[] GetTag();
    void SetExpectedTag(byte[] tag);
}

如果我们Span将所有byte[] s 化,那么这应该渗透到System.Security.Cryptography命名空间中的整个 API 以实现整体一致性。

编辑:修复了 JCA 链接

如果我们要跨越所有字节 [],则应该渗透到 System.Security.Cryptography 命名空间中的整个 API 以实现整体一致性。

我们已经这样做了。 除了 ICryptoTransform 之外的所有内容,因为我们无法更改接口。

我认为我们最好扩展 .NET Core 的 ICryptoTransform ...

这样做的问题是调用模式在最后取出标签时非常尴尬(特别是如果涉及 CryptoStream)。 原来是我写的,很丑。 还有一个问题是如何获得其中之一,因为 GCM 参数与 CBC/ECB 参数不同。

所以,这是我的想法。

  • 流式解密对 AE 来说是危险的。
  • 总的来说,我是“给我原始的,让我管理我的风险”的粉丝
  • 我也是“.NET 不应该(轻易)允许完全不安全的东西,因为这是它的一些价值主张”的粉丝
  • 如果,正如我最初所误解的那样,错误地进行 GCM 解密的风险是输入密钥恢复,那么我仍然会认为“这太不安全了”。 (.NET 和其他所有东西的区别在于“花了更长的时间来做这件事,世界学到了更多”)
  • 但是,既然不是这样,如果你真的想要训练轮脱落,那么我想我会接受这个想法。

为此我的相当原始的想法(添加到现有的建议,所以一次性仍然存在,尽管我猜它是一个虚拟的默认实现而不是抽象):

```C#
部分类 AuthenticatedEncryptor
{
// 如果操作已经在进行中则抛出
公共抽象无效初始化(ReadOnlySpan关联数据);
// 成功时为真,“目标太小”为假,其他情况为异常。
公共抽象布尔 TryEncrypt(ReadOnlySpan数据,跨度encryptedData, out int bytesRead, out int bytesWritten);
// 如果剩余加密数据太小,则返回 false,如果其他输入太小则抛出,请参阅 NonceOrIVSizeInBits 和 TagSizeInBits 属性。
// NonceOrIvUsed 可以移至 Initialize,但随后它可能被解释为输入。
公共抽象布尔 TryFinish(ReadOnlySpan剩余数据,跨度remainingEncryptedData, out int bytesWritten, Span标签,跨度nonceOrIvUsed);
}

部分类 AuthenticatedDecryptor
{
// 如果操作已经在进行中则抛出
公共抽象无效初始化(ReadOnlySpan标签,ReadOnlySpannonceOrIv, ReadOnlySpan关联数据);
// 成功时为真,“目标太小”为假,其他情况为异常。
公共抽象布尔 TryDecrypt(ReadOnlySpan数据,跨度decryptedData, out int bytesRead, out int bytesWritten);
// 抛出错误的标签,但无论如何都可能泄漏数据。
//(CBC+HMAC需要remainingDecryptedData,所以也可以加上remainingData吧?)
公共抽象布尔 TryFinish(ReadOnlySpan剩余数据,跨度剩余解密数据,出 int bytesWritten);
}
```

AssociatedData 出现在 Initialize 中,因为最后需要它的算法可以保留它,而首先需要它的算法不能以任何其他方式拥有它。

一旦决定了流式传输的外观(以及人们是否认为 CCM 应该在流式加密模式下内部缓冲,或者应该抛出),那么我将回到董事会。

@bartonjs我知道您从流的末尾提取和编程标签以实现加密/解密的对称性是什么意思。 如果留给每个用户来解决,这很棘手,但更糟糕的是。 我有一个可以在 MIT 下分享的实现; 需要与我的团队进行内部调查(而不是在我的办公桌/手机上)

中间立场可能像 OpenSSL 或 NT 的 bcrypt,您需要在最终的解密调用之前插入标签,因为那是标签比较发生的时候。 即SetExpectedTag (在最终解密之前)和GetTag (在最终加密之后)可以工作,但会将标签管理卸载给用户。 大多数将简单地将标签附加到密码流,因为它是自然的时间顺序。

我确实认为期望Initialize本身(解密)中的标签会破坏空间(字节流)和时间(结束时的标签检查,而不是开始时的标签检查)的对称性,这限制了它的有用性。 但是上面的标签 API 解决了这个问题。

同样对于加密, Initialize在任何加密转换之前都需要 IV。

最后,对于加密和解密, Initialize在任何转换之前都需要 AES 加密密钥。 (我遗漏了一些明显的东西,或者你忘记输入那个位?)

我确实认为在 Initialize 本身(解密)中期望标签会破坏对称性

在 CBC+HMAC 中,通常的建议是在开始任何解密之前验证 HMAC,因此它是一种标签优先解密算法。 类似地,可能存在一种“纯 AE”算法,它在计算期间对标签进行破坏性操作,并且仅检查最终答案是否为 0。因此,与关联的数据值一样,由于可能存在首先需要它的算法,因此它具有在完全通用的 API 中排在第一位。

将它们浮动到SetAssociatedDataSetTag的问题是,虽然基类与算法无关,但使用变得依赖于算法。 将 AesGcm 更改为 AesCbcHmacSha256 或 SomeTagDesctructiveAlgorithm 现在会导致 TryDecrypt 抛出,因为尚未提供标签。 对我来说,这比根本不具有多态性更糟糕,因此允许灵活性意味着将模型分解为每个算法完全隔离。 (是的,它可以通过更多的算法识别特征属性来控制,比如NeedsTagFirst ,但这实际上只会导致它更难使用)

同样对于加密,Initialize 在任何加密转换之前都需要 IV。

最后,对于加密和解密,Initialize 在任何转换之前都需要 AES 加密密钥。

关键是一个类 ctor 参数。 IV/nonce 来自 ctor 参数中的 IV/nonce 提供程序。

提供者模型解决了 SIV,在加密过程中没有给出 IV,而是代表数据生成一个。 否则 SIV 具有参数并要求提供空值。

或者你忘了输入那个位?

流方法被添加到我现有的提案中,该提案已经将密钥和 IV/nonce 提供程序作为 ctor 参数。

@bartonjs :很好的一点是,有些算法可能首先需要标记,而另一些算法则在最后,感谢您提醒它是对原始规范的补充。 我发现考虑一个用例会更容易,所以这里有一个云优先的例子:

我们将对保存在存储中的一个或多个 10GB AES-GCM 加密文件(即密文后的标签)执行分析。 分析的工作人员同时将多个入站流解密到单独的机器/集群中,并在最后一个字节+标签检查之后,开始每个分析工作负载。 所有存储、工作、分析 VM 都位于 Azure US-West。

在这里,无法在每个流的末尾获取标签并将其提供给 AuthenticatedDecryptor 的Initialize方法。 因此,即使用户自愿修改代码以供 GCM 使用,他们甚至无法开始使用 API。

想一想,我们可以拥有一个适应各种 AEAD 并且没有用户代码更改的 API 的唯一方法是,如果不同 AEAD 算法的加密提供者自动神奇地处理标签。 Java 通过将标签放在 GCM 密文的末尾来做到这一点,并在解密期间将其提取出来,无需用户干预。 除此之外,任何时候有人显着更改算法(例如 CBC-HMAC => GCM),由于标签优先和标签后处理的互斥性质,他们将不得不修改他们的代码。

恕我直言,我们应该首先决定是否

选项 1)算法提供者在内部处理标签管理(如 Java)

或者

选项 2) 在 API 上公开足够的内容让用户自己完成(如 WinNT bcrypt 或 openssl)

选项 1 将真正简化图书馆消费者的整体体验,因为缓冲区管理可能会变得复杂。 在库中很好地解决它,现在每个用户都不必每次都解决它。 此外,所有 AEAD 都具有相同的接口(先标记、后标记、无标记),并且换出算法也更简单。

我的投票将支持选项 1。

最后,我们能够挖掘我们的实现,允许 GCM 上的ICryptoTransform流操作自动提取标签 in-stream source 。 这是对 CLR Security 自己的包装器的重大更新,尽管有额外的缓冲区副本,但它仍然非常快(在我们的 Windows 10 训练营中的测试 macbook pro 上约为 4GB/秒)。 我们基本上围绕 CLR Security 为自己创建选项 1,因此我们不需要在其他任何地方都这样做。 这个视觉效果确实有助于解释ICryptoTransform界面的TransformBlockTransformFinalBlock中发生的事情。

@sidshetye我不确定为什么您的云优先示例被阻止。 如果您正在从存储中读取,您可以先下载最后几个标签字节并将其提供给解密器 ctor。 如果使用 Azure 存储 API,这将通过CloudBlockBlob.DownloadRangeXxx完成。

@GrabYourPitchforks不要在该示例中太过分心,但这是 Azure Blob 存储的特定功能。 通常,基于 VM 的存储 (IaaS) 或非 Azure 存储工作负载通常会获得不可搜索的网络流。

我个人很高兴看到@GrabYourPitchforks - 耶!

我们将对保存在存储中的一个或多个 10GB AES-GCM 加密文件(即密文后的标签)执行分析。 分析的工作人员同时将多个入站流解密到单独的机器/集群中,并在最后一个字节+标签检查之后,开始每个分析工作负载。 所有存储、工作、分析 VM 都位于 Azure US-West。

@sidshetye ,您非常坚持将愚蠢的危险原语和智能拥抱协议分开! 我有一个梦想——我相信了。 然后你把这个扔给我们。 这是一个协议 - 一个系统设计。 谁设计了你描述的那个协议 - 搞砸了。 现在为无法将方形钉子装入圆孔而哭泣是没有意义的。

任何 GCM 加密的 10Gb 文件不仅危险地生活在原始边缘附近(GCM 在 64Gb 之后就不好用了),而且还有一个隐含的断言,即需要缓冲整个密文。

任何 GCM 加密 10Gb 文件的人都极有可能犯协议错误。 解决方案:分块加密。 TLS 具有 16k 限制的可变长度分块,并且还有其他更简单、无 PKI 的风格。 这个假设示例的“云优先”性感并没有减少设计错误。

(我在这个线程上有很多事情要做。)

@sdrapkin提出了关于从数据保护层重用IAuthenticatedEncryptor接口的观点。 老实说,我不认为这是对原语的正确抽象,因为数据保护层在如何执行密码学方面非常固执己见。 例如,它禁止自行选择 IV 或 nonce,它要求符合要求的实现理解 AAD 的概念,并且它产生的结果有点专有。 在 AES-GCM 的情况下, IAuthenticatedEncryptor.Encrypt的返回值是用于子密钥派生的奇怪的几乎随机数的串联,密文是在提供的明文上运行 AES-GCM(但不是AAD!)和 AES-GCM 标签。 因此,虽然生成受保护有效负载所涉及的每个步骤都是安全的,但有效负载本身不遵循任何类型的公认约定,并且除了数据保护库之外,您不会找到任何可以成功解密生成的密文的人。 这使它成为面向应用程序开发人员的库的一个很好的候选者,但对于由原语实现的接口来说,它是一个糟糕的候选者。

我还应该说,我认为拥有一个所有经过身份验证的加密算法都应该实现的 One True Interface(tm) IAuthenticatedEncryptionAlgorithm并没有太大的价值。 这些原语是“复杂的”,不像简单的分组密码原语或散列原语。 这些复杂的原语中的变量太多了。 是原始AE,还是AEAD? 该算法是否完全接受 IV / nonce? (我见过一些没有。)输入 IV / nonce 或数据的结构是否有任何问题? IMO 复杂的原语应该只是独立的 API,更高级别的库将支持他们关心的特定复杂原语。 然后更高级别的库公开它认为适合其场景的任何统一 API。

@sdrapkin我们又跑题了。 我只想说一个系统是使用原语构建的。 这里的加密原语是裸露而强大的。 系统/协议层处理缓冲; 这也是在集群级别上,当然不是在单次原语会强制的主系统内存中。 “分块”边界是 X(此处 X=10GB),因为 < 64GB,因为集群的缓冲容量几乎是无限的,在集群中加载最后一个字节之前,什么都不会/可能开始。 这正是关注点的分离,针对我一直在谈论的优势优化每一层。 只有当底层原语不妨碍更高层的设计/限制时才会发生这种情况(请注意,更多现实世界的应用程序都有自己的遗留障碍)。

NIST 800-38d sec9.1 指出:

为了阻止未经授权的一方控制或影响 IV 的生成,
GCM 应仅在满足以下要求的密码模块内实现
FIPS 酒吧。 140-2。 特别是,模块的加密边界应包含一个
根据 Sec 中的一种结构产生 IV 的“生成单元”。 8.2 以上。
根据 FIPS 140-2 的要求验证模块的文档应
描述模块如何符合 IV 的唯一性要求。

这对我来说意味着 GCM IV 必须在内部自动生成(而不是从外部传递)。

@sdrapkin好点,但是如果您仔细阅读,您会发现对于 96 位及以上的 IV 长度,第 8.2.2 节允许使用至少 96 位是随机的随机位生成器 (RBG) 生成 IV(您可能只有0个其他位)。 上个月我确实在这个线程本身上提到了这一点(这里在 nonce 下)。

LT;DR: INonce是一个陷阱,导致不遵守 NIST 和 FIPS 指南。

第 9.1 节简单地说,对于 FIPS 140-2,IV 生成单元(完全随机,即 sec 8.2.2 或确定性实现,即 sec 8.2.1)必须位于经过 FIPS 验证的模块边界内。 自从 ...

  1. RBG 已通过 FIPS 验证
  2. 推荐 IV 镜头 >= 96
  3. 设计一个持续重启的 IV 生成单元,加密原始层无限期断电是很困难的
  4. 在加密库中实现上述 3 并获得认证既困难又昂贵(50K 美元用于任何导致非位精确构建图像的东西)
  5. 没有用户代码会因为上面的 4 而实现 3 并获得认证。 (让我们撇开一些异国情调的军事/政府设施)。

... 大多数正在接受 FIPS 认证的加密库(参见 Oracle 的 Java、WinNT 的 bcryptprimitives、OpenSSL 等)都使用 RBG 路由进行 IV,并简单地将字节数组作为输入。 请注意,从 NIST 和 FIPS 的角度来看,拥有INonce接口实际上是一个陷阱,因为它暗示用户应该将该接口的实现传递给加密函数。 但是几乎可以保证INonce的任何用户实施都没有经过 9 个月以上和 50K+ 美元的 NIST 认证过程。 然而,如果他们刚刚使用 RGB 构造(已经在加密库中)发送了一个字节数组,他们将完全符合指南。

我之前说过 - 这些现有的加密库已经发展了它们的 API 表面,并且已经在多个场景中进行了实战测试。 更多的是我们在这个长线程中所涉及的内容。 我的投票再次是在所有这些库、所有这些验证和所有这些安装中利用这些知识和经验,而不是试图重新发明轮子。 不要重新发明轮子。 用它来发明火箭:)

嗨伙计,

对此有何更新? 在@karelz加密路线图线程AES GCM 线程上没有看到任何更新。

谢谢
席德

所以最后一个具体的建议来自https://github.com/dotnet/corefx/issues/23629#issuecomment -334328439:

partial class AuthenticatedEncryptor
{
    // throws if an operation is already in progress
    public abstract void Initialize(ReadOnlySpan<byte> associatedData);
    // true on success, false on “destination too small”, exception on anything else.
    public abstract bool TryEncrypt(ReadOnlySpan<byte> data, Span<byte> encryptedData, out int bytesRead, out int bytesWritten);
    // false if remainingEncryptedData is too small, throws if other inputs are too small, see NonceOrIVSizeInBits and TagSizeInBits properties.
    // NonceOrIvUsed could move to Initialize, but then it might be interpreted as an input.
    public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, Span<byte> remainingEncryptedData, out int bytesWritten, Span<byte> tag, Span<byte> nonceOrIvUsed);
}

partial class AuthenticatedDecryptor 
{
    // throws if an operation is already in progress
    public abstract void Initialize(ReadOnlySpan<byte> tag, ReadOnlySpan<byte> nonceOrIv, ReadOnlySpan<byte> associatedData);
    // true on success, false on “destination too small”, exception on anything else.
    public abstract bool TryDecrypt(ReadOnlySpan<byte> data, Span<byte> decryptedData, out int bytesRead, out int bytesWritten);
    // throws on bad tag, but might leak the data anyways.
    // (remainingDecryptedData is required for CBC+HMAC, and so may as well add remainingData, I guess?)
    public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, Span<byte> remainingDecryptedData, out int bytesWritten);
}

此后仅提出了几个潜在问题:

  • 标签是预先需要的,这会阻碍某些场景。 要么 API 必须变得更加复杂以提供更大的灵活性,要么这个问题必须被视为协议(即高级)问题。
  • INonceProvider可能不必要地复杂和/或导致不符合 NIST 和 FIPS 指南。
  • 经过身份验证的加密原语的预期抽象可能是白日梦,因为差异可能太大。 没有对这个建议进行任何进一步的讨论。

我想提出以下建议:

  1. 不需要预先标记的额外复杂性似乎很严重,相应的问题场景似乎并不常见,而且这个问题确实听起来很像协议问题。 好的设计可以容纳很多,但不是一切。 我个人觉得把这个留给协议很舒服。 (欢迎强有力的反例。)
  2. 讨论一直转向灵活的低级实现,除了 IV 生成之外,它不能防止误用。 让我们保持一致。 普遍的共识似乎是,高级 API 是重要的下一步,对于大多数开发人员的正确使用至关重要——这就是我们避免在低级API 中防止误用的方法。 但似乎额外的恐惧支持了防止误用的想法_在 IV 生成领域_。 在低级 API 的上下文中,为了保持一致,我倾向于使用byte[]等效项。 但是使用注入的INonceProvider实现交换更加无缝。 @sidshetye的评论是无可辩驳的,还是仅仅调用 RNG 的简单INonceProvider实施仍然被认为是合规的?
  3. 这些抽象看起来很有用,并且在设计它们上付出了很多努力,以至于现在我相信它们的好处多于坏处。 此外,高级 API 仍然可以选择实现不符合低级抽象的低级 API。
  4. IV 是通用术语,nonce 是一种特定的 IV,对吗? 这要求从INonceProvider重命名为IIVProvider ,从nonceOrIv*重命名为iv* 。 毕竟,我们总是在处理 IV,但不一定是 nonce。

前期标签对于我的场景来说是一个非首发,所以我可能只会保留我自己的实现。 这很好我不确定在这个领域编写高性能代码是否适合每个人。

问题是它会导致不必要的延迟。 您必须预先缓冲整个消息才能在末尾获取标签以开始解码帧。 这意味着您基本上不能重叠 IO 和解密。

我不知道为什么最后允许它这么难。 但我不会为这个 API 设置障碍,它对我的​​场景没有任何兴趣。

IV 是通用术语,nonce 是一种特定的 IV,对吗?

不, nonce是一个使用过一次的数字。 指定随机数的算法表明重用违反了算法的保证。 在 GCM 的情况下,使用具有相同密钥和不同消息的相同 nonce 可能会导致 GHASH 密钥的泄露,从而将 GCM 降低到 CTR。

来自http://nvlpubs.nist.gov/nistpubs/ir/2013/NIST.IR.7298r2.pdf

Nonce:安全协议中使用的值,从不使用相同的密钥重复。 例如,在质询-响应身份验证协议中用作质询的 nonce 通常在更改身份验证密钥之前不得重复。 否则,有可能发生重放攻击。 使用 nonce 作为挑战与随机挑战不同,因为 nonce 不一定是不可预测的。

“IV”没有同样严格的要求。 例如,使用 CBC 重复 IV 只会泄露加密消息是否与具有相同 IV 的先前消息相同或不同。 它不会削弱算法。

随机数是使用一次的数字。
“IV”没有同样严格的要求。

@bartonjs是的。 我会推断,由于使用随机数来初始化加密原语,它是它的初始化向量。 它完全符合我能找到的任何 IV 定义。 是的,它有更严格的要求,就像作为牛比作为动物有更严格的要求一样。 当前的措辞似乎要求一个“cowOrAnimal”参数。 不同的模式对 IV 有不同的要求这一事实并没有改变他们都要求某种形式的 IV 的事实。 如果我遗漏了什么,请务必保留当前的措辞,但据我所知,只有“iv”或“IIVProvider”既简单又正确。

沉迷于nonceOrIv自行车运动:

96 位 GCM IV 有时被定义为 4 字节salt和 8 字节nonce (例如 RFC 5288)。 RFC 4106 将 GCM nonce定义为 4 字节salt和 8 字节iv 。 RFC 5084(CMS 中的 GCM)说 CCM 采用nonce ,GCM 采用iv ,但是 _"... 为 AES-CCM 和 AES-GCM 提供一组通用术语,在本文档的其余部分中,AES-GCM IV 被称为随机数。”_ RFC 5647(SSH 的 GCM)说 _“注意:在 [RFC5116] 中,IV 被称为随机数。”_ RFC 4543 ( IPSec 中的 GCM)说 _“我们将 AES-GMAC IV 输入称为随机数,以便将其与数据包中的 IV 字段区分开来。”_ RFC 7714(GCM for SRTP)谈到了一个 12 字节的IV并且几乎保持其一致性,但接着说“_minimum & maximum nonce (IV) length: MUST be 12 octets.”_

鉴于大多数 GCM 规范完全缺乏一致性, nonceOrIv有点道理。 0.02 美元

预先标记是行不通的

就像其他在这里表达自己的客户一样,要求预先贴上标签对我们来说也是不可能的。 .NET 无法使用这种人为引入的限制来处理并发流。 完全扼杀了可扩展性。

你能支持它增加复杂性的说法吗? 因为它实际上应该是微不足道的。 另外,平台特定的加密实现(您将要包装的)都没有这个限制。 具体来说,原因是输入标签只需与计算标签进行比较即可。 只有在TryFinish期间最后一个块被解密,计算的标签才可用。 因此,本质上,当您开始实施时,您会发现您只是将tag存储在您的实例中,直到TryFinish 。 您可以将其作为可选输入

public abstract bool TryFinish(ReadOnlySpan<byte> remainingData, 
                             Span<byte> remainingDecryptedData, 
                             out int bytesWritten, 
                             ReadOnlySpan<byte> tag = null); // <==

我还认为我们正在努力规范化为一个涵盖所有加密场景的单一界面。 我也更喜欢通用接口,但绝不会以牺牲功能或可扩展性为代价——尤其是在像语言本身的标准加密库这样的基础层上。 恕我直言,如果发现自己这样做,通常意味着抽象是错误的。

如果需要一个简单一致的接口,我更喜欢 Java 方法-之前在这里也作为选项 1 提出过。 它还通过将它们保留在算法实现中(恕我直言,我认为它应该)来回避上述标签优先/标签最后的问题。 我的团队没有实施这个,所以这不是我们的决定,但如果我们必须做出决定并开始实施——我们肯定会走这条路。

请避免使用INonce接口,简单的byte[]span<>应该足以满足兼容的低级接口。

IV vs Nonce - 广义案例确实是 IV。 对于 GCM,IV 必须是 Nonce(例如 Car vs RedOrCar)。 当我复制粘贴这个时,我注意到@timovzl使用了一个非常相似的例子:)

@sidshetye您能否提出一个精确的建议,即(1)支持需要预先标记的算法,以及(2)在所有其他情况下只需要标记到TryFinish吗?

我想你正在考虑以下方向?

  • Initialize中的标签允许为空。 只有那些预先需要它的算法才会抛出空值。
  • TryFinish中的标签是必需的,或者(或者)对于那些已经预先要求它的算法允许为空。

我想以上只是增加了文档和专业知识形式的复杂性。 对于低级 API,这可以被认为是一个小小的牺牲,因为无论如何都需要足够的文档和专有技术。

我开始相信这应该是可能的,为了与其他实现和流的兼容性。

@timovzl当然,我希望明天为此预算一些时间。

@Timovzl ,我今天终于有时间了,结果证明这真是个兔子洞! 这很长,但我认为它涵盖了大多数用例,捕捉了 .NET 加密的优势( ICryptoTransform ),同时拥抱 .NET Core/Standard 方向( Span<> )。 我已经重新阅读,但希望下面没有错别字。 我还认为一些实时通信(聊天、会议通话等)对于快速头脑风暴至关重要; 我希望你能考虑一下。

编程模型

我将首先讨论为 API 用户生成的编程模型。

流式加密

var aesGcm = new AesGcm();
using (var encryptor = aesGcm.CreateAuthenticatedEncryptor(Key, IV, AAD))
{
  using (var cryptoOutStream = new CryptoStream(cipherOutStream, encryptor, CryptoStreamMode.Write))
  {
    clearInStream.CopyTo(cryptoOutStream);
  }
}

流式解密

var aesGcm = new AesGcm();
using (var decryptor = aesGcm.CreateAuthenticatedDecryptor(Key, IV, AAD))
{
  using (var decryptStream = new CryptoStream(cipherInStream, encryptor, CryptoStreamMode.Write))
  {
    decryptStream.CopyTo(clearOutStream);
  }
}

非流媒体

由于非流式处理是流式处理的一种特殊情况,我们可以将上述用户代码包装到AuthenticatedSymmetricAlgorithm (定义如下)上的辅助方法中,以公开更简单的 API。 IE

public class AesGcm : AuthenticatedSymmetricAlgorithm
{
  ...
  // These return only after consuming entire input buffer

  // Code like Streaming Encrypt from above within here
​  public abstract bool TryEncrypt(ReadOnlySpan<byte> clearData, Span<byte> encryptedData);

  // Code like Streaming Decrypt from above within here
  public abstract bool TryDecrypt(ReadOnlySpan<byte> encryptedData, Span<byte> clearData); 
  ...
}

这可以兼作呈现更简单的 API,例如

非流式加密

var aesGcm = new AesGcm(Key, IV, AAD);
aesGcm.TryEncrypt(clearData, encryptedData);
var tag = aesGcm.Tag;

非流式解密

var aesGcm = new AesGcm(Key, IV, AAD);
aesGcm.Tag = tag;
aesGcm.TryDecrypt(encryptedData, clearData);

在引擎盖下

查看 corefx 源码,Span<> 无处不在。 这包括 System.Security.Cryptography.* - 对称密码除外,因此让我们先修复该问题并在顶部进行经过身份验证的加密。

1. 为 Span I/O 创建ICipherTransform

这就像ICryptoTransform的 Span 感知版本。 我只是将界面本身作为框架升级的一部分进行更改,但由于人们对此很敏感,因此我将其称为ICipherTransform

public partial interface ICipherTransform : System.IDisposable
{
  bool CanReuseTransform { get; }
  bool CanTransformMultipleBlocks { get; } // multiple blocks in single call?
  bool CanChainBlocks { get; }             // multiple blocks across Transform/TransformFinal
  int InputBlockSize { get; }
  int OutputBlockSize { get; }
  int TransformBlock(ReadOnlySpan<byte> inputBuffer, int inputOffset, int inputCount, Span<byte> outputBuffer, int outputOffset);
  Span<byte> TransformFinalBlock(ReadOnlySpan<byte> inputBuffer, int inputOffset, int inputCount);
}

还将ICryptoTransform标记为[Obsolete]

对以前了解 .NET 加密的人有礼貌

[Obsolete("See ICipherTransform")]
public partial interface ICryptoTransform : System.IDisposable { ... }

2. 为 Span I/O 扩展现有的SymmetricAlgorithm

public abstract class SymmetricAlgorithm : IDisposable
{
  ...
  public abstract ICipherTransform CreateDecryptor(ReadOnlySpan<byte> Key, ReadOnlySpan<byte> IV);
  public abstract ICipherTransform CreateEncryptor(ReadOnlySpan<byte> Key, ReadOnlySpan<byte> IV);
  public virtual ReadOnlySpan<byte> KeySpan {...}
  public virtual ReadOnlySpan<byte> IVSpan {...}
  public virtual ReadOnlySpan<byte> KeySpan {...}
  ...
}

3. 为 Span I/O 扩展现有的CryptoStream

这就像 System.Runtime 中的Stream 。 另外,我们将为我们的 AEAD 案例添加一个 c'tor。

CRITICAL: CryptoStream需要在FlushFinalBlock中进行强制升级,以便在加密期间将标签添加到流的末尾,并在解密期间自动提取标签(TagSize 字节) 。 这类似于其他经过实战测试的 API,例如 Java 的 Cryptographic Architecture 或 C# BouncyCastle。 这是不可避免的,但最好的地方是这样做的,因为在流式传输中,标签是在最后产生的,但在解密期间最终块被转换之前不需要。 好处是它极大地简化了编程模型。

注意:1)使用CBC-HMAC,可以选择先验证标签。 这是更安全的选择,但如果是这样,它实际上使它成为一个两遍算法。 第一遍计算 HMAC 标签,然后第二遍实际进行解密。 因此,内存流或网络流将始终必须在内存中进行缓冲,从而将其减少为一次性模型; 不是流媒体。 真正的 AEAD 算法(如 GCM 或 CCM)可以有效地流式传输。

public class CryptoStream : Stream, IDisposable
{
  ...
  public CryptoStream(Stream stream, IAuthenticatedCipherTransform transform, CryptoStreamMode mode);
  public override int Read(Span<byte> buffer, int offset, int count);
  public override Task<int> ReadAsync(Span<byte> buffer, int offset, int count, CancellationToken cancellationToken);
  public override void Write(ReadOnlySpan<byte> buffer, int offset, int count);
  public override Task WriteAsync(ReadOnlySpan<byte> buffer, int offset, int count, CancellationToken cancellationToken);

  public void FlushFinalBlock()
  {
    ...
    // If IAuthenticatedCipherTransform
    //    If encrypting, `TransformFinalBlock` -> `GetTag` -> append to out stream
    //    If decryption, extract last `TagSize` bytes -> `SetExpectedTag` -> `TransformFinalBlock`
    ...
  }

  ...
}

认证加密层

有了上面的内容,我们可以添加缺失的位以允许使用关联数据进行身份验证加密 (AEAD)

为 AEAD 扩展新的ICipherTransform

这使得CryptoStream可以正常工作。 我们还可以使用IAuthenticatedCipherTransform接口来实现我们自己的自定义流类/用法,但使用CryptoStream可以实现超级内聚和一致的 .net 加密 API。

  public interface IAuthenticatedCipherTransform : ICipherTransform
  {
    Span<byte> GetTag();
    void SetExpectedTag(Span<byte> tag);
  }

认证加密基类

只需扩展SymmetricAlgorithm

public abstract class AuthenticatedSymmetricAlgorithm : SymmetricAlgorithm
{
  ...
  // Program Key/IV/AAD via class properties OR CreateAuthenticatedEn/Decryptor() params
  public abstract IAuthenticatedCipherTransform CreateAuthenticatedDecryptor(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default);
  public abstract IAuthenticatedCipherTransform CreateAuthenticatedEncryptor(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default);
  public virtual Span<byte> AuthenticatedData {...}
  public virtual Span<byte> Tag {...}
  public virtual int TagSize {...}
  ...
}

AES GCM 类

public class AesGcm : AuthenticatedSymmetricAlgorithm
{
  public AesGcm(ReadOnlySpan<byte> Key = default, ReadOnlySpan<byte> IV = default, ReadOnlySpan<byte> AuthenticatedData = default)

  /* other stuff like valid key sizes etc similar to `System.Security.Cryptography.Aes` */
}

@sidshetye我为这种努力鼓掌。

通过 GCM 进行流式加密是可行的。 GCM 上的流式解密是

  • NIST 800-38d 中不允许。 第 5.2.2 节“经过身份验证的解密函数”非常清楚,解密后的明文 P 的返回必须暗示通过标签 T 进行的正确身份验证。
  • 不安全。 在“未经验证的明文发布”(RUP)设置中有一个算法是安全的安全概念。 RUP 安全性由 Andreeva-et-al 在 2014 年的论文中正式确定。 GCM 在 RUP 设置中是不安全的。 CAESAR 竞赛将每个条目与 GCM 进行比较,将 RUP 安全性列为可取的属性。 从 GCM 发布的未经验证的明文很容易受到比特翻转攻击。

在这个线程的早些时候,提出了非对称加密/解密 API 的可能性(从概念上),我认为共识是这将是一个非常糟糕的主意。

总之,您不能拥有用于 GCM 解密的高级字节粒度流 API。 我以前说过很多次,现在我再说一遍。 拥有流 API 的唯一方法是分块加密。 我会让每个人都免于分块加密的旋转木马..

无论 MS 决定为 GCM API 做什么,都不能允许 RUP。

@sdrapkin RUP 在这里进行了详细讨论,我们已经越过了那座桥。 简而言之,RUP 意味着在验证标签之前不需要使用解密的数据,但就像 Java JCE、WinNT bcrypt、OpenSSL 等一样,它不必在方法边界强制执行。 与大多数加密原语一样,尤其是低级原语,请谨慎使用。

^^^就这么多了。 我同意更高级别的流 API 等,然后很好地执行它。 但是,当我想使用低级原语时,我需要能够使用拆分缓冲区等内容,并且由我来确保不使用数据。 在标签计算/检查点中抛出异常,但不要限制低级原语。

由我来确保不使用数据

错误的。 这不取决于你。 AES-GCM 有一个非常具体的定义,并且该定义确保它不取决于您。 您想要的是一个单独的 AES-CTR 原语和一个单独的 GHASH 原语,然后您可以将它们组合起来并按照您认为合适的方式应用。 但是我们不是在讨论单独的 AES-CTR 和 GHASH 原语,是吗? 我们正在讨论 AES-GCM。 并且 AES-GCM要求不允许使用 RUP。

我还建议查看来自crypto.stackexchange的 Ilmari Karonen 的回答。

@sdrapkin你说得很好。 然而,希望最终有一个在 RUP 下是安全的算法,并让该算法适合这里决定的 API。 所以我们必须选择:

  1. API 不支持流式传输。 简单,但缺少低级 API。 有一天我们可能会后悔。
  2. AES-GCM实现防止流式传输,遵守规范而不限制 API。

我们能否检测流式传输场景并抛出异常来解释为什么这种用法不正确? 或者我们是否需要使其流式实现消耗整个缓冲区? 后者将是不幸的:您可能认为您正在流式传输,但事实并非如此。

我们可以添加一个由流实现检查的SupportsStreaming(out string whyNot)方法。

一般来说,我们是否有反对流式传输/标记在末端的有力论据? 如果不是,那么我相信我们的目标不应是使用 API 排除它。

@sdrapkin :让我们更广泛地了解 RUP,因为这是库,而不是应用程序。 因此,这更多的是缓冲和层设计问题,而不是实际发布/使用未经验证的数据。 查看针对 AES GCM 的 NIST 特别出版物 800-38D ,我们看到

  1. GCM 规范将最大明文长度定义为 2^39-256 位 ~ 64 GB。 在接近系统内存的任何地方进行缓冲是不合理的。

  2. 如果标签失败,GCM 规范将输出定义为 FAIL。 但是它没有规定实现中的哪一层必须缓冲直到标签验证。 让我们看一个调用堆栈,如:

A => AESGCM_Decrypt_App(key, iv, ciphertext, aad, tag)
B =>  +- AESGCM_Decrypt_DotNet(key, iv, ciphertext, aad, tag)
C =>    +- AESGCM_Decrypt_OpenSSL(key, iv, ciphertext, aad, tag)

在哪里
A是应用层的AES GCM
B 是语言层的 AES-GCM
C是平台层的AES-GCM

如果标签签出,则明文在 (A) 处释放,否则返回 FAIL。 但是,在规范中绝对没有任何地方表明主内存是唯一可以缓冲正在进行的纯文本的地方,也不应该在 (B) 或 (C) 或其他地方进行缓冲。 实际上,在 (C) 的示例中,OpenSSL、Windows NT Bcrypt 允许在更高层进行缓冲。 Java JCA、微软的 CLR Security 和我上面的建议是 (B) 的示例,其中流允许在应用程序层进行缓冲。 假设 A 的设计者在发布明文之前没有更好的缓冲能力是冒昧的。 该缓冲区在理论上和实践中可以是内存或 SSD 或跨网络的存储集群。 或打卡;)!

即使不考虑缓冲,本文还讨论了其他实际问题(参见第 9.1 节,设计注意事项和第 9.2 节,操作注意事项),例如密钥新鲜度或电源无限期故障时的 IV 不重复。 我们显然不会将它烘焙到 B 层,即这里。

@timovzl上面最近的提议解决了这两种情况 - 一次性(架构师不关心缓冲)以及流式传输(架构师具有更好的缓冲能力)。 只要低级流 API文档明确消费者现在负责缓冲它,安全证明的减少为零并且不偏离规范。

编辑:语法、错别字和试图让降价工作

Bingo .. 同样是层设计者决定标签验证发生在哪里。 我绝不提倡向应用层发布未经验证的数据。

讨论又回到了这些“消费者”级别的 API 还是它们是真正的 Primitives。 如果它们是真正的原语,那么它们应该公开功能,以便可以在上面构建更高级别的“更安全”的 API。 上面的 Nonce 讨论已经决定这些应该是真正的原语,这意味着你可以在脚上开枪,我认为这同样适用于流/部分密文解码。

话虽如此,必须迅速提供“更高”级别和更安全的 API,以阻止人们在这些 API 之上“滚动”自己的 API。

我的兴趣来自网络/管道,如果你不能做部分缓冲区并且必须做“一次”,那么这些 API 的缺点将没有任何好处,所以我将继续直接使用 BCrypt/OpenSsl 等。

我的兴趣来自网络/管道,如果你不能做部分缓冲区并且必须做“一次”,那么这些 API 的缺点将没有任何好处,所以我将继续直接使用 BCrypt/OpenSsl 等。

确切地。 需求不会消失,因此人们将使用其他实现或推出自己的实现。 这不一定比允许带有良好警告文档的流式传输更安全。

@Timovzl ,我认为我们已经围绕上一个提案获得了很多技术反馈和设计要求。 关于实施和发布的想法?

@Sidshetye提出了一个详细的建议,我相信它可以满足所有要求。 关于 RUP 的单一批评已经得到解决,没有进一步的反对。 (具体来说,RUP 可以在几个层之一中被阻止,并且低级 API 不应该规定哪一个;并且提供_no_流预计会产生更糟的效果。)

为了取得进展,我想邀请任何对最新提案有进一步担忧的人发言——当然,也提供替代方案。

我对这个提议和一个正在成形的 API 充满热情。

@Sidshetye ,我有一些问题和建议:

  1. 从现有的SymmetricAlgorithm继承是否可取? 是否有任何我们想要集成的现有组件? 除非我错过了这种方法的一些优势,否则我宁愿看到没有基类的AuthenticatedEncryptionAlgorithm 。 如果不出意外,它可以避免暴露不受欢迎的CreateEncryptor / CreateDecryptor (未经身份验证的!)方法。
  2. 所涉及的组件都不能用于非对称加密,是吗? 几乎所有组件的名称中都省略了“对称”,我同意这一点。 除非我们继续继承SymmetricAlgorithm ,否则AuthenticatedSymmetricAlgorithm可以重命名为AuthenticatedEncryptionAlgorithm ,遵循传统术语 Authenticated Encryption。
  3. 更改TryEncrypt / TryDecrypt以写入/接收标签,而不是在算法上具有可设置的Tag属性。
  4. 通过公共设置器设置keyivauthenticatedAdditionalData的目的是什么? 我会尽可能避开多种有效方法和可变属性。 没有它们,你能创建一个更新的提案吗?
  5. 我们想要AesGcm中的任何状态吗? 我的直觉是绝对避免ivauthenticatedAdditionalData ,因为这些是每条消息。 key可能值得作为状态,因为我们通常希望使用单个键执行多个操作。 尽管如此,也可以在每次通话的基础上获取密钥。 同样的问题也适用于CreateAuthenticatorEncryptor 。 无论如何,我们应该选择_一种方式_来传递参数。 我急于讨论利弊。 我倾向于AesGcm的关键状态,其余的分别在CreateAuthenticatedEncryptorTryEncrypt 。 如果我们已经达成一致,请向我们展示更新的提案。 :-)
  6. ICipherTransform应该是一个抽象类CipherTransform ,这样可以在不破坏现有实现的情况下添加方法。
  7. 所有的函数参数都应该使用camelCase,即以小写开头。 另外,我们应该说authenticatedData还是authenticatedAdditionalData ? 另外,我认为我们应该选择参数名称plaintextciphertext
  8. 无论 IV 被传递到何处,我都希望将其视为一个可选参数,这使得获得正确生成的(加密随机)IV 比提供我们自己的更容易。 至少,让低级 API 的滥用变得更加困难,而且我们免费获得了这个。
  9. 我仍在试图弄清楚TryEncrypt的客户端代码如何知道为ciphertext提供所需的跨度长度! TryDecryptplaintext的长度相同。 当然,我们不应该在成功之前循环尝试它们,每次失败迭代后将长度加倍?

最后,提前想一想,在此之上构建的高级 API 会是什么样子? 单纯从 API 的使用来看,似乎没有什么改进的余地,因为流式和非流式 API 已经非常简单了! 我想象的主要区别是自动 IV、自动输出大小,以及可能对加密数据量的限制。

Windows 允许流式传输。 OpenSSL 也可以。 这两个人大多将其归类为现有概念(尽管他们都对“你必须处理这件事,否则我会出错”表示不满)。

Go 没有,而 libsodium 没有。

似乎第一波允许它,而后来的波则不允许。 由于我们无可争辩地处于后期浪潮中,我认为我们将坚持不允许它。 如果在引入 one-shot 模型后对流媒体的需求增加(对于加密/解密,密钥可以跨调用维护),那么我们可以重新评估。 因此,遵循该模式的 API 提案似乎是有益的。 尽管 SIV 和 CCM 都不支持流式加密,但它们的流式 API 可能会大量缓冲。 保持清晰似乎更好。

提案也不应将标签嵌入有效负载中(GCM 和 CCM 将其称为单独的数据),除非算法本身 (SIV) 将其合并到加密输出中。 ( E(...) => (c, t)E(...) => c || tE(...) => t || c )。 API 的用户当然可以将其用作 concat(只需适当地打开 Span)。

GCM 规范不允许在标签不匹配时发布除FAIL以外的任何内容。 NIST 对此非常清楚。 McGrew & Viega 的原始 GCM 论文还说:

解密操作将返回FAIL而不是明文,解封装将停止,明文将被丢弃而不是转发或进一步处理。

之前的评论都没有提到 RUP——他们只是挥手把它扔掉(“更高层会处理它”——是的,对)。

很简单:GCM 加密可以流式传输。 GCM 解密无法流式传输。 其他任何东西都不再是 GCM。

似乎第一波允许它,而后来的波则不允许。 由于我们无可争辩地处于后期浪潮中,我认为我们将坚持不允许它。

@bartonjs ,您实际上是在忽略所有技术和逻辑分析,而是使用 Go 和 libsodium 项目的日期作为实际分析的弱代理? 想象一下,如果我根据项目名称提出类似的论点。 另外,我们正在决定接口和实现。 您确实意识到为 AEAD 决定非流接口会排除所有此类实现,对吗?

如果在引入 one-shot 模型后对流媒体的需求增加(对于加密/解密,密钥可以跨调用维护),那么我们可以重新评估。

为什么到目前为止在 GitHub 上展示的需求不足? 它已经到了支持必须做的工作少于任何技术或客户需求优点的工作似乎完全异想天开的地步。

@bartonjs ,您实际上是在忽略所有技术和逻辑分析,而是使用 Go 和 libsodium 项目的日期作为实际分析的弱代理?

不,我正在使用专业密码学家的建议,他们说这是非常危险的,我们应该避免使用流式传输 AEAD。 然后我使用了来自 CNG 团队的信息:“很多人说他们在理论上想要它,但实际上几乎没有人这样做”(我不知道其中有多少是遥测与来自现场援助请求的轶事)。 其他图书馆采取一次性路线的事实只是_加强_决定。

为什么到目前为止在 GitHub 上展示的需求不足?

已经提到了一些场景。 处理碎片化的缓冲区可能会通过接受ReadOnlySequence来解决,如果看起来有足够的场景来保证使 API 复杂化而不是让调用者进行数据重组。

大文件是个问题,但大文件已经是个问题了,因为 GCM 的截止值接近 64GB,这“不是那么大”(好吧,它相当大,但不是“哇,那是大”)它曾经是)。 内存映射文件将允许使用 Spans(最多 2^31-1)而不需要 2GB 的 RAM。 所以我们已经从最大值中减少了几位……无论如何,这可能会随着时间的推移而发生。

您确实意识到,为 AEAD 决定非流接口会排除所有此类实现,对吗?

我越来越相信@GrabYourPitchforks是正确的(https://github.com/dotnet/corefx/issues/23629#issuecomment-334638891)可能没有一个明智的统一界面。 GCM _requiring_ 一个 nonce/IV 和 SIV _forbidding_ 这意味着 AEAD 模式/算法的初始化已经需要了解将要发生的事情......对于 AEAD 并没有真正的“抽象”概念。 SIV 规定了“标签”的去向。 GCM/CCM 没有。 根据规范,SIV 是标签优先的。

SIV 在拥有所有数据之前无法开始加密。 所以它的流加密要么会抛出(这意味着你必须知道不要调用它)或缓冲区(这可能会导致 n^2 操作时间)。 在知道长度之前,CCM 无法启动; 但是 CNG 不允许对长度进行预加密提示,所以它在同一条船上。

我们不应该设计一个新组件,默认情况下做错事比做对事更容易。 流式解密使得连接 Stream 类变得非常容易和诱人(你建议使用 CryptoStream 这样做),这使得在验证标签之前很容易获得数据验证错误,这几乎完全抵消了 AE 的好处. ( IGcmDecryptor => CryptoStream => StreamReader => XmlReader => "等等,这不是合法的 XML..." => 自适应密文 oracle) .

说到重点……客户需求。

不幸的是,我一生中听过太多次:对不起,您不是我们心目中的客户。 我承认,也许您知道如何安全地进行 GCM。 您知道在标签验证之前只流式传输到易失性文件/缓冲区/等。 您知道 nonce 管理意味着什么,并且您知道出错的风险。 您知道要注意流大小并在 2^36-64 字节后切换到新的 GCM 段。 你知道,在这一切都说了又做了之后,如果你把这些事情弄错了,那就是你的错误。

另一方面,我心目中的客户是知道“我必须加密这个”的人,因为他们的老板告诉他们这样做。 他们知道,在搜索如何进行加密时,一些教程说“始终使用 AE”并提到 GCM。 然后他们找到了使用 CryptoStream 的“.NET 中的加密”教程。 然后他们连接管道,不知道他们刚刚做了与选择 SSLv2 相同的事情……理论上勾选了一个框,但实际上并没有。 而当他们这样做时,_那个_错误属于每个知道得更好的人,但让错误的事情太容易做。

你不是我们心目中的客户 [...] 另一方面,我心目中的客户是知道“我必须加密这个”的人,因为他们的老板告诉他们 [...]

@bartonjs几个月前,我们已经决定目标是通过使用低级 API(强大但在某些条件下不安全)和高级 API(万无一失)来定位两个客户档案。 甚至在标题中。 它当然是一个自由的国家,但现在通过声称其他方式来移动球门柱是不诚实的。

另一方面,我心目中的客户是知道“我必须加密这个”的人,因为他们的老板告诉他们这样做。 他们知道,在搜索如何进行加密时,一些教程说“始终使用 AE”并提到 GCM。 然后他们找到了使用 CryptoStream 的“.NET 中的加密”教程。 然后他们连接管道,不知道他们刚刚做了与选择 SSLv2 相同的事情……理论上勾选了一个框,但实际上并没有。 而当他们这样做的时候,这个错误属于每个更了解的人,但是让错误的事情太容易做。

@bartonjs等等,低级原语发生了什么? 我认为这个特定问题的目的是灵活性而不是保姆。 如果计划发生变化,请务必让我们知道,这样我们都在谈论同一件事。

此外,是否仍在考虑按块方法,或者只是一次性方法?

我越来越相信@GrabYourPitchforks是正确的(#23629(评论))可能没有一个明智的统一界面。

看到所有的例子,这确实开始看起来越来越徒劳 - 特别是对于实现具有如此不同限制的低级 API。 也许我们应该仅仅用 AES-GCM 树立一个可靠的例子,而不是一个统一的接口。 作为旁注,后者对于未来的高级 API 可能仍然很有趣。 它具有更多限制性的特性可能会使统一界面更容易在那里实现。

每个块的方法仍在考虑中,还是只是一次性方法?

https://github.com/dotnet/corefx/issues/23629#issuecomment -378605071 中所述,我们认为风险与回报与表达用例的对比表明我们应该只允许 AE 的一次性版本。

我没有阅读整个讨论,只是随机部分。 不知道你往哪个方向走。 对不起,如果我写的东西在这种情况下没有意义。 我的 2 美分:

  • 流很重要。 如果你不能直接支持它们,因为这意味着安全漏洞,那么如果可能的话,提供一个构建在你的低级 API 之上的更高级别的包装器,它会暴露流(以一种低效但安全的方式)。
  • 如果 AES-GCM 绝对不能在任何场景中使用流,那么提供基于流的 AES-CBC-HMAC 的合法实现。 或者其他一些AE算法。
  • 级别越高越好。 用户犯错的区域越少越好。 含义 — 公开 API 以隐藏尽可能多的内容(例如此身份验证标签)。 当然,也可以(应该)有更具体的重载。
  • 恕我直言,如果它们根本不适合,则不会费心将与其他加密服务的接口统一起来。 这就是openssl 对他们的CLI 所做的事情,结果很差(例如,不可能提供身份验证标签)。

我们(.NET 安全团队)在我们自己之间以及与微软内部更广泛的加密团队进行了协商。 我们讨论了这个线程中提到的许多问题和担忧。 最终,这些担忧不足以说服将流式 GCM API 作为框架内的核心构建块引入。

如果需要,将来可以重新考虑该决定。 与此同时,我们并没有让事情变得比现在更糟:目前正在使用第三方加密库来支持流式 GCM 的开发人员可以继续这样做,并且不会因为我们打算引入的非流式 GCM API。

如何处理不适合内存的数据加密?

@pgolebiowski您使用专门设计用于提供安全流加密的高级 .NET 加密库

@sdrapkin这说起来容易做起来难。 “安全”有很多要求。 有什么经过证明并且实际上可以信任的? 你自己说:

Bouncy Castle c# 库(典型的 StackOverflow 推荐)。 Bouncy Castle c# 是一个巨大的(145k LOC),性能不佳的加密博物馆目录(其中一些是古老的),旧的 Java 实现移植到同样旧的 .NET(2.0?)。

好的,那么有哪些选择? 也许你自己的图书馆? 不是真的。 嗯......也许是libsodium-net? 不是真的

当您实际寻找来自相当可信赖的来源(如 Microsoft 或社区广泛使用)的经过审核的库时,我认为 .NET Core 世界中不存在这样的库。


  • 客户:对不适合内存的数据进行身份验证加密?
  • 微软:很抱歉,你不是我们心目中的客户。 使用未经审核且其安全性值得怀疑的库,如果您受到侧信道攻击,这不是我们的问题。
  • 顾客:

@pgolebiowski选项是使用已建立的 .NET 框架 - 即。 如您所愿,已经证明并且可以信任的东西。 其他库(包括我的)等待 Microsoft 将 ECDH 等缺失的加密原语添加到 NetStandard 中。

您还可以查看 Inferno前叉。 至少有 2 个分支,其中一些微不足道的更改实现了 NetStandard20。

你的图书馆在2 年前一个人2 天内审计过。 我不相信,对不起。 在微软,会有一个专门的团队来做这件事——我比 Cure53 的人更信任这些人。

说真的,我们可以谈论很多事情的第三方支持。 但是所有需要的与安全相关的东西都应该由标准库提供。

@pgolebiowski试图说服任何人相信任何事情都不是我的想法,但你的陈述并不准确。 Inferno 由来自“Cure53”组织的 2 名专业人员审核。 审计耗时 2 天,整个库大约有 1,000 行代码。 这是每个审计员/天约 250 行代码 - 非常易于管理。

事实上,简单的可审计性是 Inferno 的主要功能之一,正是为那些不想信任的人准备的。

此线程的目标是添加对 AES-GCM 的支持。 您的库甚至不支持 AES-GCM。 如果您希望人们使用您的代码,请将其作为 corefx 的提案提交。 在适当的线程中。

另一件事,即使它支持这种算法——它还没有经过 .net 加密委员会的审查,也不是 corefx 的一部分。 它甚至不适合进行这样的审查。 这意味着这种徒劳的讨论和广告的结束。

@pgolebiowski我没有做任何广告——只是回答了你的问题,为你的用例提出了替代方案,并纠正了不准确的说法。 AES-GCM 不适合流式加密,这.NET 安全团队已经审查和同意的,因此您可以信任.

我是否在任何地方为流式传输 AES-GCM 辩护? 不记得了。 但可以回忆说:

  • 流很重要。 如果你不能直接支持它们,因为这意味着安全漏洞,那么如果可能的话,提供一个构建在你的低级 API 之上的更高级别的包装器,它会暴露流(以一种低效但安全的方式)。
  • 如果 AES-GCM 绝对不能在任何场景中使用流,那么提供基于流的 AES-CBC-HMAC 的合法实现。 或者其他一些AE算法。

或为 corefx 说明一个未解决的问题:

如何处理不适合内存的数据加密?


奖金:

您还可以查看 Inferno 前叉。 至少有 2 个分支,其中一些微不足道的更改实现了 NetStandard20。 [...] Inferno 由来自“Cure53”组织的 2 位专业人士审核 [...] 易于审核是 Inferno 的主要功能之一,正是针对那些不想信任的人。

我没有做任何广告

顺便说一句,从大多数人的想法来看,我从来没有真正想要“流媒体”。 出于性能和内存使用的原因,我只想“阻止”处理。 我实际上并不想“流出”结果。 这就是 openssl 和 cng 支持失去它似乎是一种耻辱,并且基本上使“原语”在我能想到的任何情况下都无用。

@Drawaes想想看,块操作可能比使用流更安全。 外行可能会接触流,但他更愿意使用一次性 API 而不是阻塞操作。 此外,块操作不能_直接_与XmlReader结合使用。 所以实际上,讨论的许多危险适用于流对象,但不适用于阻塞操作。

在一次性 API 也可用时使用块操作,这表明我们知道我们在做什么,并且我们特别需要低级别的调整。 我们可以保护外行_并且_具有灵活性。

至于避免 RUP,我仍在思考块操作对于 GCM 究竟有多大优势。 加密获得了全部好处,而解密只获得了一些好处。 我们可以避免存储完整的密文,但我们仍然必须缓冲完整的明文。 解密器_可以_选择将中间明文存储在磁盘上。 但作为回报,我们引入了更多的错误空间。 我们是否有一个令人信服的论点来不在更高的层次上解决这个问题(例如那里的块,或者使用真正的流式算法)?

TLS 和管道。 当前(以及在可预见的将来)管道使用 4k 块,但 tls 消息可以是 16k 的密文。一次性将 16k 复制到单个连续缓冲区,然后才能解密。 对于块,您可能会说 4 或 5 个,并且您可能需要缓冲多达 16 个字节以确保完整的块。

@Drawaes 16k 仍然是恒定的并且不是很大。 在这种情况下,它有很大的不同吗?

是的,这意味着管道中的另一个副本。 这对性能有重大且可衡量的影响。

需要什么来实现这一点? 什么是下一个步骤? @Drawaes

至于 AES-GCM,我认为由于相应的问题被锁定,它的交付受到了损害: https://github.com/dotnet/corefx/issues/7023。 @blowdart ,你能解锁吗? 当人们无法讨论时,真的很难有进步。 或者,如果这不是一个选项,也许可以提出一个替代解决方案,允许将此功能带给公众。

不,我不会解锁它。 决定了,话题就结束了。

@blowdart感谢您的回复。 我知道这可能不够清楚:

或者,如果这不是一个选项,也许可以提出一个替代解决方案,允许将此功能带给公众。

我很欣赏支持 AES-GCM 的决定。 这很棒,我绝对想要那个算法。 因此,现在真正支持它会很酷。 您希望在此处或在新问题中进行有关 AES-GCM 设计和实施的讨论吗?

此外,如果该主题已完成,为什么不关闭它? 并通过更改该问题的标题使其更加明确,因为现在它表明有关实施的讨论将在这里举行: https://github.com/dotnet/corefx/issues/7023。 可能类似于决定首先支持哪种 AEAD 算法

换句话说:我提供的反馈是,在当前情况下,尚不清楚需要什么来推动 AES-GCM 向前发展。

@karelz

@pgolebiowski已经有 PR 了。 它可能会在下周的主周三可用。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

nalywa picture nalywa  ·  3评论

chunseoklee picture chunseoklee  ·  3评论

jchannon picture jchannon  ·  3评论

Timovzl picture Timovzl  ·  3评论

noahfalk picture noahfalk  ·  3评论