让我们首先确定一组可能的问题,然后尝试解决它。我们有一些数据(一条记录)和一个签名。可以使用不同的算法计算签名。程序可以进化和改变它的行为,libsodium 也可以(独立地)进化和改变它的行为。在签名生成方面,我们有:
crypto_sign()
,它使用一些默认算法来产生签名(在写作的时候只是调用crypto_sign_ed25519()
)
crypto_sign_ed25519()
,它根据特定ed25519
算法生成签名
我假设对于一个给定相同输入数据和相同键的特定算法,我们将始终得到相同的结果,因为它是数学,并且任何与此规则的偏差都会使库完全无法使用。
让我们看一下两个主要选项:
- 一直在使用
crypto_sign_ed25519()
,从不改变这一点。一个不错的选择,因为它很简单,只要crypto_sign_ed25519()
存在于 libsodium 中并且输出稳定,您就无需担心稳定的固定大小签名和零管理开销。当然,将来有人可能会发现该算法存在一些可怕的问题,如果您不准备更改可能对您来说意味着可怕问题的算法。
- 使用
crypto_sign()
. 有了这个我们突然有很多问题,因为算法可以改变,所以你必须存储一些元数据以及签名,这引发了一系列问题:
对于第二种方法,我们在提到的功能中有什么?
sodium_library_version_major()
是一个告诉我们库 API 版本的函数。它与支持/默认算法的更改没有直接关系,因此对我们的问题几乎没有用处。
crypto_sign_primitive()
是一个函数,它返回一个字符串,用于标识crypto_sign()
. 这是我们需要的完美匹配,因为据说它的输出会在算法发生变化的时候发生变化。
crypto_sign_bytes()
是一个crypto_sign()
以字节为单位返回签名大小的函数。这对于确定签名所需的存储量很有用,但如果算法发生变化,它很容易保持不变,所以它不是我们需要显式存储的元数据。
既然我们知道要存储什么,就有一个处理存储数据的问题。您需要获取算法名称并使用它来调用匹配验证功能。不幸的是,据我所见,libsodium 本身并没有提供任何简单的方法来获取给定算法名称(如EVP_get_cipherbyname()
或EVP_get_digestbyname()
在 openssl 中)的正确函数,因此您需要自己制作一个(对于未知名称当然应该失败)。而且,如果您必须自己制作一个,则存储一些数字标识符而不是库中的名称可能会更容易(尽管有更多代码)。
现在让我们回到文件级与记录级。为了解决这个问题,还有两个问题要问——您能否在任何给定时间为旧记录生成新签名(这在技术上是否可行,政策是否允许)以及您是否需要将新记录附加到旧文件中?
如果您无法为旧记录生成新签名,或者您需要追加新记录并且不希望签名重新生成的性能损失,那么您没有太多选择,您需要:
- 为您的签名提供动态大小字段
- 存储用于生成签名的算法(动态字符串字段或内部(用于您的应用程序)ID)以及签名本身
如果您可以生成新签名,或者特别是如果您不需要附加新记录,那么当您将使用的算法存储在特殊文件级字段中时,如果签名算法发生变化,则可以使用更简单的文件级方法,在保存文件时重新生成所有签名(或在附加新记录时使用旧签名,这也是一个兼容性策略问题)。
其他选择?那么,有什么特别之处crypto_sign()
呢?这是它的行为不受您的控制,libsodium 开发人员为您选择算法(毫无疑问他们选择了好的算法),但是如果您的文件结构中有任何版本控制信息(不是特定于签名的,我的意思是)没有什么能阻止您做出自己的特定选择,并在一个文件版本中使用一种算法,而在另一种文件版本中使用另一种算法(当然,在需要时使用转换代码)。同样,这也是基于您可以生成新签名并且策略允许的假设。
这让我们回到了最初的两个选择,问题是与仅使用crypto_sign_ed25519()
. 这主要取决于您的程序寿命,我可能会说(只是作为一种观点)如果不到 5 年,那么只使用一种特定的算法会更容易。如果它很容易超过 10 年,那么不,你真的需要能够承受算法(甚至可能是整个加密库)的变化。