各种加密模式在TLS协议中的运用 3 (AEAD:GCM模式)

11 篇文章 8 订阅
11 篇文章 4 订阅

GCM,不知道该如何说起,因为涉及到群论、有限域等,先写个大概,有机会再补。

就是我们常见的一个集合,集合中的元素可以进行加减乘除,除了0以外的元素都有逆元。
例如实数域,R = (…,-1, 0, 1, 2, 3, 4 …)就是一个域,他是无限大小的。
对于任意R中的X1 X2,加减乘的结果还在R中。0是加法单位元(任意元素与其相加都为原先值),1是乘法单位元(任意元素与其相乘都为原先值)。
不严谨的说,域就是一个集合。
好,过。

有限域

如何使得一个集合是有限的呢,举个例子
集合(0,1,2,3,4,5,6) 7个元素,我们定义运算为 (X1运算X2 mod 7) ,即任意运算结果模上7,这样就肯定还在集合中了。
有限域又称之为伽罗华域。
好,过。

定理1

任何有限域的元素个数,必定有p^m 个,其中p是素数,m是大于等于1个正整数。
过。

素数域

素数域,规定,m强制为1,p为任意素数。例如上题中,7个元素的有限域,p是7,m是1。我们称之为GF§,我们也称之为素数域(prime field)。
GF(11) = {0, 1, 2, 3, 4 ,5 ,6 ,7 ,8 ,9 ,10}
过。

拓展域

定理1 中的m不是1,怎么办呢(也就不是素数域了)?m不是1,p^m就不是素数了,显然不能称之为素数域了。
我们讲这种特殊的情况,就再次规定,是p强制取最小素数2的情况。m为任意合规的数。

我们这么描述这个特殊的GF(2^m)。我们称之为拓展域。

举个例子,GF(2^3),显然,他有8个元素,但是元素怎么表示呢?这么表示:(0, 1, 2, 3 ,4 ,5 ,6 ,7),对是对,但是这么表示计算机起算起来不是很快,之所以要规定p是2,就是因为计算机计算快速。

所以我们采用多项式表示:
a2 * X^2 + a1 * X + a0
也即,GF(2^3)={0, 1, X, X+1, X^2, X^2 + 1, X^2 + X, X^2 + X + 1}
换句话说,因子a2,a1,a0的值是属于GF(2)={0, 1}的。现在这个GF(2^3)元素看似是有限的,但是前提它的运算结果也需要有限,回忆素数域,我们mod一个素数,同样,这里也需要mod一个值。

加减运算:
运算规则就是多项式加法的规则。
X^m的 与X^m的运算 然后 mod 2
例子(X^2 + 1) + (X^2 + X) ,显然结果是2X^2 + X + 1,然后mod2之后,只有X+1这一项。减法运算被定义为何加法运算一样。

乘除运算:
运算规则就是多项式乘法的规则。
这里不举例了, 但是乘法运算,导致了指数会变大,超过GF(2^3),所以我们需要mod一个值,这个值我们称为不可约多项式GF(2^3)的不可约多项式是 X^3 + X + 1

AES有关的拓展域

AES 的拓展域是GF(2^128),其不可约多项式是:X^8 + X^4 + X^3 + X + 1
GCM的输入三要素:plaintext(必选)、tag(可选)、iv(必选)
GCM的输出三要素:ciphertext(必选)、tag(必选)

GCM流程图

这里写图片描述
其中GHASH和GCTR流程图如下:
这里写图片描述

其实他比CCM简单多了。图中的H就是有限域上的乘法。

先说一下图中的各个参数。
encode:
GHASH

情况一:IV如果是12字节,即在TLS协议中的大小,所谓的encode其实就是把12字节拓展成16字节。

        memcpy( ctr, iv, iv_len );
        ctr[15] = 1;

情况二:如果IV不是12字节,情况加复杂了,需要做GHASH。
GHASH的入参是
iv + {0,0,0,0,0,0,0,0,0,0,0,0} + 4字节的iv_length。incr不管他。在TLS中不存在这种情况,不详细说了。

其次,0v 和0u都表示补齐的0字节。
用来算Tag的最后一个块是8字节的len(A)与8字节的len©组成的16字节块。
至此,GCM计算的流程到此结束。

H:GHASH那个图中的 “·H”,其中·是实在GF(2^128)上的乘法,H是hashkey,就是key derive的那个enc/dec key。

换句话说,GCM模式时,key derive时,mac key不会生成,只会生成对称秘钥key和iv。

GCTR 和 CTR 的区别

1、如图上所示,GCTR是先对counter++,然后使用counter。而CTR是先使用counter,使用完再++,顺序有区别。
2、CTR模式,counter 全部的字节都可以进位。而GCTR模式,counter只有后4字节,进行++运算,前12字节不动,即使后4字节自加时溢出了。

大家在用CTR模拟GCTR时需要注意这几点。

换个思路理解GCM模式

