開源、先進、易用加密庫 Libsodium 中文指南

totravel發表於2021-02-07

? 1. 簡介

Libsodium 是一個開源、跨平臺、跨語言的加密庫,提供了一組簡單易用的函式,大大簡化了加密、雜湊、簽名、鑑別、解密等複雜工作。支援許多種主流的加密演算法和雜湊演算法,包括 AES256-GCM 和 ChaCha20-Poly1305 兩種 AEAD 加密方案。此外還提供了一系列方便實用的函式,可完成隨機數的生成、大數的計算、編碼和解碼等輔助性工作。

? 2. 起步

執行以下命令,完成 Libsodium 的下載、解壓、編譯和安裝。

$ yum -y groupinstall "Development Tools" # apt install -y build-essential
$ wget -N --no-check-certificate https://download.libsodium.org/libsodium/releases/libsodium-1.0.17.tar.gz
$ tar -zxf libsodium-1.0.17.tar.gz
$ cd libsodium-1.0.17
$ ./configure
$ make && make check
$ make install

Libsodium 的動態連結庫 lib*.so* 位於 /usr/local/lib 目錄中。須將此目錄設為動態庫的搜尋目錄之一,否則依賴於 Libsodium 的程式將無法執行。

$ echo "/usr/local/lib" > /etc/ld.so.conf.d/usr-local-lib.conf
$ ldconfig

/usr/local/lib/pkgconfig 目錄中,可以找到檔案 libsodium.pc。為了能夠透過命令 pkg-config 獲取編譯和連結所需引數,須將此檔案複製到 pkg-config 命令的搜尋目錄中。

$ cp /usr/local/lib/pkgconfig/libsodium.pc /usr/share/pkgconfig/

透過命令 pkg-config 獲取編譯和連結所需引數。

$ pkg-config --cflags libsodium
-I/usr/local/include
$ pkg-config --libs libsodium
-L/usr/local/lib -lsodium

如果使用了 make,應當在 Makefile 檔案中使用上述兩條命令。

CFLAGS = $(pkg-config --cflags libsodium)
LDFLAGS = $(pkg-config --libs libsodium)

而在程式中,只需包含標頭檔案 sodium.h 即可。

#include <sodium.h>

int main(void)
{
    if (sodium_init() == -1) {
        return 1;
    }
    ...
}

在使用 Libsodium 的其他函式之前,必須先呼叫函式 sodium_init()。該函式不需要任何引數,返回 0 表示成功,返回 -1 表示失敗,返回 1 則表示已經初始化過了。

? 3. 知識儲備

3.1 兩種密碼體制

當前有兩種密碼體制:一種稱為對稱金鑰密碼體制;另一種稱為公鑰密碼體制。

在對稱金鑰密碼體制中,加密和解密使用相同的金鑰。金鑰由通訊雙方事先約定。演算法可以公開,而金鑰需要保密。

公鑰密碼體制在加密和解密過程中使用不同的金鑰。並且使用其中一個進行加密,則需要用另一個才能解密。這兩個成對的金鑰在使用時,一個金鑰作為私鑰,需要保密;另一個金鑰作為公鑰,可以公開。

3.2 對稱加密演算法

對稱加密演算法分為分組密碼(又稱塊加密)和序列密碼(又稱流密碼、流加密)兩種。

  • 著名的分組密碼:DES、AES
  • 常用的流密碼:Salsa20、ChaCha20

3.3 MAC 報文鑑別碼

MAC 是 Message Authentication Code 的縮寫,即報文鑑別碼。通常是經過加密的雜湊值。計算報文鑑別碼的演算法稱為 MAC 演算法。常用的 MAC 演算法有:

  • GMAC
  • CBC-MAC
  • Poly1305

3.4 AE

AE 是 Authenticated encryption 的縮寫。顧名思義,這種加密方案不僅能提供機密性,還能提供完整性。任何偽造或篡改都會被發現。

AE 實際上是對稱加密演算法和 MAC 演算法的結合體。在加密一個報文時,需要一個金鑰和一個不重數,加密後將得到密文和一個報文鑑別碼。報文鑑別碼須隨同密文一起傳送給接收方。

