SM4-GCM Test Vectors

   Initialization Vector:   00001234567800000000ABCD
   Key:                     0123456789ABCDEFFEDCBA9876543210
   Plaintext:               AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB
                            CCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDD
                            EEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFF
                            EEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA
   Associated Data:         FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2
   CipherText:              17F399F08C67D5EE19D0DC9969C4BB7D
                            5FD46FD3756489069157B282BB200735
                            D82710CA5C22F0CCFA7CBF93D496AC15
                            A56834CBCF98C397B4024A2691233B8D
   Authentication Tag:      83DE3541E4C2B58177E065A9BF7B62EC

SM4-CCM Test Vectors

   Initialization Vector:   00001234567800000000ABCD
   Key:                     0123456789ABCDEFFEDCBA9876543210
   Plaintext:               AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB
                            CCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDD
                            EEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFF
                            EEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA
   Associated Data:         FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2
   CipherText:              48AF93501FA62ADBCD414CCE6034D895
                            DDA1BF8F132F042098661572E7483094
                            FD12E518CE062C98ACEE28D95DF4416B
                            ED31A2F04476C18BB40C84A74B97DC5B
   Authentication Tag:      16842D4FA186F56AB33256971FA110F4

来源:https://github.com/majek/openssl/blob/master/demos/evp/aesgcm.c
包含NIST 测试向量

/* Simple AES GCM test program, uses the same NIST data used for the FIPS
 * self test but uses the application level EVP APIs.
 */
#include <stdio.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

/* AES-GCM test data from NIST public test vectors */

static const unsigned char gcm_key[] = {
    0xee,0xbc,0x1f,0x57,0x48,0x7f,0x51,0x92,0x1c,0x04,0x65,0x66,
    0x5f,0x8a,0xe6,0xd1,0x65,0x8b,0xb2,0x6d,0xe6,0xf8,0xa0,0x69,
    0xa3,0x52,0x02,0x93,0xa5,0x72,0x07,0x8f
};

static const unsigned char gcm_iv[] = {
    0x99,0xaa,0x3e,0x68,0xed,0x81,0x73,0xa0,0xee,0xd0,0x66,0x84
};

static const unsigned char gcm_pt[] = {
    0xf5,0x6e,0x87,0x05,0x5b,0xc3,0x2d,0x0e,0xeb,0x31,0xb2,0xea,
    0xcc,0x2b,0xf2,0xa5
};

static const unsigned char gcm_aad[] = {
    0x4d,0x23,0xc3,0xce,0xc3,0x34,0xb4,0x9b,0xdb,0x37,0x0c,0x43,
    0x7f,0xec,0x78,0xde
};

static const unsigned char gcm_ct[] = {
    0xf7,0x26,0x44,0x13,0xa8,0x4c,0x0e,0x7c,0xd5,0x36,0x86,0x7e,
    0xb9,0xf2,0x17,0x36
};

static const unsigned char gcm_tag[] = {
    0x67,0xba,0x05,0x10,0x26,0x2a,0xe4,0x87,0xd7,0x37,0xee,0x62,
    0x98,0xf7,0x7e,0x0c
};

void aes_gcm_encrypt(void)
    {
    EVP_CIPHER_CTX *ctx;
    int outlen, tmplen;
    unsigned char outbuf[1024];
    printf("AES GCM Encrypt:\n");
    printf("Plaintext:\n");
    BIO_dump_fp(stdout, gcm_pt, sizeof(gcm_pt));
    ctx = EVP_CIPHER_CTX_new();
    /* Set cipher type and mode */
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
    /* Set IV length if default 96 bits is not appropriate */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, sizeof(gcm_iv), NULL);
    /* Initialise key and IV */
    EVP_EncryptInit_ex(ctx, NULL, NULL, gcm_key, gcm_iv);
    /* Zero or more calls to specify any AAD */
    EVP_EncryptUpdate(ctx, NULL, &outlen, gcm_aad, sizeof(gcm_aad));
    /* Encrypt plaintext */
    EVP_EncryptUpdate(ctx, outbuf, &outlen, gcm_pt, sizeof(gcm_pt));
    /* Output encrypted block */
    printf("Ciphertext:\n");
    BIO_dump_fp(stdout, outbuf, outlen);
    /* Finalise: note get no output for GCM */
    EVP_EncryptFinal_ex(ctx, outbuf, &outlen);
    /* Get tag */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, outbuf);
    /* Output tag */
    printf("Tag:\n");
    BIO_dump_fp(stdout, outbuf, 16);
    EVP_CIPHER_CTX_free(ctx);
    }

void aes_gcm_decrypt(void)
    {
    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_256_gcm(), NULL, NULL, NULL);
    /* Set IV length, omit for 96 bits */
    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);
#if 0
    /* Set expected tag value. A restriction in OpenSSL 1.0.1c and earlier
         * required the tag before any AAD or ciphertext */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof(gcm_tag), gcm_tag);