维基百科的GCM模式如下:
这里写图片描述

这比上面两张图更加简洁。
这张图可以大致划分为2部分,第一部分是plaintext通过类CTR模式生成ciphertext。
第二部分是add和ciphertext以CBC的模式计算tag,其中核心操作是有限域上的乘法。
其中,ADD的作用和AES_CBC模式中计算HMAC的ADD是一样的,功能也是一样的。

并行计算

回忆GHASH流程图:
Ghash
乍一看,这个CBC模式加密两样,确实,如果只关心亦或操作,是一样,链式。但是GHASH中的核心流程不像AES-CBC那样是加密函数,而是有限域上的乘法,拥有结合律、交换律,分配率。

所以,GHASH可以用如下等式描述:
这里写图片描述
又由于H是固定的(对于每个协商好的session,hashkey肯定不会变),所以可以提前计算好H^x,给每个等式用,这就是所谓的并行。(相对于AES-CBC而言)

普遍GCM的使用

下面,add和iv和tag对应上图流程中的各个参数。

    EVP_CIPHER_CTX *ctx;
    unsigned char outbuf[1024];

    ctx = EVP_CIPHER_CTX_new();
    /* 设置GCM算法 */
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
    /* 设置IV大小 */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
    /* 设置key和iv */
    EVP_EncryptInit_ex(ctx, NULL, NULL, gcm_key, gcm_iv);
    /* 添加 AAD;第2个参数为NULL时,AES加密函数默认第4个参数是ADD而不是待加密数据 */
    EVP_EncryptUpdate(ctx, NULL, &outlen, gcm_aad, sizeof(gcm_aad));
    /* 对明文1进行加密 */
    EVP_EncryptUpdate(ctx, outbuf, &outlen, gcm_pt, sizeof(gcm_pt));
    /* 对明文2进行加密 ,注意,其结果不要覆盖明文1的加密结果,所以注意第二个参数的内存地址*/
    EVP_EncryptUpdate(ctx, outbuf+outlen, &outlen, gcm_pt2, sizeof(gcm_pt2));
    
    /*入参其实没啥意义。。。反正不会输出*/
    EVP_EncryptFinal_ex(ctx, outbuf, &outlen);
    
    /*获取tag*/
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, total_len, outbuf);

GCM在TLS中的使用:

https://tools.ietf.org/html/rfc5288
大致就是:
1、IV是12字节,其中前4字节是kdf得到的。后8字节是自定义的。
2、ADD和CBC模式的类似,13字节。(8字节序号+1字节类型+2字节TLS版本号+2字节明文长度)

实例:解密TLS报文

加密的key:74 A2 0A 31 8C 80 79 5C 00 28 C5 01 B3 8C 89 28(内部hack得到)
fixediv:B4 79 0C 00(内部hack得到)
密文:39f3760a8adfef4a7c48adbfc38eae9c3ef20c6cfb86e51af67f5964a63c9d34f5a4f66ecfd4d1e7(抓包得到)

#include <stdio.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

unsigned char gcm_add[]= {
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x17, 0x03, 0x03, 0x0, 0x10
};


unsigned char gcm_iv[]= {
0xB4, 0x79, 0x0C, 0x00,
0x39, 0xf3, 0x76, 
0x0a, 0x8a, 0xdf, 0xef, 0x4a
};

unsigned char gcm_key[]={
0x74 ,0xA2 ,0x0A ,0x31 ,0x8C ,0x80 ,0x79 ,0x5C ,0x00 ,0x28 ,0xC5 ,0x01 ,0xB3 ,0x8C ,0x89 ,0x28
};

unsigned char gcm_ct[]={
0x7c, 0x48, 0xad, 
0xbf, 0xc3, 0x8e, 0xae, 0x9c, 0x3e, 0xf2, 0x0c, 
0x6c, 0xfb, 0x86, 0xe5, 0x1a, 0xf6, 0x7f, 0x59, 
0x64, 0xa6, 0x3c, 0x9d, 0x34, 0xf5, 0xa4, 0xf6, 
0x6e, 0xcf, 0xd4, 0xd1, 0xe7};

void main()
{
    EVP_CIPHER_CTX *ctx;
    int outlen, tmplen, rv;
    unsigned char outbuf[1024];
    printf("AES GCM Derypt:\n");
    printf("Ciphertext:\n");
    BIO_dump_fp(stdout, gcm_ct, sizeof(gcm_ct));
    ctx = EVP_CIPHER_CTX_new();
    /* Select cipher */
    EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL);
    /* Set IV length, omit for 96 bits */
    printf("iv size: %d\n",sizeof(gcm_iv));
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
    /* Specify key and IV */
    EVP_DecryptInit_ex(ctx, NULL, NULL, gcm_key, gcm_iv);
    /* Zero or more calls to specify any AAD */
    printf("add size: %d\n",sizeof(gcm_add));
    EVP_DecryptUpdate(ctx, NULL, &outlen, gcm_add, sizeof(gcm_add));
    /* Decrypt plaintext */
    printf("ciphertext size: %d\n",sizeof(gcm_ct) - 16); 
    EVP_DecryptUpdate(ctx, outbuf, &outlen, gcm_ct, sizeof(gcm_ct) - 16);
    /* Output decrypted block */
    printf("Plaintext:\n");
    BIO_dump_fp(stdout, outbuf, outlen);
    /* Set expected tag value. */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
                        (void *)(gcm_ct + sizeof(gcm_ct) - 16));
    /* Finalise: note get no output for GCM */
    rv = EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
    printf("Tag Verify %s\n", rv > 0 ? "Successful!" : "Failed!");
    EVP_CIPHER_CTX_free(ctx);
}