接收方收到報文鑑別碼和密文後,須用相同的金鑰和不重數才能進行解密。任何對密文和報文鑑別碼的篡改都會導致解密失敗。當然,只要確保金鑰沒有洩露,其他人也無法偽造出合法的密文和相應的報文鑑別碼。

不重數,即不重複的數,通常是從 0 開始遞增的計數器,不需要保密。金鑰和不重數的結合,相當於一次一密,能有效抵禦「重放攻擊」。

3.5 AEAD

AEAD 是 Authenticated Encryption with Additional Data 的縮寫。相比於 AE,AEAD 在加、解密時還可以選擇性地給定一些沒有保密性要求的「附加資料」,例如版本號、時間戳、報文的長度和編碼方式等。這些附加資料會參與到報文鑑別碼的計算中去,但不會被加密,也不會成為密文的一部分。附加資料可以隨同密文一起傳送。

常用的 AEAD 有以下兩種:

  • AES256-GCM
  • ChaCha20-Poly1305

Intel 在 2008 年推出新的指令集——AES-NI,為 AES 演算法提供了硬體層面上的支援。但在其他平臺(ARM)上,針對移動網際網路最佳化的 ChaCha20 的速度大約是 AES 的三倍。

ChaCha20-Poly1305 最初在 2014 年提出,在 2015 年成為 IETF 標準,即 ChaCha20-Poly1305-IETF。後來,又透過對 ChaCha20 的改進,形成 XChaCha20-Poly1305-IETF。這一版本有望成為新的 IETF 標準,也是 Libsodium 目前首推的加密方案。

不同 AEAD 的金鑰、不重數和報文鑑別碼的長度(單位:位):

AEAD Key 金鑰 Nonce 不重數 MAC 報文鑑別碼
AES256-GCM 256 96 128
ChaCha20-Poly1305 256 64 128
ChaCha20-Poly1305-IETF 256 96 128
XChaCha20-Poly1305-IETF 256 192 128

? 4. Libsodium 對 ChaCha20-Poly1305 的支援

Libsodium 為 ChaCha20-Poly1305 的三種版本分別提供了三組函式:

  • crypto_aead_chacha20poly1305_*()
  • crypto_aead_chacha20poly1305_ietf_*()
  • crypto_aead_xchacha20poly1305_ietf_*()

這三組函式在用法上完全一致,因此只要掌握了其中一種,自然也就掌握了其餘兩種。

4.1 加密

int crypto_aead_xchacha20poly1305_ietf_encrypt_detached(unsigned char *c,
                                                        unsigned char *mac,
                                                        unsigned long long *maclen_p,
                                                        const unsigned char *m,
                                                        unsigned long long mlen,
                                                        const unsigned char *ad,
                                                        unsigned long long adlen,
                                                        const unsigned char *nsec,
                                                        const unsigned char *npub,
                                                        const unsigned char *k);

函式 crypto_aead_xchacha20poly1305_ietf_encrypt_detached() 使用金鑰 k
不重數 npubmlen 位元組的報文 m 進行加密,並根據密文和 adlen 位元組的附加資料 ad 計算報文鑑別碼。密文將被寫到 c,而報文鑑別碼將被寫到 macmaclen 會被設為 mac 的長度。

密文和明文等長。而金鑰、不重數、報文鑑別碼的長度都是固定的,它們分別等於:

  • crypto_aead_xchacha20poly1305_ietf_KEYBYTES
  • crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
  • crypto_aead_xchacha20poly1305_ietf_ABYTES

若沒有關聯的資料,則把 ad 設為 NULL,並把 adlen 設為 0。

此處 nsec 必須始終設為 NULL,下同。

#include <sodium.h>

