如何正确使用 libsodium 以使其在版本之间兼容?

How do I correctly use libsodium so that it is compatible between versions?

我打算将一堆记录存储在一个文件中,然后用 libsodium 对每条记录进行签名。但是,我希望我的程序的未来版本能够检查当前版本所做的签名,反之亦然。

对于当前版本的 Sodium,签名是使用 Ed25519 算法进行的。我想默认原语可以在新版本的 Sodium 中更改(我认为 libsodium 不会公开选择特定原语的方法)。

我应该...

  1. 始终使用默认原语(即crypto_sign
  2. 使用特定原语(即crypto_sign_ed25519
  3. 执行 (1),但将 sodium_library_version_major() 的值存储在文件中(在专用 'sodium version' 字段或通用 'file format revision' 字段中)并在当前 运行版本较低
  4. 做(3),还要存储crypto_sign_primitive()
  5. 做(4),还要存储crypto_sign_bytes()和朋友

...或者我应该完全做些别的事情?

我的程序将用 C 语言编写。

让我们首先确定一组可能的问题,然后尝试解决它。我们有一些数据(记录)和签名。可以使用不同的算法计算签名。程序可以进化和改变它的行为,libsodium 也可以(独立地)进化和改变它的行为。在签名生成方面,我们有:

  • crypto_sign(),它使用一些默认算法来生成签名(在撰写本文时只是调用 crypto_sign_ed25519()
  • crypto_sign_ed25519(),根据特定的ed25519算法生成签名

我假设对于一个特定的算法,给定相同的输入数据和相同的密钥,我们总是会得到相同的结果,因为它是数学的,任何偏离这个规则都会使库完全无法使用。

让我们来看看两个主要选项:

  1. 一直使用 crypto_sign_ed25519(),从不更改。一个不错的选择,因为它很简单,只要 crypto_sign_ed25519() 存在于 libsodium 中并且其输出稳定,您就无需担心稳定的固定大小签名和零管理开销。当然,将来有人可能会发现此算法存在一些可怕的问题,如果您不准备更改可能对您来说意味着可怕问题的算法。
  2. 正在使用 crypto_sign()。有了这个我们突然遇到了很多问题,因为算法可以改变,所以你必须存储一些元数据和签名,这引发了一系列问题:
    • 要存储什么?
    • 此元数据应该是记录级还是文件级?

我们在提到的第二种方法的功能中有什么?

  • sodium_library_version_major() 是一个告诉我们库 API 版本的函数。它与 supported/default 算法的变化没有直接关系,因此对我们的问题用处不大。
  • crypto_sign_primitive() 是一个函数,它 returns 一个字符串,用于标识 crypto_sign() 中使用的算法。这完全符合我们的需要,因为它的输出可能会在算法发生变化的时候发生变化。
  • crypto_sign_bytes() 是 returns 由 crypto_sign() 生成的签名大小(以字节为单位)的函数。这对于确定签名所需的存储量很有用,但如果算法发生变化,它可以很容易地保持不变,因此它不是我们需要显式存储的元数据。

现在我们知道要存储什么,接下来就是处理存储的数据的问题。您需要获取算法名称并使用它来调用匹配验证功能。不幸的是,据我所知,libsodium 本身并没有提供任何简单的方法来获得给定算法名称的正确函数(例如 openssl 中的 EVP_get_cipherbyname()EVP_get_digestbyname()),因此您需要自己制作一个(对于未知名称,这当然应该失败)。如果你必须自己制作一个,也许存储一些数字标识符而不是库中的名称会更容易(尽管有更多代码)。

现在让我们回到文件级与记录级。要解决这个问题,还有另外两个问题要问——您能否在任何给定时间为旧记录生成新签名(这在技术上是否可行,政策是否允许)以及您是否需要将新记录附加到旧文件?

如果您不能为旧记录生成新签名或者您需要附加新记录并且不希望签名重新生成的性能损失,那么您没有太多选择,您需要:

  • 您的签名有动态大小字段
  • 存储用于生成签名的算法(动态字符串字段或内部(对于您的应用程序)ID)以及签名本身

如果您可以生成新签名,或者特别是如果您不需要附加新记录,那么当您将算法存储在一个特殊的文件级字段中时,您可以使用更简单的文件级方法,并且,如果签名算法发生变化,则在保存文件时重新生成所有签名(或者在添加新记录时使用旧签名,这也是一个兼容性策略问题)。

其他选择?那么,crypto_sign() 有什么特别之处?它的行为不在你的控制之下,libsodium 开发人员为你选择算法(毫无疑问他们选择了好的算法),但是如果你的文件结构中有任何版本控制信息(不是特定于签名的,我的意思是)没有什么能阻止你做出您自己的特定选择,并对一个文件版本使用一种算法,对另一个文件版本使用另一种算法(当然,在需要时使用转换代码)。同样,这也是基于您 可以 生成新签名并且策略允许的假设。

这让我们回到了最初的两个选择,问题是与仅使用 crypto_sign_ed25519() 相比,这样做是否值得。这主要取决于您的程序生命周期,我可能会说(只是作为一种意见)如果它少于 5 年,那么只使用一种特定的算法会更容易。如果它很容易超过 10 年,那么不,你真的需要能够经受住算法(甚至可能是整个加密库)的变化。

就用高级API.

高级函数 API 不会使用不同的算法,除非库的主要版本发生变化。

libsodium 1.x.y 中唯一可以预期的重大变化是删除了 deprecated/undocumented 函数(在使用 --enable-minimal 开关编译的当前版本中甚至不存在)。其他一切都将保持向后兼容。

新算法可能会在没有高级包装器的 1.x.y 版本中引入,并将通过 libsodium 2 中的新高级 API 稳定和公开。

因此,请不要打扰crypto_sign_ed25519()。只需使用 crypto_sign().