#endif
    /* Zero or more calls to specify any AAD */
    EVP_DecryptUpdate(ctx, NULL, &outlen, gcm_aad, sizeof(gcm_aad));
    /* Decrypt plaintext */
    EVP_DecryptUpdate(ctx, outbuf, &outlen, gcm_ct, sizeof(gcm_ct));
    /* Output decrypted block */
    printf("Plaintext:\n");
    BIO_dump_fp(stdout, outbuf, outlen);
    /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof(gcm_tag), gcm_tag);
    /* Finalise: note get no output for GCM */
    rv = EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
    /* Print out return value. If this is not successful authentication
     * failed and plaintext is not trustworthy.
     */
    printf("Tag Verify %s\n", rv > 0 ? "Successful!" : "Failed!");
    EVP_CIPHER_CTX_free(ctx);
    }

int main(int argc, char **argv)
    {
    aes_gcm_encrypt();
    aes_gcm_decrypt();
    }

在对数据进行加解密时,通常将数据按照固定的大小(block size)分成多个组,那么随之就产生了一个问题,如果分到最后一组,不够一个 block size 了,要怎么办?此时就需要进行补齐操作。
我们常用的填充的方式就包括ZeroPadding、PKCS5Padding与PKCS7Padding。
NoPadding
不填充,如果加密內容不是block size整数倍加密则报错。
ZeroBytePadding
所有需要填充的地方都以0填充。
示例如下:Block 大小为 8 Byte,需要填充 4 Byte(以十六进位表示)
DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |

PKCS#7 Padding
每个填充字节的值是用于填充的字节数,若需要填充 N 个字节,每个填充字节值都是 N 。
01
02 02
03 03 03
04 04 04 04
05 05 05 05 05
etc.
示例如下:Block 大小为 8 Byte,需要填充 4 Byte(以十六进位表示)

| DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |

PKCS#5 Padding
​ PKCS#5 和PKCS#7计算方式都相同,區別是PKCS5 只能用來填充 8 Byte (64bit)的Block。

示例如下:Block 大小主 8 Byte,需要填充 4 Byte(以十六进位表示)

| DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |

PKCS7Padding补齐规则:The value of each added byte is the number of bytes that are added, i.e. N bytes, each of value N are added.

举例:

36 位的 UUID,如果按照 block size=16 字节(即 128 比特),那么就需要补齐到 48 位,差 12 个字节。那么最后填充的 12 个字节的内容,都是字节表示的 0x0c(即 12)。

PKCS7Padding,假设每个区块大小为blockSize,分为二种填充情况。

1、已对齐,填充一个长度为blockSize且每个字节均为blockSize的数据。

2、未对齐,需要补充的字节个数为n,则填充一个长度为n且每个字节均为n的数据。

PKCS5Padding是PKCS7Padding的子集,只是块大小固定为8字节。在aes/sm4等加密算法中,密码的最小长度都是16字节(128位),所以,事实上都没PKCS5Padding什么事了,只有在类似RC2 / RC5和 DES/3DES 算法的情況,这些算法 BlockSize=64bits=8bytes算法才有有到PKCS5Padding。

PKCS#5 PKCS#7 差异
最大的差异是要求的BlockSize 不同

PKCS#5只针对8 Byte(BlockSize=8)填充,填充內容为 0x01- 0x08;
PKCS#7是对任意BlockSize填充,其BlockSize範圍是 1-255 Byte。

蝴蝶密钥扩展(ButterflyExpansion)算法,用于为车辆持续生成PC证书。中国的CCSA标准《基于LTE的车联网无线通信技术 安全证书管理系统技术要求》使用了该算法。
bufferflyexpansio.png
在假名证书申请流程和下载流程的基础上,密钥衍生的补充流程如下,本标准对密钥衍生流程中所使用的算法提出了一种建议:

  • 1) V2X设备在生成PC申请请求时,使用非对称密钥算法生成两对种子公私密钥对:签名密钥对(a, A),和加密密钥对(p, P);并使用对称密钥算法生成两个对称密钥:签名对称密钥kS和加密对称密钥kE;
  • 2) V2X设备发送给PRA的PC申请请求中,包括了A、P、kS、kE;
  • 3) PRA对PC申请请求、EC证书或令牌等信息校验通过后,使用A、P、kS、kE进行第一轮扩展运算,公钥因子A与kS进行扩展计算,得到签名扩展公钥B,P与kE进行扩展计算,得到加密扩展公钥Q;如此进行若干轮扩展运算,得到(Bi,j,Qi,j);
  • 4) PRA向PCA发送PC签发请求时,将(Bi,j, Qi,j)逐一发送给PCA,用于签发批量的PC;
  • 5) PCA签发PC证书前,随机生成1对公私钥对(c, C),其中公钥分量C与若干个Bi,j逐一进行运算,得到完整公钥Si,j;
  • 6) PCA基于Si,j构造的ToBeSignedCertificate进行签发得到假名证书PCi,j,再用对应的Qi,j对PCi,j及c进行加密,对该密文使用PCA的私钥进行签名;
  • 7) PCA返回批量PC证书至PRA时,将若干个密文发送至PRA,PRA检查并筛选出该V2X设备对应的若干个密文消息,将其打包为PC证书下载应答;
  • 8) V2X设备收到PRA返回的PC证书下载应答后,使用步骤1)中生成的a和p,分别与kS和kE进行若干轮计算,得到bi,j和qi,j;
  • 9) 验证步骤5)中PCA签名的有效性,验证通过后,再使用qi,j解密对应的密文,得到PCi,j及c,最终使用bi,j与c计算得到PCi,j所对应的完整私钥si,j。
  • 注1: 下标(i,j)表示单个的假名证书所对应的i和j的组合。其中i为该假名证书所对应的周期,j为该假名证书在当前周期中对应的序号。
  • 注2: 以上所述“签名”的种子密钥对(公钥因子、私钥因子)、对称密钥、扩展密钥,用于衍生V2X设备假名证书PC内的完整公钥和对应的完整私钥。
  • 注3: 以上所述“加密”的种子密钥对(公钥因子、私钥因子)、对称密钥、扩展密钥,用于衍生一对公私钥,对PCA返回至V2X设备的PC证书等消息进行加密、解密。
    密钥衍生函数