int main(void)
{
    if (sodium_init() == -1) {
        return 1;
    }

    // 金鑰
    unsigned char k[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] = "123456";
    // 不重數
    unsigned char npub[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] = {0};

    // 明文
    int mlen = 5;
    unsigned char m[6] = "hello";
    // 附加的資料
    int adlen = 4;
    unsigned char ad[5] = "2020";

    // 密文
    unsigned char c[6];
    // 報文鑑別碼
    unsigned char mac[crypto_aead_xchacha20poly1305_ietf_ABYTES];
    unsigned long long maclen;

    // 加密
    crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c,
                                                        mac, &maclen,
                                                        m, mlen,
                                                        ad, adlen, NULL,
                                                        npub, k);
    // 獲取密文和報文鑑別碼的十六進位制表示
    char buf[1024];
    sodium_bin2hex(buf, sizeof buf, c, 5);
    printf("Ciphertext: %s\n", buf);

    sodium_bin2hex(buf, sizeof buf, mac, maclen);
    printf("MAC: %s\n", buf);

    return 0;
}
Ciphertext: 5abc40d737
MAC: 0be7cd4beaf9ec2a063170aab65fa5aa

函式 sodium_bin2hex() 是 Libsodium 提供的「輔助函式」,具體用法詳見下文。

在金鑰不變的情況下,不重數必須每次都不一樣。建議用 randombytes_buf() 函式產生第一條報文的不重數,再用 sodium_increment() 函式對其進行遞增。

4.2 解密

解密必須提供相同的金鑰 k、不重數 npub 和附加資料 ad

int crypto_aead_xchacha20poly1305_ietf_decrypt_detached(unsigned char *m,
                                                        unsigned char *nsec,
                                                        const unsigned char *c,
                                                        unsigned long long clen,
                                                        const unsigned char *mac,
                                                        const unsigned char *ad,
                                                        unsigned long long adlen,
                                                        const unsigned char *npub,
                                                        const unsigned char *k);

函式 crypto_aead_xchacha20poly1305_ietf_decrypt_detached() 首先驗證 c 中包含的 tag 是否合法。若函式返回 -1 表示驗證未透過;若驗證透過,則返回 0,並將解密得到的報文寫到 m

#include <sodium.h>

int main(void)
{
    if (sodium_init() == -1) {
        return 1;
    }

    // 金鑰
    unsigned char k[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] = "123456";
    // 不重數
    unsigned char npub[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] = {0};

    // 明文
    unsigned char m[6];
    // 附加的資料
    int adlen = 4;
    unsigned char ad[5] = "2020";

    // 密文
    int clen = 5;
    unsigned char c[6];
    sodium_hex2bin(c, clen, "5abc40d737", 10, NULL, NULL, NULL);

    // 報文鑑別碼
    unsigned char mac[crypto_aead_xchacha20poly1305_ietf_ABYTES];
    sodium_hex2bin(mac, crypto_aead_xchacha20poly1305_ietf_ABYTES,
                   "0be7cd4beaf9ec2a063170aab65fa5aa", 32, NULL, NULL, NULL);

    // 解密
    crypto_aead_xchacha20poly1305_ietf_decrypt_detached(m,
                                                        NULL,
                                                        c, clen,
                                                        mac,
                                                        ad, adlen,
                                                        npub, k);
    printf("Message: %s\n", m);

    return 0;
}
Message: hello

4.3 合併模式

以上這種將密文和報文鑑別碼分開儲存的方式稱為分開模式。由於大多數需求都是將報文鑑別碼直接追加到密文後面,即合併模式。因此,Libsodium 實際上為每種 AEAD 方案都提供兩組函式:一組實現分開模式;另一組實現合併模式。

為合併模式設計的函式,相比於分開模式的函式,函式名少了字尾 _detached

int crypto_aead_xchacha20poly1305_ietf_encrypt(unsigned char *c,
                                               unsigned long long *clen_p,
                                               const unsigned char *m,
                                               unsigned long long mlen,
                                               const unsigned char *ad,
                                               unsigned long long adlen,
                                               const unsigned char *nsec,
                                               const unsigned char *npub,
                                               const unsigned char *k);

金鑰、不重數、附加資料、明文等引數的含義同上。在合併模式下,報文鑑別碼直接追加到密文後面,因此減少了 macmaclen 兩個引數,但引數 c 必須為報文鑑別碼預留儲存空間。

#include <sodium.h>

