无法使用 "Libsodium-net" (C#) 打开由 TweetNacl (Java) 生成的 "SecretBox"

Cannot open "SecretBox" generated by TweetNacl (Java) with "Libsodium-net" (C#)

我在 C# 中使用 libsodium-net 库打开 Nacl SecretBox(使用 TweetNaclFast 库在 java 中生成)时遇到问题。 我也不能反其道而行之(使用 TweetNaclFast 打开 libsodium-net 生成的框)。

在下面的示例中,我将使用 TweetNaclFast (Java) 创建一个 SecretBox,并尝试使用 libsodium-net (C#) 打开它

创建 SecretBox (Java)

    String secretMessage = "Hello Stack overflow!";
    byte[] messageBytes = secretMessage.getBytes("UTF-8");
    byte[] keyBytes = secureRandomGenerator(); //returns 32 random bytes (256 bits)
    byte[] nonceBytes = TweetNaclFast.makeSecretBoxNonce();

    byte[] boxBytes = new TweetNaclFast.SecretBox(keyBytes).box(messageBytes,nonceBytes);

    System.out.println("Base64 box -> "+Base64.encodeBase64String(boxBytes));
    System.out.println("Base64 key -> "+Base64.encodeBase64String(keyBytes));
    System.out.println("Base64 nonce -> "+Base64.encodeBase64String(nonceBytes));

创作输出

Base64 box -> iNEpgwFIo6nyaLNgMpSWqwTQ9Z5y/y+BUXszXVFZ2gP2A3XJ0Q==
Base64 key -> FKpCo/AhRRUjdQIpzMbZSnnzfBx1e/Ni9VZyNWYEB8E=
Base64 nonce -> 2qngWbMLFVNiPTFqTVO9nsraB8ACIrwV

打开 SecretBox (C#)

string box = "iNEpgwFIo6nyaLNgMpSWqwTQ9Z5y/y+BUXszXVFZ2gP2A3XJ0Q==";
string key = "FKpCo/AhRRUjdQIpzMbZSnnzfBx1e/Ni9VZyNWYEB8E=";
string nonce = "2qngWbMLFVNiPTFqTVO9nsraB8ACIrwV";


        try
        {
            byte[] message = Sodium.SecretBox.Open(
            Convert.FromBase64String(box),
            Convert.FromBase64String(nonce),
            Convert.FromBase64String(key));

            Console.WriteLine(Encoding.UTF8.GetString(message));
        }
        catch (CryptographicException e)
        {
            Console.WriteLine(e.Message);
            Console.WriteLine(e.StackTrace);
        }

打开输出

Failed to open SecretBox
   at Sodium.SecretBox.Open(Byte[] cipherText, Byte[] nonce, Byte[] key)

知道我可能做错了什么吗?


编辑

我猜问题出在其中一个库(最有可能是 libsodium-net)。 如果我用相同的变量创建一个 SecretBox,我会得到一个不同的盒子...

使用 TweetNaclFast 创建一个秘密盒子

String message = "Hello Stack overflow!";
String key = "uCEgauAQDWGDkcclGe1rNV6V77xtizuemhgxzM5nqO4=";
String nonce = "+RTDstWX1Wps5/btQzSMHWBqHU9s6iqq";

SecretBox box = new SecretBox(Base64.decodeBase64(key));
byte[] cipherText = box.box(message.getBytes("UTF-8"), Base64.decodeBase64(nonce));

RETURNS: yDCt/kOLFUWPZpV3deVNUZaH0ZHLVmj9Nvm8QlbVKPe1a/INDw==

使用 libsodium-net 创建一个秘密盒子

string message = "Hello Stack overflow!";
string key = "uCEgauAQDWGDkcclGe1rNV6V77xtizuemhgxzM5nqO4=";
string nonce = "+RTDstWX1Wps5/btQzSMHWBqHU9s6iqq";

byte[] box = Sodium.SecretBox.Create(Encoding.UTF8.GetBytes(message), 
                                             Convert.FromBase64String(nonce), 
                                             Convert.FromBase64String(key));

Console.WriteLine(Convert.ToBase64String(box));

RETURNS: AAAAAAAAAAAAAAAAAAAAAMgwrf5DixVFj2aVd3XlTVGWh9GRy1Zo/Tb5vEJW1Sj3tWvyDQ8=

我应该阅读文档 (RTFM)... 显然 libsodium-net 在密文的开头添加了一个 16 字节的身份验证标记 (https://bitbeans.gitbooks.io/libsodium-net/content/secret-key_cryptography/authenticated_encryption.html)。如果我删除前 16 个字节,我会得到与 TweetNaclFast SecretBox 相同的输出。

string message = "Hello Stack overflow!";
string key = "uCEgauAQDWGDkcclGe1rNV6V77xtizuemhgxzM5nqO4=";
string nonce = "+RTDstWX1Wps5/btQzSMHWBqHU9s6iqq";

byte[] box = Sodium.SecretBox.Create(Encoding.UTF8.GetBytes(message), 
                                             Convert.FromBase64String(nonce), 
                                             Convert.FromBase64String(key));

byte[] boxWithoutAuthenticationTag = new byte[box.Length - 16];
Array.Copy(box, 16, boxWithoutAuthenticationTag, 0, box.Length - 16);
Console.WriteLine(Convert.ToBase64String(boxWithoutAuthenticationTag));

现在 returns: yDCt/kOLFUWPZpV3deVNUZaH0ZHLVmj9Nvm8QlbVKPe1a/INDw==


要打开(解密)第一个示例的秘密盒子,请使用以下代码:

string box = "iNEpgwFIo6nyaLNgMpSWqwTQ9Z5y/y+BUXszXVFZ2gP2A3XJ0Q==";
string key = "FKpCo/AhRRUjdQIpzMbZSnnzfBx1e/Ni9VZyNWYEB8E=";
string nonce = "2qngWbMLFVNiPTFqTVO9nsraB8ACIrwV";

try
{
    //Libsodium-net SecretBox.Open() requires a 16 byte authentication tag at the start of the ciphertext
    //TweetNaclFast boxing method does not append a 16 byte authentication tag anywhere
    //Thus, we need to add a 16 byte authentication tag at the start of ciphertext encripted by TweetNaclFast
    byte[] authenticationTag = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //Zeroed 16 Bytes Authentication Tag
    byte[] tweetNaclFastCiphertextBytes = Convert.FromBase64String(box);
    byte[] libsodiumNetLikeCiphertext = new byte[tweetNaclFastCiphertextBytes.Length + authenticationTag.Length];
    Array.Copy(authenticationTag, libsodiumNetLikeCiphertext, authenticationTag.Length);
    Array.Copy(tweetNaclFastCiphertextBytes, 0, libsodiumNetLikeCiphertext, authenticationTag.Length, tweetNaclFastCiphertextBytes.Length);
    byte[] nonceBytes = Convert.FromBase64String(nonce);
    byte[] keyBytes = Convert.FromBase64String(key);

    Console.WriteLine(Encoding.UTF8.GetString(Sodium.SecretBox.Open(libsodiumNetLikeCiphertext, nonceBytes, keyBytes)));
}
catch (CryptographicException e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine(e.StackTrace);
}

现在应该 return Hello Stack overflow!

Sodium.SecretBox.Create使用原始NaClcrypto_box()API,需要在消息和密文前额外填充

这个 API 有点令人困惑,除了在 C 中很少有用。即使在 C 中,使用它的人最终也会编写包装器来添加或删除填充。

boxsecretbox 结构,正如大多数 API 所公开的那样,不需要额外的填充。之前没有额外增加16字节,直接返回密文。消息可以直接给定,不用前置16个nul字节。

TweetNaclFast 不需要任何填充,但 libsodium-net 显然需要。

您使用 libsodium-net 观察到的密文之前的额外 16 个字节不包含任何有用的信息。这只是一堆零。您可以安全地删除它们,稍后在调用 Sodium.SecretBox.Open.

时添加它们

请注意,与 Sodium.SecretBox 不同,Sodium.PublicKeyBox 不需要填充。