铜锁探“密”训练营实验手册
铜锁探“密”活动由开放原子开源基金会和铜锁社区共同举办,包含5次课程,以“抽丝剥茧,循序渐进,一起揭开商用密码的面纱”为主题,旨在让参与者更加深入地了解商用密码的原理和应用方法。 录播链接如下:
- 商用密码介绍和铜锁密码库入门,https://live.csdn.net/room/csdnnews/NJPxluNk
- 常用国密算法编程入门与实战,https://live.csdn.net/room/csdnnews/hZel8pLF
- 实战国密证书和国密传输协议,https://live.csdn.net/room/csdnnews/8fs5oVfT
- 实战铜锁国密应用和结营作业说明,https://live.csdn.net/room/csdnnews/BzeD1mGt
- 训练营结营&作品点评,https://live.csdn.net/room/csdnnews/fsZDshwb
实验环境说明
实验手册中大部分实验以docker环境为主,基于Ubuntu 20.04容器镜像。或者直接运行于Ubuntu 20.04操作系统或虚拟机也可以。比如电脑使用的Windows系统,可以通过安装Docker环境、Linux虚拟机、或者Linux子系统进行实验。其他Linux系统或者macOS系统可以参考本实验内容。
安装docker:
可以参考docker官方安装教程,详见https://docs.docker.com/engine/install/。
创建docker容器:
docker run -d -it --name tongsuolab ubuntu:20.04 bash
执行所有实验,都需要先进入docker容器。进入docker容器:
docker exec -it tongsuolab bash
其他软件,直接在操作系统上安装即可。
- 支持国密协议的浏览器,比如360企业安全浏览器。
- Wireshark,用于抓包分析国密协议。
构建铜锁密码库
基于铜锁密码库源代码进行构建和安装。代码下载地址:
AtomGit地址(推荐):https://atomgit.com/tongsuo/Tongsuo
GitHub地址:https://github.com/Tongsuo-Project/Tongsuo
下载代码需要使用git工具;构建铜锁密码库需要使用perl、gcc、make等基础开发工具。
安装依赖:
# 需要先更新软件包索引
apt update
apt install git gcc make -y
进入Shell终端执行以下命令:
git clone https://github.com/Tongsuo-Project/Tongsuo
cd Tongsuo
./config --prefix=/opt/tongsuo enable-ntls enable-ssl-trace -Wl,-rpath,/opt/tongsuo/lib64 --debug
make -j
make install
查看安装情况:
ls -l /opt/tongsuo
产看铜锁版本,执行如下命令:
/opt/tongsuo/bin/tongsuo version
SM2&SM3&SM4算法实战
实战SM4加解密算法
echo "hello tongsuo" > msg.bin
# SM4-CBC加密
/opt/tongsuo/bin/tongsuo enc -K "3f342e9d67d6ce7be701756af7bac8f2" -e -sm4-cbc -in msg.bin -iv "1fb2d42fb36e2e88a220b04f2e49aa13" -nosalt -out cipher.bin
# SM4-CBC解密
/opt/tongsuo/bin/tongsuo enc -K "3f342e9d67d6ce7be701756af7bac8f2" -d -sm4-cbc -in cipher.bin -iv "1fb2d42fb36e2e88a220b04f2e49aa13" -nosalt -out msg2.bin
# 比较解密的明文和原来的消息是否一样
diff msg.bin msg2.bin
实战SM3杂凑算法
echo -n "hello tongsuo" | /opt/tongsuo/bin/tongsuo dgst -sm3
结果如下:
实战SM2签名和验签
# 生成一个随机内容文件
dd if=/dev/urandom of=msg.bin bs=1024 count=1
# SM2私钥签名,签名算法为SM2withSM3,Tongsuo/test/certs/sm2.key来自Tongsuo源代码仓库
/opt/tongsuo/bin/tongsuo dgst -sm3 -sign Tongsuo/test/certs/sm2.key -out sigfile msg.bin
# SM2公钥验签,Tongsuo/test/certs/sm2pub.key来自Tongsuo源代码仓库
/opt/tongsuo/bin/tongsuo dgst -sm3 -verify Tongsuo/test/certs/sm2pub.key -signature sigfile msg.bin
签名正确时,验证成功可以看到:
SM2&SM3&SM4算法编程入门
SM4加解密算法编程入门
SM4加密
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm4_enc(const unsigned char *key, const unsigned char *iv,
unsigned char *in, int inlen, unsigned char *out,
int *outlen)
{
EVP_CIPHER_CTX *ctx = NULL;
int outl, tmplen;
if ((ctx = EVP_CIPHER_CTX_new()) == NULL
|| !EVP_EncryptInit_ex(ctx, EVP_sm4_cbc(), NULL, key, iv)
|| !EVP_EncryptUpdate(ctx, out, &outl, in, inlen)
|| !EVP_EncryptFinal_ex(ctx, out + outl, &tmplen)) {
EVP_CIPHER_CTX_free(ctx);
return 0;
}
EVP_CIPHER_CTX_free(ctx);
if (outlen)
*outlen = outl + tmplen;
return 1;
}
int main()
{
unsigned char key[] = {
0x3f, 0x34, 0x2e, 0x9d, 0x67, 0xd6, 0xce, 0x7b,
0xe7, 0x01, 0x75, 0x6a, 0xf7, 0xba, 0xc8, 0xf2,};
unsigned char iv[] = {
0x1f, 0xb2, 0xd4, 0x2f, 0xb3, 0x6e, 0x2e, 0x88,
0xa2, 0x20, 0xb0, 0x4f, 0x2e, 0x49, 0xaa, 0x13,};
unsigned char in[] = "hello tongsuo";
unsigned char *out = NULL;
int outlen;
int ret;
out = malloc(sizeof(in) + EVP_MAX_BLOCK_LENGTH);
assert(out != NULL);
ret = sm4_enc(key, iv, in, strlen(in), out, &outlen);
assert(ret == 1);
for (size_t i = 0; i < outlen; i++)
printf("%x", out[i]);
printf("\n");
free(out);
return 0;
}
将内容保存为sm4_enc.c,并编译运行:
gcc sm4_enc.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
输出明文消息的密文如下:
SM4解密:
#include <openssl/evp.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm4_dec(const unsigned char *key, const unsigned char *iv,
unsigned char *in, int inlen, unsigned char *out,
int *outlen)
{
EVP_CIPHER_CTX *ctx = NULL;
int outl, tmplen;
if ((ctx = EVP_CIPHER_CTX_new()) == NULL
|| !EVP_DecryptInit_ex(ctx, EVP_sm4_cbc(), NULL, key, iv)
|| !EVP_DecryptUpdate(ctx, out, &outl, in, inlen)
|| !EVP_DecryptFinal_ex(ctx, out + outl, &tmplen)) {
ERR_print_errors_fp(stderr);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
EVP_CIPHER_CTX_free(ctx);
if (outlen)
*outlen = outl + tmplen;
return 1;
}
int main()
{
unsigned char key[] = {
0x3f, 0x34, 0x2e, 0x9d, 0x67, 0xd6, 0xce, 0x7b,
0xe7, 0x01, 0x75, 0x6a, 0xf7, 0xba, 0xc8, 0xf2,};
unsigned char iv[] = {
0x1f, 0xb2, 0xd4, 0x2f, 0xb3, 0x6e, 0x2e, 0x88,
0xa2, 0x20, 0xb0, 0x4f, 0x2e, 0x49, 0xaa, 0x13,};
unsigned char in[] = {
0xe2, 0x44, 0xdb, 0xeb, 0x97, 0x58, 0x83, 0x1e,
0xa8, 0x7b, 0x7c, 0xeb, 0x27, 0x8e, 0x6e, 0x5d,};
unsigned char *out = NULL;
int outlen;
int ret;
out = malloc(sizeof(in));
assert(out != NULL);
ret = sm4_dec(key, iv, in, sizeof(in), out, &outlen);
assert(ret == 1);
printf("%*s\n", outlen, out);
free(out);
return 0;
}
// gcc sm4_dec.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
保存内容为sm4_dec.c,编译并执行:
gcc sm4_dec.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
输出如下:
SM3杂凑算法编程入门
计算SM3杂凑:
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm3(const unsigned char *in, size_t inlen, unsigned char *out)
{
EVP_MD_CTX *mctx = NULL;
if ((mctx = EVP_MD_CTX_new()) == NULL
|| !EVP_DigestInit_ex(mctx, EVP_sm3(), NULL)
|| !EVP_DigestUpdate(mctx, in, inlen)
|| !EVP_DigestFinal_ex(mctx, out, NULL)) {
EVP_MD_CTX_free(mctx);
return 0;
}
EVP_MD_CTX_free(mctx);
return 1;
}
int main()
{
unsigned char in[] = "hello tongsuo";
unsigned char out[EVP_MAX_MD_SIZE];
int ret;
ret = sm3(in, strlen(in), out);
assert(ret == 1);
for (int i = 0; i < EVP_MD_size(EVP_sm3()); i++)
printf("%x", out[i]);
printf("\n");
return 0;
}
保存内容为sm3.c,编译运行如下:
gcc sm3.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
运行结果如下:
SM2签名算法编程入门
SM2签名:
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm2_sign(EVP_PKEY *pkey, const unsigned char *in, size_t inlen,
unsigned char *out, size_t *outlen)
{
EVP_MD_CTX *mctx = NULL;
if ((mctx = EVP_MD_CTX_new()) == NULL
|| !EVP_DigestSignInit(mctx, NULL, EVP_sm3(), NULL, pkey)
|| !EVP_DigestSign(mctx, out, outlen, in, inlen))
{
ERR_print_errors_fp(stderr);
EVP_MD_CTX_free(mctx);
return 0;
}
EVP_MD_CTX_free(mctx);
return 1;
}
int main()
{
unsigned char msg[] = "hello tongsuo";
unsigned char *sig = NULL;
EVP_PKEY *pkey = NULL;
BIO *bio = NULL;
size_t siglen;
unsigned char privkey[] =
"-----BEGIN PRIVATE KEY-----\n"
"MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgSKhk+4xGyDI+IS2H\n"
"WVfFPDxh1qv5+wtrddaIsGNXGZihRANCAAQwqeNkWp7fiu1KZnuDkAucpM8piEzE\n"
"TL1ymrcrOBvv8mhNNkeb20asbWgFQI2zOrSM99/sXGn9rM2/usM/Mlca\n"
"-----END PRIVATE KEY-----\n";
int ret;
bio = BIO_new_mem_buf(privkey, -1);
assert(bio != NULL);
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
assert(pkey != NULL);
siglen = EVP_PKEY_size(pkey);
sig = malloc(siglen);
ret = sm2_sign(pkey, msg, strlen((const char *)msg), sig, &siglen);
assert(ret == 1);
for (size_t i = 0; i < siglen; i++)
printf("%02x", sig[i]);
printf("\n");
EVP_PKEY_free(pkey);
BIO_free(bio);
free(sig);
return 0;
}
保存代码到文件sm2_sign.c,编译并运行
gcc sm2_sign.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
输出签名结果:
SM2验签:
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm2_verify(EVP_PKEY *pkey, const unsigned char *msg, size_t msglen,
unsigned char *sig, size_t siglen)
{
EVP_MD_CTX *mctx = NULL;
if ((mctx = EVP_MD_CTX_new()) == NULL
|| EVP_DigestVerifyInit(mctx, NULL, EVP_sm3(), NULL, pkey) != 1
|| EVP_DigestVerify(mctx, sig, siglen, msg, msglen) != 1)
{
ERR_print_errors_fp(stderr);
EVP_MD_CTX_free(mctx);
return 0;
}
EVP_MD_CTX_free(mctx);
return 1;
}
int main()
{
unsigned char msg[] = "hello tongsuo";
unsigned char sig[] = {
0x30, 0x44, 0x02, 0x20, 0x64, 0x68, 0xab, 0xee, 0x05, 0xa0, 0x46, 0xef,
0xe2, 0xcd, 0x05, 0x79, 0xa2, 0xa2, 0xe8, 0x9a, 0xf2, 0x70, 0xc4, 0xa3,
0x36, 0x6b, 0xd3, 0x37, 0x2c, 0xee, 0x9a, 0x7f, 0x26, 0x2b, 0x61, 0x01,
0x02, 0x20, 0x73, 0x51, 0x81, 0x60, 0x40, 0xfc, 0x10, 0x32, 0xde, 0xd0,
0x57, 0x4b, 0x43, 0xbb, 0xe8, 0xf0, 0x92, 0x6d, 0x48, 0x24, 0x24, 0x32,
0x6d, 0x1a, 0x52, 0xb2, 0xb0, 0x4e, 0x8a, 0xb5, 0x55, 0x80,};
EVP_PKEY *pkey = NULL;
BIO *bio = NULL;
unsigned char pubkey[] =
"-----BEGIN PUBLIC KEY-----\n"
"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEMKnjZFqe34rtSmZ7g5ALnKTPKYhM\n"
"xEy9cpq3Kzgb7/JoTTZHm9tGrG1oBUCNszq0jPff7Fxp/azNv7rDPzJXGg==\n"
"-----END PUBLIC KEY-----\n";
int ret;
bio = BIO_new_mem_buf(pubkey, -1);
assert(bio != NULL);
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
assert(pkey != NULL);
ret = sm2_verify(pkey, msg, strlen((const char *)msg), sig, sizeof(sig));
assert(ret == 1);
EVP_PKEY_free(pkey);
BIO_free(bio);
return 0;
}
// gcc sm2_verify.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
保存代码为sm2_verify.c,编译并运行:
gcc sm2_verify.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
运行没有报错,说明验证签名成功。
SM2加解密算法编程入门(补充)
SM2公钥加密:
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm2_enc(EVP_PKEY *pkey, const void *in, size_t inlen,
unsigned char **out, size_t *outlen)
{
int ok = 0;
size_t len;
unsigned char *buf = NULL;
EVP_PKEY_CTX *pctx = NULL;
pctx = EVP_PKEY_CTX_new(pkey, NULL);
if (pctx == NULL)
return 0;
if (EVP_PKEY_encrypt_init(pctx) <= 0)
goto end;
if (EVP_PKEY_encrypt(pctx, NULL, &len, in, inlen) <= 0)
goto end;
buf = OPENSSL_malloc(len);
if (buf == NULL)
goto end;
if (EVP_PKEY_encrypt(pctx, buf, &len, in, inlen) <= 0)
goto end;
OPENSSL_free(*out);
*out = buf;
buf = NULL;
*outlen = len;
ok = 1;
end:
EVP_PKEY_CTX_free(pctx);
OPENSSL_free(buf);
return ok;
}
int main()
{
const char *msg = "hello tongsuo";
unsigned char *buf = NULL;
char *s = NULL;
size_t len = 0;
EVP_PKEY *pkey = NULL;
BIO *bio = NULL;
unsigned char pubkey[] =
"-----BEGIN PUBLIC KEY-----\n"
"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEMKnjZFqe34rtSmZ7g5ALnKTPKYhM\n"
"xEy9cpq3Kzgb7/JoTTZHm9tGrG1oBUCNszq0jPff7Fxp/azNv7rDPzJXGg==\n"
"-----END PUBLIC KEY-----\n";
int ret;
bio = BIO_new_mem_buf(pubkey, -1);
assert(bio != NULL);
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
assert(pkey != NULL);
ret = sm2_enc(pkey, msg, strlen((const char *)msg), &buf, &len);
assert(ret == 1);
s = OPENSSL_buf2hexstr(buf, len);
printf("SM2_enc(%s)=%s\n", msg, s);
OPENSSL_free(s);
OPENSSL_free(buf);
EVP_PKEY_free(pkey);
BIO_free(bio);
return 0;
}
保存代码为sm2_enc.c,编译并运行:
gcc sm2_enc.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
注意:因为SM2加密过程有随机数的参与,所以每次加密的结果并不相同。
SM2私钥解密:
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
static int sm2_dec(EVP_PKEY *pkey, const void *in, size_t inlen,
unsigned char **out, size_t *outlen)
{
int ok = 0;
size_t len;
unsigned char *buf = NULL;
EVP_PKEY_CTX *pctx = NULL;
pctx = EVP_PKEY_CTX_new(pkey, NULL);
if (pctx == NULL)
return 0;
if (EVP_PKEY_decrypt_init(pctx) <= 0)
goto end;
if (EVP_PKEY_decrypt(pctx, NULL, &len, in, inlen) <= 0)
goto end;
buf = OPENSSL_malloc(len);
if (buf == NULL)
goto end;
if (EVP_PKEY_decrypt(pctx, buf, &len, in, inlen) <= 0)
goto end;
OPENSSL_free(*out);
*out = buf;
buf = NULL;
*outlen = len;
ok = 1;
end:
EVP_PKEY_CTX_free(pctx);
OPENSSL_free(buf);
return ok;
}
int main()
{
unsigned char ciphertext[] = {
0x30, 0x76, 0x02, 0x21, 0x00, 0x85, 0x94, 0xA2, 0x50, 0x1D, 0x75, 0x9B, 0x8C,
0xF2, 0x29, 0x48, 0x86, 0xC5, 0xB6, 0x22, 0xAF, 0xDF, 0xAC, 0x82, 0xA0, 0x0C,
0x92, 0x71, 0x7E, 0x69, 0x11, 0x0D, 0x33, 0xD5, 0x9B, 0x3C, 0xEB, 0x02, 0x20,
0x18, 0xCE, 0x4B, 0x1F, 0xF9, 0x11, 0xD4, 0xCC, 0x1E, 0xF7, 0x7D, 0xFF, 0xC1,
0xAE, 0x3C, 0xBA, 0x00, 0x20, 0x9C, 0xE8, 0x8A, 0x4A, 0x01, 0x8A, 0x6C, 0x68,
0xF3, 0x99, 0xD4, 0x3D, 0x75, 0x64, 0x04, 0x20, 0x3B, 0xDE, 0xE9, 0xB4, 0xA0,
0x3D, 0x90, 0xC9, 0xA6, 0x9A, 0xA6, 0x3D, 0x88, 0xB3, 0xF2, 0x3C, 0xFB, 0x53,
0x61, 0x4C, 0x7F, 0x7B, 0xC6, 0x70, 0x4E, 0x06, 0x5F, 0xDB, 0x9A, 0x1C, 0x4D,
0x04, 0x04, 0x0D, 0xCF, 0x7B, 0x21, 0x0C, 0x4B, 0x20, 0xA1, 0x31, 0xE4, 0x79,
0xA6, 0xCF, 0xDC,
};
unsigned char *buf = NULL;
size_t i, len = 0;
EVP_PKEY *pkey = NULL;
BIO *bio = NULL;
unsigned char privkey[] =
"-----BEGIN PRIVATE KEY-----\n"
"MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgSKhk+4xGyDI+IS2H\n"
"WVfFPDxh1qv5+wtrddaIsGNXGZihRANCAAQwqeNkWp7fiu1KZnuDkAucpM8piEzE\n"
"TL1ymrcrOBvv8mhNNkeb20asbWgFQI2zOrSM99/sXGn9rM2/usM/Mlca\n"
"-----END PRIVATE KEY-----\n";
int ret;
bio = BIO_new_mem_buf(privkey, -1);
assert(bio != NULL);
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
assert(pkey != NULL);
ret = sm2_dec(pkey, ciphertext, sizeof(ciphertext), &buf, &len);
assert(ret == 1);
printf("SM2_dec()=");
for (i = 0; i < len; i++)
printf("%c", buf[i]);
printf("\n");
OPENSSL_free(buf);
EVP_PKEY_free(pkey);
BIO_free(bio);
return 0;
}
保存代码为sm2_dec.c,编译并运行:
gcc sm2_dec.c -I/opt/tongsuo/include -L/opt/tongsuo/lib64 -lcrypto -Wl,-rpath=/opt/tongsuo/lib64
./a.out
解密出来的明文应该是hello tongsuo,
自签发国密证书
签发步骤为:
- 签发CA根证书
- 签发中间CA证书
- 签发服务器国密双证书