int main(void)
{
    if (sodium_init() == -1) {
        return 1;
    }

    // 金鑰
    unsigned char k[crypto_aead_xchacha20poly1305_ietf_KEYBYTES] = "123456";
    // 不重數
    unsigned char npub[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES] = {0};

    // 明文
    int mlen = 5;
    unsigned char m[6] = "hello";
    // 附加的資料
    int adlen = 4;
    unsigned char ad[5] = "2020";

    // 密文
    unsigned char c1[6];
    unsigned char c2[6 + crypto_aead_xchacha20poly1305_ietf_ABYTES];
    unsigned long long clen;
    // 報文鑑別碼
    unsigned char mac[crypto_aead_xchacha20poly1305_ietf_ABYTES];
    unsigned long long maclen;

    // 加密(分開模式)
    crypto_aead_xchacha20poly1305_ietf_encrypt_detached(c1,
                                                        mac, &maclen,
                                                        m, mlen,
                                                        ad, adlen, NULL,
                                                        npub, k);
    char buf[1024];
    sodium_bin2hex(buf, sizeof buf, c1, 5);
    printf("Ciphertext: %s\n", buf);

    sodium_bin2hex(buf, sizeof buf, mac, maclen);
    printf("MAC: %s\n", buf);

    // 加密(合併模式)
    crypto_aead_xchacha20poly1305_ietf_encrypt(c2,
                                               &clen,
                                               m, mlen,
                                               ad, adlen, NULL,
                                               npub, k);
    sodium_bin2hex(buf, sizeof buf, c2, clen);
    printf("Ciphertext: %s\n", buf);

    return 0;
}
Ciphertext: 5abc40d737
MAC: 0be7cd4beaf9ec2a063170aab65fa5aa
Ciphertext: 5abc40d7370be7cd4beaf9ec2a063170aab65fa5aa

? 5. 金鑰的派生

在實際應用中,不應從始至終都使用同一個金鑰,更不能直接使用密碼(通常是簡短的字串)作為金鑰,否則很容易遭受「字典攻擊」。應當為每次會話專門準備一個子金鑰。這就需要一種能夠產生大量子金鑰的機制。

5.1 KDF

KDF 是 Key Derivation Function 的縮寫,即金鑰派生函式。能夠滿足上述需求。這類函式透過引入隨機數、增加雜湊迭代次數,增加暴力破解難度。常用的 KDF 有:

  • PBKDF2
  • Scrypt
  • Argon2

Argon2 是最新的演算法,也是 Libsodium 首推及其底層預設使用的演算法。

5.2 基於密碼派生金鑰

根據給定的密碼和一個長度固定的隨機數生成指定長度的金鑰。

int crypto_pwhash(unsigned char * const out,
                  unsigned long long outlen,
                  const char * const passwd,
                  unsigned long long passwdlen,
                  const unsigned char * const salt,
                  unsigned long long opslimit,
                  size_t memlimit, int alg);

函式 crypto_pwhash() 根據 passwdlen 位元組的密碼 passwdcrypto_pwhash_SALTBYTES 位元組的隨機數 salt 派生出 outlen 位元組的金鑰並儲存到 out 中。全部引數相同時,生成相同的金鑰。

\ passwdlen outlen
最小值 crypto_pwhash_PASSWD_MIN crypto_pwhash_BYTES_MIN
最大值 crypto_pwhash_PASSWD_MAX crypto_pwhash_BYTES_MAX

倒數兩個引數 opslimitmemlimit 與效能和記憶體佔用有關,取值如下:

\ opslimit memlimit
最小值 crypto_pwhash_OPSLIMIT_MIN crypto_pwhash_MEMLIMIT_MIN
較快/小 crypto_pwhash_OPSLIMIT_INTERACTIVE crypto_pwhash_MEMLIMIT_INTERACTIVE
中等 crypto_pwhash_OPSLIMIT_MODERATE crypto_pwhash_MEMLIMIT_MODERATE
較慢/大 crypto_pwhash_OPSLIMIT_SENSITIVE crypto_pwhash_MEMLIMIT_SENSITIVE
最大值 crypto_pwhash_OPSLIMIT_MAX crypto_pwhash_MEMLIMIT_MAX