符号表达说明
有一个公认的“基点”,表示为G;
椭圆曲线的阶数用l表示;
对于一个比特串b和数字n,bn表示通过将比特串b重复n次而形成的比特串;
对于比特串x,xINT表示x转换为整数,例如:如果x = 0101,则xINT = 5;
对于比特串x和数字n,x + n是xINT + n产生的比特串的简写,例如:如果x = 0100,则x + 1 = 0101,x + 2 = 0110,x + 3 = 0111,依此类推;
对于比特串x和y,x ⊕ y表示它们的按位异或,而x || y表示串联的比特串,例如:如果x = 0110且y = 1010,则x ⊕ y = 1100且x || y = 01101010;
对于128比特字符串k和m,Symm (k, m)表示使用对称加密算法得到的128比特密文,m为被加密的明文数据,k为对称密钥,使用128比特的分组大小;
对于数字m和n,m mod n表示对具有模数n的m进行模运算的结果,例如:如果m = 9且n = 2,则m mod n = 1。
密钥衍生函数
2个密钥衍生函数:签名函数fS和加密函数fE。2个函数的参数如下:

  • 对称加密算法Symm,具有128比特的输入和输出以及128比特的密钥,采用SM4分组密码算法,ECB/NoPadding工作模式。
  • 一个128比特的密钥,分别表示为fS函数的kS,和fE函数的kE。
  • l是一个256比特的整数,表示进行密钥衍生的椭圆曲线的基点的阶。
    两个函数的输入为(iINT, jINT)两个整数,其范围是(0 , 232-1)。

两个函数的输出为一个256比特的整数o,范围为(0 , l)。
两个函数过程如下:

  • a)将输入iINT和jINT转换为32比特的比特串i和j。
  • b)拼接下列128位的比特串xS(用于fS)或xE(用于fE)。
  • 1)xS = (032 || i || j || 032)
  • 2)xE = (132 || i || j || 032)
  • c)创建如下的临时输出yS(用于fS)或yE(用于fE)。输出为3×128 = 384比特的字符串。
  • 1)yS = (Symm(kS, xS+1)⊕(xS+1)) || (Symm(kS, xS+2)⊕(xS+2)) ||(Symm(kS, xS+3)⊕(xS+3))
  • 2)yE = (Symm(kE, xE+1)⊕(xE+1)) || (Symm(kE, xE+2)⊕(xE+2)) ||(Symm(kE, xE+3)⊕(xE+3))
    这里,“x + i”表达了“将八位位组字符串x转换为整数,该整数加i,然后将加了i之后的整数转换回八位位组字符串”。例如,如果xS = (032 || i || j || 032) = (0000 0000 0000 0000 0000 0000 0000 0000b || i || j || 0000 0000 0000 0000 0000 0000 0000 0000) ,则xS + 2 = (0000 0000 0000 0000 0000 0000 0000 0000 b || i || j || 0000 0000 0000 0000 0000 0000 0000 0010)
  • d)fS和fE的最终输出如下:
  • 1)fS(kS, iINT, jINT) = ySINT mod l
  • 2)fE(kE, iINT, jINT) = yEINT mod l

相关:
https://blog.csdn.net/zhushuanghe/article/details/121169472

在终端下输入如下命令

ssh -N -v username@hostip -D 127.0.0.1:7070

把其中的 username , hostip 替换成你自已的内容。

第一次运行此命令需要输入 yes 来接受证书,最后输入 SSH 密码。如果你不想每次都输入密码的话,可以采用证书认证方式。