结果

AES GCM Derypt:
Ciphertext:
0000 - 7c 48 ad bf c3 8e ae 9c-3e f2 0c 6c fb 86 e5 1a   |H......>..l....
0010 - f6 7f 59 64 a6 3c 9d 34-f5 a4 f6 6e cf d4 d1 e7   ..Yd.<.4...n....
iv size: 12
add size: 13
ciphertext size: 16
Plaintext:
0000 - 47 45 54 20 2f 20 48 54-54 50 2f 31 2e 30 0a 0a   GET / HTTP/1.0..
Tag Verify Successful!

使用AES_CTR来解密AES_GCM密文

上面说过 AES_GCM的核心是GCTR模式加密,所以我们可以使用CTR模式来解密AES_GCM的密文,当然校验tag就得另寻它法了,这里只讲解密。

下面这段代码与上面那段代码的核心区别就是,iv设置成了16字节,因为AES_CTR要求16字节。其次关于拓展iv的值,我们拓展成了00 00 00 02,为什么不是00 00 00 01和GCM拓展一样?因为上面说了GCTR,是先将iv自加再使用的,即GCM的第一个加密块使用的iv是....00 00 00 02,所以这里,将CTR加密的iv追加了00 00 00 02

#include <stdio.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

unsigned char gcm_add[]= {
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x17, 0x03, 0x03, 0x0, 0x10
};


unsigned char gcm_iv[]= {
0xB4, 0x79, 0x0C, 0x00,
0x39, 0xf3, 0x76, 
0x0a, 0x8a, 0xdf, 0xef, 0x4a,
0x00, 0x00, 0x00, 0x02,
};

unsigned char gcm_key[]={
0x74 ,0xA2 ,0x0A ,0x31 ,0x8C ,0x80 ,0x79 ,0x5C ,0x00 ,0x28 ,0xC5 ,0x01 ,0xB3 ,0x8C ,0x89 ,0x28
};

unsigned char gcm_ct[]={
0x7c, 0x48, 0xad, 
0xbf, 0xc3, 0x8e, 0xae, 0x9c, 0x3e, 0xf2, 0x0c, 
0x6c, 0xfb, 0x86, 0xe5, 0x1a, 0xf6, 0x7f, 0x59, 
0x64, 0xa6, 0x3c, 0x9d, 0x34, 0xf5, 0xa4, 0xf6, 
0x6e, 0xcf, 0xd4, 0xd1, 0xe7};

void main()
{
    EVP_CIPHER_CTX *ctx;
    int outlen, tmplen, rv;
    unsigned char outbuf[1024];
    printf("AES GCM Derypt:\n");
    printf("Ciphertext:\n");
    BIO_dump_fp(stdout, gcm_ct, sizeof(gcm_ct));
    ctx = EVP_CIPHER_CTX_new();
    /* Select cipher */
    EVP_DecryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, NULL, NULL);
    /* Set IV length, omit for 96 bits */
    printf("iv size: %d\n",sizeof(gcm_iv));
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
    /* Specify key and IV */
    //注意,改函数会对所有gcm_iv ++,所以当解密数据过长时,会有问题,本例子没问题
    EVP_DecryptInit_ex(ctx, NULL, NULL, gcm_key, gcm_iv);
    /* Zero or more calls to specify any AAD */
    //printf("add size: %d\n",sizeof(gcm_add));
    //EVP_DecryptUpdate(ctx, NULL, &outlen, gcm_add, sizeof(gcm_add));
    /* Decrypt plaintext */
    printf("ciphertext size: %d\n",sizeof(gcm_ct) - 16); 
    EVP_DecryptUpdate(ctx, outbuf, &outlen, gcm_ct, sizeof(gcm_ct) - 16);
    /* Output decrypted block */
    printf("Plaintext:\n");
    BIO_dump_fp(stdout, outbuf, outlen);
    /* Set expected tag value. */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
                        (void *)(gcm_ct + sizeof(gcm_ct) - 16));
    /* Finalise: note get no output for GCM */
    rv = EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
    //printf("Tag Verify %s\n", rv > 0 ? "Successful!" : "Failed!");
    EVP_CIPHER_CTX_free(ctx);
}
  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值