最後一個引數 alg 決定選用的演算法,只有下列 3 種取值可選:

  • crypto_pwhash_ALG_DEFAULT Libsodium 推薦的選項。
  • crypto_pwhash_ALG_ARGON2I13 Argon2i 1.3。
  • crypto_pwhash_ALG_ARGON2ID13 Argon2id 1.3。

函式返回 0 表示成功;返回 -1 表示失敗(這通常是由於作業系統拒絕分配請求的記憶體)。

#include <sodium.h>

int main(void)
{
    if (sodium_init() == -1) {
        return 1;
    }

    // 密碼
    unsigned char passwd[] = "secret";
    // 長度固定的隨機數
    unsigned char salt[crypto_pwhash_SALTBYTES] = {0};
    // 金鑰
    unsigned char key[16];

    crypto_pwhash(key, sizeof key, passwd, strlen(passwd), salt, 
                  crypto_pwhash_OPSLIMIT_INTERACTIVE,
                  crypto_pwhash_MEMLIMIT_INTERACTIVE,
                  crypto_pwhash_ALG_DEFAULT);

    char buf[1024];
    sodium_bin2hex(buf, sizeof buf, key, sizeof key);
    printf("key: %s\n", buf);

    return 0;
}
key: a5c2d5ca23026834f7ff177fb8137b62

5.3 基於主金鑰派生子金鑰

根據一個主金鑰生成多個子金鑰。Libsodium 專門為此提供了兩個函式 crypto_kdf_*()

這兩個函式可以根據一個主金鑰 key 和一個被稱為上下文的引數 ctx 派生出 2^64 個金鑰,並且單個子金鑰的長度可以在 128(16 位元組)到 512 位(64 位元組)之間。

void crypto_kdf_keygen(uint8_t key[crypto_kdf_KEYBYTES]);

函式 crypto_kdf_keygen() 的作用是生成一個主金鑰。

int crypto_kdf_derive_from_key(unsigned char *subkey, size_t subkey_len,
                               uint64_t subkey_id,
                               const char ctx[crypto_kdf_CONTEXTBYTES],
                               const unsigned char key[crypto_kdf_KEYBYTES]);

函式 crypto_kdf_derive_from_key() 可以根據主金鑰 key 和上下文 ctx 派生出長度為 subkey_len 位元組的子金鑰。subkey_id 是子金鑰的編號,可以是不大於 2^64 - 1 的任意值。

主金鑰的長度必須是 crypto_kdf_KEYBYTES。子金鑰的長度 subkey_len 必須介於 crypto_kdf_BYTES_MIN(含)和 crypto_kdf_BYTES_MAX(含)之間。

上下文 ctx 是一個 8 字元的字串,應能描述子金鑰的用途。不需要保密,並且強度可以很低。比如 "UserName""__auth__""pictures""userdata" 等。但其長度必須是 crypto_kdf_CONTEXTBYTES 位元組。

使用相同的金鑰,但使用不同的 ctx,就會得到不同的輸出。正如其名,ctx 可以和程式的上下文對應。當然,就算一個程式從頭到尾只使用一個 ctx,那也有防止金鑰被不同程式重複使用的作用。

#include <sodium.h>

int main(void)
{
    if (sodium_init() == -1) {
        return 1;
    }

    char ctx[] = "Examples";

    uint8_t master_key[crypto_kdf_KEYBYTES];
    uint8_t subkey1[16];
    uint8_t subkey2[16];
    uint8_t subkey3[32];

    // 建立主金鑰
    crypto_kdf_keygen(master_key);

    // 派生子金鑰
    crypto_kdf_derive_from_key(subkey1, sizeof subkey1, 1, ctx, master_key);
    crypto_kdf_derive_from_key(subkey2, sizeof subkey2, 2, ctx, master_key);
    crypto_kdf_derive_from_key(subkey3, sizeof subkey3, 3, ctx, master_key);

    // 獲取子金鑰的十六進位制表示
    char buf[1024];
    sodium_bin2hex(buf, sizeof buf, subkey1, sizeof subkey1);
    printf("subkey1: %s\n", buf);

    sodium_bin2hex(buf, sizeof buf, subkey2, sizeof subkey2);
    printf("subkey2: %s\n", buf);

    sodium_bin2hex(buf, sizeof buf, subkey3, sizeof subkey3);
    printf("subkey3: %s\n", buf);

    return 0;
}
subkey1: 0440b65332dc5f6b4a46d262996af08e
subkey2: 73e6d9bbfb25c25d3898ba435f16b710
subkey3: 9fdffff7fd9d4ba7a8b1172c79cdf86b7a823256b418e9a61cb8e21f1170ef1f

