关于
HTTP/3使用QUIC传输协议,而她底层使用TLS1.3变种协议进行密钥交换,其中涉及到密码学知识,先慢慢积累一些,看后面整理下成个专栏。
对称加解密
之所以称为对称,是因为加密和解密操作使用同一个密钥,需要注意加密密钥和密码(密码)的区别,前者可以根据后者生成(derive),这个涉及到密码学另一个概念,KDF(Key Derivation Function),本文不会涉及这个概念。
对称加密(Symmetric encryption)使用加密密钥(encryption key)加密数据。对称加密有多种算法(symmetric encryption algorithm),在密码学里面,也叫做cipher,后者更加泛,可以指对称加密算法,也可以是非对称加密的或者TLS中的算法套件等。
需要被加密的数据称为纯文本(plaintext),即使她是二进制数据,音乐或者视频等。加密后的数据称为密文(ciphertext)。 为了加密纯文本,需要以下要素
- 选择加密算法(cipher)
- 生成加密密钥(encryption key)
- 生成初始化向量 IV(Initialization Vector)
- 选择算法操作模式(cipher operation mode)或者加密模式(Encryption Mode)
- 选择填充类型(padding type)
根据不同算法,有些元素不是必须的,比如,流式算法和一些块式算法操作模式不需要填充(padding)。 对称加解密算法分为两类:块式算法和流式算法。
块式算法
顾名思义,块式算法以数据块为操作单位。比如流行的AES(Advanced Encryption Standard)算法的块大小(block size)为128位,意思是加解密以128位为单位操作,如果纯文本长度大于块大小,数据会分成多个块进行加解密。如果纯文本的长度不是块大小的倍数,最后一个块一般会填充到块大小,这里就需要用到填列类型了,不同的填充类型,有不同的填充方式。
块式算法大致工作流程
正因为块式算法以块为单位加解密,所以不同的操作引入了操作模式。根据操作可以分为ECB(Electronic Code Block), CBC(Cipher Block Chain), CTR(Counter), GCM(Galois Counter Mode)。所以AES根据不同的模式有AES_128_GCM
, AEC_128_ECB
等算法,中间128代表密钥长度(key length),后面的GCM代表算法操作模式。
Mode | Formulas | Ciphertext |
---|---|---|
Electronic codebook(ECB) | Yi = F(PlainTexti, Key) | Yi |
Cipher block chaining(CBC) | Yi = PlainTexti XOR Ciphertexti−1 | F(Y, Key); Ciphertext0 = IV |
Counter(CTR) | Yi = F(IV + g (i), Key); IV = token() | Plaintext XOR Yi |
块式算法介绍
AES(Adanced Encryption Standard)也叫Rijndael加密算法。因为NIST组织选择Rijndael算法标准化后叫做AES。
AES被认为是目前最流行的对称加解密算法。如果你不确定使用什么对称加解密算法时,她是最好的选择,兼具速度和安全。目前X86系列和ARMv8都有相关的模块提供硬件加速。TLS1.3的安全套件中对称加解密使用这个算法,比如AES_128_GCM_SHA256
,AES_256_GCM_SHA384
,上面2个密码套件里面使用key length分别为128bits和256bits。
流式算法
流式算法使用字节为单位或者以二进制位为单位,所以流式算法一般不需要填充,也不需要操作模式,但是有些算法研究者尝试着加入操作模式。因此流式算法更加简单的实现和使用,特别对于流式数据,整个过程相比块式算法更加快。但是密码学专家认为现存的流式算法安全性不如块式算法。
流式算法大致工作流程
首先根据加密密钥(encryption key)和初始化向量(IV)或者随机数(nonce)生成无限长度的伪随机密码数字流(pseudorandom cipher digit stream)或者密钥流(keystream)。整个过程相当于伪随机数发生器,只不过这里的seed是加密密钥和初始化向量。 加密的时候,一般使用密钥流和纯文本做异或(XOR)操作;相反,解密的时候,使用密文和密钥流做异或(XOR)操作。
流式算法介绍
ChaCha20是一种流行的流式加密算法,支持128bits和256bits密钥长度。主要的一般使用256bits的密钥长度,比如OpenSSL。ChaCha20在没有硬件加速的场景下比AES要快,比如手机等。
唯一不足的地方是,她使用32bits的block counter,所以理论上,256G是她的加密安全范围,但是网络数据一般没有那么大量,所以网络中加解密是绰绰有余的。ChaCha20一般不单独使用,和POLY1305(MAC算法)一起使用。TLS1.3中ChaCha20_POLY1305_SHA256套件就是使用她们。
算法应用实践
OpenSSL ChaCha20
c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
void print_hex(char *label, unsigned char *data, int len)
{
fprintf(stdout, "%s(%d): ", label, len);
for (int i = 0; i < len; i++)
{
fprintf(stdout, "%02x ", data[i]);
}
fprintf(stdout, "\n");
}
unsigned char *hexstr2buf(const char *str)
{
size_t len = strlen(str);
if (len % 2 != 0)
return NULL;
size_t res_len = len / 2 + 1;
unsigned char *res = (unsigned char *)malloc(res_len);
for (int i = 0; i < len; i += 2)
{
sscanf(str + i, "%2hhx", res + i / 2);
}
*(res + res_len - 1) = '\0';
return res;
}
int main()
{
int ok = 0;
/* 测试数据来源: https://datatracker.ietf.org/doc/html/rfc8439#section-2.4.2 */
char *key_hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
char *iv_hex = "01000000000000000000004a00000000"; // 4bytes(32bits)counter + 12bytes(96bits)nonce
char *plaintext_hex = "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e";
unsigned char *key, *iv, *plaintext;
unsigned char out[4096];
int outlen = 0;
key = hexstr2buf(key_hex);
iv = hexstr2buf(iv_hex);
plaintext = hexstr2buf(plaintext_hex);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
ok = EVP_CipherInit_ex(ctx, EVP_chacha20(), NULL, key, iv, 1);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 16, NULL);
ok = EVP_CipherUpdate(ctx, out, &outlen, plaintext, strlen(plaintext));
print_hex("out", out, outlen);
outlen = 0;
EVP_EncryptFinal_ex(ctx, out, &outlen);
print_hex("out", out, outlen);
OPENSSL_free(NULL);
OPENSSL_free(key);
OPENSSL_free(iv);
OPENSSL_free(plaintext);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
Python
Python中使用加解密第三方库主要有pycrypto, pycryptodom, pycryptography。pycrypto已经不再维护,pycryptodom可以兼容前者的接口。pycryptograph也是很推荐的密码学库,aioquic中使用了此库。
python
# pyCrypto不在维护,pyCryptodom的API兼容前者。作者推荐pyCryptography和pyCryptodom
# pyCryptodom库的ChaCha20和ChaCha20-Poly1305是遵循RFC 8439(https://datatracker.ietf.org/doc/html/rfc8439)
# 但是不能指定counter
# pyCryptography 不遵循RFC 8439
# 运行
# python3 -m pip install pycryptodom
# python3 chacha20_poly1305.py
from binascii import hexlify
from Crypto.Cipher import ChaCha20, ChaCha20_Poly1305
# https://www.pycryptodome.org/src/cipher/chacha20
# PyCryptodome ChaCha20
# 测试数据来源: https://datatracker.ietf.org/doc/html/rfc8439#section-2.4.2
key = bytes.fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
nonce = bytes.fromhex("000000000000004a00000000")
cipher = ChaCha20.new(key=key, nonce=nonce)
plaintext = bytes.fromhex(
"4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e"
)
ciphertext = cipher.encrypt(plaintext)
print("ChaCha20 ciphertext: {hexlify(ciphertext)}")
# https://www.pycryptodome.org/src/cipher/chacha20_poly1305
# PyCryptodome ChaCha20-Poly1305
# 测试数据来源: https://datatracker.ietf.org/doc/html/rfc8439#section-2.8.2
cp_key = bytes.fromhex(
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
)
cp_iv = bytes.fromhex("070000004041424344454647")
aad = bytes.fromhex("50515253c0c1c2c3c4c5c6c7")
cp = ChaCha20_Poly1305.new(key=cp_key, nonce=cp_iv)
cp.update(aad)
cp_ciphertext, cp_tag = cp.encrypt_and_digest(plaintext)
print(f"ChaCha20-Poly1305 ciphertext: {hexlify(cp_ciphertext)}")
print(f"ChaCha20-Poly1305 tag: {hexlify(cp_tag)}")