? 6. 輔助函式

儘可能使用這些函式,以抵禦「時序攻擊」。

6.1 測試位元組序列

sodium_memcmp()

函式 sodium_memcmp() 可完成兩個等長位元組序列的對比。

int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len);

如果位於 b1_len 個位元組和位於 b2_len 個位元組相同,函式返回 0,否則返回 -1

char b1_[6] = "hello";
char b2_[6] = "hello";
char b3_[6] = "Hello";

if (sodium_memcmp(b1_, b2_, 5) == -1) {
    puts("No match");
} else {
    puts("Match");
}

if (sodium_memcmp(b1_, b3_, 5) == -1) {
    puts("No match");
} else {
    puts("Match");
}
Match
No match

sodium_is_zero()

函式 sodium_is_zero() 可判斷給定的位元組序列是否全為 0

int sodium_is_zero(const unsigned char *n, const size_t nlen);

若位於 nnlen 個位元組是否全為 0,則返回 1,否則返回 0

6.2 位元組序列的十六進位制表示

sodium_bin2hex()

函式 sodium_bin2hex() 可獲取位元組序列的十六進位制表示,並由此得到一個字串。

char *sodium_bin2hex(char * const hex, const size_t hex_maxlen,
                     const unsigned char * const bin, const size_t bin_len);

函式將字串寫到 hex,這個字串就是從 bin 開始的 bin_len 個位元組的十六進位制表示,包括 '\0',故 hex_maxlen 至少為 2*bin_len + 1。該函式始終返回 hex

char hex[9]; // 2*4 + 1 = 9
char bin[5] = "AAAA";

sodium_bin2hex(hex, 9, bin, 4);
puts(hex);
41414141

sodium_hex2bin()

函式 sodium_hex2bin() 作用相反,透過解析位元組序列的十六進位制表示,還原該位元組序列。

int sodium_hex2bin(unsigned char * const bin, const size_t bin_maxlen,
                   const char * const hex, const size_t hex_len,
                   const char * const ignore, size_t * const bin_len,
                   const char ** const hex_end);

函式將位元組序列寫到 binbin_maxlen 表示允許寫入的最大位元組數。而位於 hex 的字串應當是一個位元組序列的十六進位制表示,可以沒有 '\0' 結尾,需要解析的長度由 hex_len 指定。

ignore 是需要跳過的字元組成的字串。比如 ": " 表示跳過冒號和空格。此時 "69:FC""69 FC""69 : FC""69FC" 都視為合法的輸入,併產生相同的輸出。ignore 可以設為 NULL,表示不允許任何非法的字元出現。

函式返回 0 表示轉換成功,同時 bin_len 會被設為解析得到的位元組數;返回 -1 則表示失敗。失敗的情況有以下兩種:

  • 解析的結果超過 bin_maxlen 位元組;
  • 遇到非法字元時,如果前面的字元都能順利解析,函式仍然返回 0,否則返回 -1

無論如何 hex_end 總是會被設為下一個待解析的字元的地址。

char bin[5] = {0};
char hex[12] = "61*62636472";
size_t bin_len = 0;
const char * hex_end;

sodium_hex2bin(bin, 4, hex, 9, "*", &bin_len, &hex_end);
printf("%d: %s, %c\n", bin_len, bin, *hex_end);
4: abcd, 7

6.3 Base64 編碼/解碼

sodium_bin2base64()

函式 sodium_bin2base64() 可獲取位元組序列的 Base64 編碼。

char *sodium_bin2base64(char * const b64, const size_t b64_maxlen,
                        const unsigned char * const bin, const size_t bin_len,
                        const int variant);

Base64 編碼有多種變體,採用哪種變體由 variant 指定,有下列 4 種取值可選:

  • sodium_base64_VARIANT_ORIGINAL
  • sodium_base64_VARIANT_ORIGINAL_NO_PADDING
  • sodium_base64_VARIANT_URLSAFE
  • sodium_base64_VARIANT_URLSAFE_NO_PADDING

這些 Base64 編碼並不提供任何形式的加密;就像十六進位制編碼一樣,任何人都可以對它們進行解碼。

可以令 b64_maxlen 等於宏 sodium_base64_ENCODED_LEN(BIN_LEN, VARIANT),它表示使用 VARIANT 這種變體時,BIN_LEN 個位元組的 Base64 編碼(包括 '\0')的最小長度。

char bin[6] = "hello";
int b64_len = sodium_base64_ENCODED_LEN(5, sodium_base64_VARIANT_ORIGINAL);
char b64[b64_len];

sodium_bin2base64(b64, b64_len, bin, 5, sodium_base64_VARIANT_ORIGINAL);
printf("%d: %s\n", b64_len, b64);
9: aGVsbG8=

sodium_base642bin()

函式 sodium_base642bin() 可完成 Base64 解碼工作。

int sodium_base642bin(unsigned char * const bin, const size_t bin_maxlen,
                      const char * const b64, const size_t b64_len,
                      const char * const ignore, size_t * const bin_len,
                      const char ** const b64_end, const int variant);

返回 -1 表示錯誤,返回 0 表示解碼成功,同時 bin_len 會被設為解碼得到的位元組數,其他引數的含義參考前文。

size_t bin_len;
char bin[6];
char b64[9] = "aGVsbG8=";
sodium_base642bin(bin, sizeof bin,
                  b64, strlen(b64), "", &bin_len, NULL,
                  sodium_base64_VARIANT_ORIGINAL);
printf("%d: %s\n", bin_len, bin);
5: hello

6.4 大數的計算

sodium_increment()

函式 sodium_increment() 用來遞增一個任意長度的無符號數。

void sodium_increment(unsigned char *n, const size_t nlen);

位於 nnlen 位元組的數字將按小端位元組序處理。加密演算法中經常提到的不重數 nonce 就可用此函式進行遞增。

unsigned char nonce[8] = {0};

sodium_increment(nonce, sizeof(nonce));
printf("%d\n", *(int *)nonce);

sodium_increment(nonce, sizeof(nonce));
printf("%d\n", *(int *)nonce);

sodium_increment(nonce, sizeof(nonce));
printf("%d\n", *(int *)nonce);
1
2
3

sodium_add()

函式 sodium_add() 可完成大數的加法。

void sodium_add(unsigned char *a, const unsigned char *b, const size_t len);

位於 ab 的兩個 nlen 位元組的加數均按小端位元組序的無符號數處理。計算結果將覆蓋 a

unsigned char a[8] = {1};
unsigned char b[8] = {1};
printf("%d\n", *(int *)a);

sodium_add(a, b, sizeof a); // a = a + b
printf("%d\n", *(int *)a);
1
2

sodium_sub()

函式 sodium_sub() 可完成大數減法。

void sodium_sub(unsigned char *a, const unsigned char *b, const size_t len);

位於 ab 的兩個 nlen 位元組的加數均按小端位元組序的無符號數處理。計算結果將覆蓋 a

sodium_compare()

函式 sodium_compare() 可完成兩個大數的比較。兩個大數均按小端位元組序處理。

int sodium_compare(const void * const b1_, const void * const b2_, size_t len);

返回 0 表示相等,返回 -1 表示 b1_ 小於 b2_;返回 1 表示 b1_ 大於 b2_

? 參考文獻

  1. libsodium 密碼學庫 中文文件
  2. 現代密碼學實踐指南[2015年]
  3. 【翻譯】密碼學一小時必知
  4. 加密解密學習筆記
  5. 密碼學基礎系列
  6. 實用密碼學工具——KDF
  7. 如何儲存密碼(KDF)
  8. ldconfig命令
  9. pkg-config 詳解
  10. 什麼是 AES-NI(AES指令集)
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章