相关文章:

  • 国密SM3哈希算法原理及实现(附源码)
  • SHA1哈希算法原理及实现(附源码)
  • MD5哈希算法原理及实现(附源码)
  • MD4哈希算法原理及实现(附源码)
  • MD2哈希算法原理及实现(附源码)
  • MD2中用于随机置换的S盒是如何生成的?

最近陆续造了一批哈希算法的轮子,包括MD家族(包括MD2/MD4/MD5), SHA1, SHA2家族(SHA256/SHA384/SHA512),SHA3家族以及国密SM3算法。
原来打算将每一个算法都详细分析并实现,现在看来,这个工作短时间可能无法完成,所以先将源码发上来。

这部分实现的源码完全参考官方文档的算法描述,连变量名也尽可能和官方文档中的变量保持一致,方便学习。

另外, 代码封装的SM3哈希调用接口参考了openssl官方的接口,完全兼容,无缝对接。会使用这里的接口,就会使用openssl的库函数接口,甚至连代码都不需要修改。

除了实现的源码外,还另外附带了一个测试例子,这个测试例子不仅仅是用于测试哈希算法的实现是否正确,还可以提供了"-f"/"-s"等选项用于对任意文件和字符串进行哈希,因此作为一个工具使用,类似系统内置的md5sum/sha1sum。

SM3实现源码

1. 头文件sm3.c

/** @        file: sm3.h* @ description: header file for sm3.c* @      author: Gu Yongqiang* @        blog: https://blog.csdn.net/guyongqiangx*/
#ifndef __ROCKY_SM3__H
#define __ROCKY_SM3__H#define ERR_OK           0
#define ERR_ERR         -1  /* generic error */
#define ERR_INV_PARAM   -2  /* invalid parameter */
#define ERR_TOO_LONG    -3  /* too long */
#define ERR_STATE_ERR   -4  /* state error */typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;typedef struct sm3_context {/* message total length in bytes */uint64_t total;/* intermedia hash value for each block */struct {uint32_t a;uint32_t b;uint32_t c;uint32_t d;uint32_t e;uint32_t f;uint32_t g;uint32_t h;}hash;/* last block */struct {uint32_t used;     /* used bytes */uint8_t  buf[64];  /* block data buffer */}last;
}SM3_CTX;/* https://www.openssl.org/docs/man1.1.1/man3/SHA256_Final.html */int SM3_Init(SM3_CTX *c);
int SM3_Update(SM3_CTX *c, const void *data, size_t len);
int SM3_Final(unsigned char *md, SM3_CTX *c);
unsigned char *SM3(const unsigned char *d, size_t n, unsigned char *md);
#endif

2. 代码文件sm3.c

/** @        file: sm3.c* @ description: implementation for the SM3 Cryptographic Hash Algorithm* @      author: Gu Yongqiang* @        blog: https://blog.csdn.net/guyongqiangx*/
#include <stdio.h>
#include <string.h>#include "utils.h"
#include "sm3.h"// #define DEBUG#ifdef DEBUG
#define DBG(...) printf(__VA_ARGS__)
#define DUMP_SCHED_DATA 1
#define DUMP_BLOCK_DATA 1
#define DUMP_BLOCK_HASH 1
#define DUMP_ROUND_DATA 1
#else
#define DBG(...)
#define DUMP_SCHED_DATA 0
#define DUMP_BLOCK_DATA 0
#define DUMP_BLOCK_HASH 0
#define DUMP_ROUND_DATA 0
#endif#define HASH_BLOCK_SIZE           64  /* 512 bits = 64 Bytes */
#define HASH_LEN_SIZE           8   /* 64 bits = 8 bytes */
#define HASH_LEN_OFFSET         (HASH_BLOCK_SIZE - HASH_LEN_SIZE)#define HASH_DIGEST_SIZE        32 /* 256 bits = 32 bytes */#define HASH_PADDING_PATTERN  0x80
#define HASH_ROUND_NUM          64/* SM3 Constants */
static uint32_t T[2] =
{0x79CC4519, 0x7A879D8A
};/* ROTate Left (circular left shift) */
static uint32_t ROTL(uint32_t x, uint8_t shift)
{shift %= 32;return (x << shift) | (x >> (32 - shift));
}static uint32_t FF(uint32_t x, uint32_t y, uint32_t z, uint32_t j)
{if (j<16) /* 0 <= j <= 15 */{return x ^ y ^ z;}else /* 16 <= j <= 63 */{return (x & y) | (x & z) | (y & z);}
}static uint32_t GG(uint32_t x, uint32_t y, uint32_t z, uint32_t j)
{if (j<16) /* 0 <= j <= 15 */{return x ^ y ^ z;}else /* 16 <= j <= 63 */{return (x & y) | (~x & z);}
}/* P0, Permutation 0 */
static uint32_t P0(uint32_t x)
{return x ^ ROTL(x, 9) ^ ROTL(x, 17);
}/* P1, Permutation 1 */
static uint32_t P1(uint32_t x)
{return x ^ ROTL(x, 15) ^ ROTL(x, 23);
}int SM3_Init(SM3_CTX *c)
{if (NULL == c){return ERR_INV_PARAM;}memset(c, 0, sizeof(SM3_CTX));/* Initial Value for SM3 */c->hash.a = 0x7380166f;c->hash.b = 0x4914b2b9;c->hash.c = 0x172442d7;c->hash.d = 0xda8a0600;c->hash.e = 0xa96f30bc;c->hash.f = 0x163138aa;c->hash.g = 0xe38dee4d;c->hash.h = 0xb0fb0e4e;return ERR_OK;
}static int SM3_PrepareScheduleWord(const uint32_t *block, uint32_t *W, uint32_t *Wp)
{uint32_t j;if ((NULL == block) || (NULL == W) || (NULL == Wp)){return ERR_INV_PARAM;}/* Array W */for (j=0; j<(HASH_ROUND_NUM+4); j++){if (j<=15) /*  0 <= j <= 15 */W[j] = be32toh(block[j]);else      /* 16 <= j <= 67 */W[j] = P1(W[j-16] ^ W[j-9] ^ ROTL(W[j-3],15)) ^ ROTL(W[j-13],7) ^ W[j-6];}/* Array W Prime */for (j=0; j<HASH_ROUND_NUM; j++){Wp[j] = W[j] ^ W[j+4];}#if (DUMP_SCHED_DATA == 1)printf("          W1...W67:\n");for (j=0; j<(HASH_ROUND_NUM+4); j++){if (j%8 == 0) /* line indent */{printf("          ");}printf("%08x ", W[j]);if (j%8 == 7){printf("\n");}else if (j == (HASH_ROUND_NUM+4-1)){printf("\n"); /* last one */}}printf("          W'1...W'63:\n");for (j=0; j<HASH_ROUND_NUM; j++){if (j%8 == 0) /* line indent */{printf("          ");}printf("%08x ", Wp[j]);if (j%8 == 7){printf("\n");}else if (j == HASH_ROUND_NUM-1){printf("\n"); /* last one */}}
#endifreturn ERR_OK;
}static int SM3_ProcessBlock(SM3_CTX *ctx, const void *block)
{uint32_t j;uint32_t W[HASH_ROUND_NUM+4], Wp[HASH_ROUND_NUM];uint32_t SS1, SS2;uint32_t TT1, TT2;uint32_t A, B, C, D, E, F, G, H;if ((NULL == ctx) || (NULL == block)){return ERR_INV_PARAM;}#if (DUMP_BLOCK_DATA == 1)DBG("---------------------------------------------------------\n");DBG("   BLOCK: %llu\n", ctx->total/HASH_BLOCK_SIZE);DBG("    DATA:\n");print_buffer(block, HASH_BLOCK_SIZE, "    ");
#endif/* prepare schedule word */SM3_PrepareScheduleWord(block, W, Wp);A = ctx->hash.a;B = ctx->hash.b;C = ctx->hash.c;D = ctx->hash.d;E = ctx->hash.e;F = ctx->hash.f;G = ctx->hash.g;H = ctx->hash.h;#if (DUMP_BLOCK_HASH == 1)DBG("      IV: %08x %08x %08x %08x %08x %08x %08x %08x\n",ctx->hash.a, ctx->hash.b, ctx->hash.c, ctx->hash.d, ctx->hash.e, ctx->hash.f, ctx->hash.g, ctx->hash.h);
#endiffor (j=0; j<HASH_ROUND_NUM; j++){SS1 = ROTL(ROTL(A, 12) + E + ROTL(T[j<16?0:1], j), 7);SS2 = SS1 ^ ROTL(A, 12);TT1 = FF(A, B, C, j) + D + SS2 + Wp[j];TT2 = GG(E, F, G, j) + H + SS1 + W[j];D = C;C = ROTL(B, 9);B = A;A = TT1;H = G;G = ROTL(F, 19);F = E;E = P0(TT2);#if (DUMP_ROUND_DATA == 1)
#if 1 /* Don't show temp variables: SS1/SS2/TT1/TT2/W/W' */DBG("      %02d: A=0x%08x, B=0x%08x, C=0x%08x, D=0x%08x, E=0x%08x, F=0x%08x, G=0x%08x, H=0x%08x\n", \j, A, B, C, D, E, F, G, H);
#elseDBG("      %02d: SS1=0x%08x, SS2=0x%08x, TT1=0x%08x, TT2=0x%08x, W=0x%08x, Wp=0x%08x\n"\"         A=0x%08x,    B=0x%08x,   C=0x%08x,   D=0x%08x, E=0x%08x, F=0x%08x, G=0x%08x, H=0x%08x\n", \j, SS1, SS2, TT1, TT2, W[j], Wp[j], A, B, C, D, E, F, G, H);
#endif
#endif}ctx->hash.a ^= A;ctx->hash.b ^= B;ctx->hash.c ^= C;ctx->hash.d ^= D;ctx->hash.e ^= E;ctx->hash.f ^= F;ctx->hash.g ^= G;ctx->hash.h ^= H;#if (DUMP_BLOCK_HASH == 1)DBG("    HASH: %08x %08x %08x %08x %08x %08x %08x %08x\n",ctx->hash.a, ctx->hash.b, ctx->hash.c, ctx->hash.d, ctx->hash.e, ctx->hash.f, ctx->hash.g, ctx->hash.h);
#endifreturn ERR_OK;
}int SM3_Update(SM3_CTX *c, const void *data, size_t len)
{uint32_t copy_len = 0;if ((NULL == c) || (NULL == data)){return ERR_INV_PARAM;}/* has used data */if (c->last.used != 0){/* less than 1 block in total, combine data */if (c->last.used + len < HASH_BLOCK_SIZE){memcpy(&c->last.buf[c->last.used], data, len);c->last.used += len;return ERR_OK;}else /* more than 1 block */{/* process the block in context buffer */copy_len = HASH_BLOCK_SIZE - c->last.used;memcpy(&c->last.buf[c->last.used], data, copy_len);SM3_ProcessBlock(c, &c->last.buf);c->total += HASH_BLOCK_SIZE;data = (uint8_t *)data + copy_len;len -= copy_len;/* reset context buffer */memset(&c->last.buf[0], 0, HASH_BLOCK_SIZE);c->last.used = 0;}}/* less than 1 block, copy to context buffer */if (len < HASH_BLOCK_SIZE){memcpy(&c->last.buf[c->last.used], data, len);c->last.used += len;return ERR_OK;}else{/* process data blocks */while (len >= HASH_BLOCK_SIZE){SM3_ProcessBlock(c, data);c->total += HASH_BLOCK_SIZE;data = (uint8_t *)data + HASH_BLOCK_SIZE;len -= HASH_BLOCK_SIZE;}/* copy rest data to context buffer */memcpy(&c->last.buf[0], data, len);c->last.used = len;}return ERR_OK;
}int SM3_Final(unsigned char *md, SM3_CTX *c)
{uint32_t *temp;//uint64_t *buf;if ((NULL == c) || (NULL == md)){return ERR_INV_PARAM;}/* Last block should be less thant HASH_BLOCK_SIZE - HASH_LEN_SIZE */if (c->last.used >= (HASH_BLOCK_SIZE - HASH_LEN_SIZE)){c->total += c->last.used;/* one more block */c->last.buf[c->last.used] = HASH_PADDING_PATTERN;c->last.used++;memset(&c->last.buf[c->last.used], 0, HASH_BLOCK_SIZE - c->last.used);SM3_ProcessBlock(c, &c->last.buf);memset(&c->last.buf[0], 0, HASH_BLOCK_SIZE - HASH_LEN_SIZE);c->last.used = 0;/* save length *///buf = (uint64_t *)&(c->last.buf[HASH_LEN_OFFSET]);//*buf = htobe64(c->total << 3);temp = (uint32_t *)&(c->last.buf[HASH_LEN_OFFSET]);temp[0] = htobe32((c->total << 3) >> 32 & 0xFFFFFFFF);temp[1] = htobe32((c->total << 3) & 0xFFFFFFFF);SM3_ProcessBlock(c, &c->last.buf);}else /* 0 <= last.used < HASH_BLOCK_SIZE - HASH_LEN_SIZE */{c->total += c->last.used;/* one more block */c->last.buf[c->last.used] = HASH_PADDING_PATTERN;c->last.used++;/* padding 0s */memset(&c->last.buf[c->last.used], 0, HASH_BLOCK_SIZE - HASH_LEN_SIZE - c->last.used);/* save length *///buf = (uint64_t *)&(c->last.buf[HASH_LEN_OFFSET]);//*buf = htobe64(c->total << 3);temp = (uint32_t *)&(c->last.buf[HASH_LEN_OFFSET]);temp[0] = htobe32((c->total << 3) >> 32 & 0xFFFFFFFF);temp[1] = htobe32((c->total << 3) & 0xFFFFFFFF);SM3_ProcessBlock(c, &c->last.buf);}temp = (uint32_t *)md;temp[0] = htobe32(c->hash.a);temp[1] = htobe32(c->hash.b);temp[2] = htobe32(c->hash.c);temp[3] = htobe32(c->hash.d);temp[4] = htobe32(c->hash.e);temp[5] = htobe32(c->hash.f);temp[6] = htobe32(c->hash.g);temp[7] = htobe32(c->hash.h);return ERR_OK;
}unsigned char *SM3(const unsigned char *d, size_t n, unsigned char *md)
{SM3_CTX c;if ((NULL == d) || (NULL == md)){return NULL;}SM3_Init(&c);SM3_Update(&c, d, n);SM3_Final(md, &c);return md;
}

SM3源码的编译和测试

我直接在Makefile中内置了一个test伪目标,编译时除了编译生成名为sm3的哈希工具外,还会直接调用内置的哈希测试。

编译和运行如下:

$ make
gcc -Wall -g -O2 -c utils.c -o utils.o
gcc -Wall -g -O2 -c sm3.c -o sm3.o
gcc -Wall -g -O2 -c sm3test.c -o sm3test.o
gcc -Wall -g -O2 utils.o sm3.o sm3test.o -o sm3Run Test...
./sm3 -x
Internal hash tests for ./sm3:
./sm3("")Expect: 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2bResult: 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b./sm3("a")Expect: 623476ac18f65a2909e43c7fec61b49c7e764a91a18ccb82f1917a29c86c5e88Result: 623476ac18f65a2909e43c7fec61b49c7e764a91a18ccb82f1917a29c86c5e88./sm3("abc")Expect: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0Result: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0./sm3("message digest")Expect: c522a942e89bd80d97dd666e7a5531b36188c9817149e9b258dfe51ece98ed77Result: c522a942e89bd80d97dd666e7a5531b36188c9817149e9b258dfe51ece98ed77./sm3("abcdefghijklmnopqrstuvwxyz")Expect: b80fe97a4da24afc277564f66a359ef440462ad28dcc6d63adb24d5c20a61595Result: b80fe97a4da24afc277564f66a359ef440462ad28dcc6d63adb24d5c20a61595./sm3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")Expect: 2971d10c8842b70c979e55063480c50bacffd90e98e2e60d2512ab8abfdfcec5Result: 2971d10c8842b70c979e55063480c50bacffd90e98e2e60d2512ab8abfdfcec5./sm3("12345678901234567890123456789012345678901234567890123456789012345678901234567890")Expect: ad81805321f3e69d251235bf886a564844873b56dd7dde400f055b7dde39307aResult: ad81805321f3e69d251235bf886a564844873b56dd7dde400f055b7dde39307a./sm3("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd")Expect: debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732Result: debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732

最新版本的openssl工具已经支持sm3哈希算法,因此可以将sm3工具和openssl执行dgst计算的结果进行比较:

$ sm3 -h
Usage:
Common options: [-x|-f file|-s string|-h]
Hash a string:sm3 -s string
Hash a file:sm3 -f file [-k key]
-x      Internal string hash test
-h      Display this message# 使用"-f"和"-s"选项分别对文件和字符串计算sm3哈希值
$ sm3 -f sm3.o
sm3(sm3.o) = 347d091d1269da1e00166b3dec34885b55b011ddd06d989f092f73c9488fcac7
$ sm3 -s "I Love China!"
sm3("I Love China!") = c661494fb7e8f3c7fe9c1926d69961fb1a3ccdc2a1c8cdd817fe0b7f777cea5a# 使用开源的openssl工具计算相应的哈希进行对比
$ openssl dgst -sm3 sm3.o
SHA1(sha1.o)= 347d091d1269da1e00166b3dec34885b55b011ddd06d989f092f73c9488fcac7
$ echo -n "I Love China!" | openssl dgst -sm3
(stdin)= c661494fb7e8f3c7fe9c1926d69961fb1a3ccdc2a1c8cdd817fe0b7f777cea5a

完整代码

完整的代码文件列表如下:

sm3$ ls -lh
total 140K
-rwxr--r-- 1 rocky rocky  592 Jun 19 23:14 Makefile
-rwxrwxr-x 1 rocky rocky  11K Jun 20 00:04 sm3.c
-rwxrwxr-x 1 rocky rocky 1.4K Jun 19 23:19 sm3.h
-rwxr--r-- 1 rocky rocky 6.0K Jun 19 23:35 sm3test.c
-rwxr--r-- 1 rocky rocky  578 Jun 17 21:46 utils.c
-rwxr--r-- 1 rocky rocky 1.7K Jun 17 21:46 utils.h
-rwxr--r-- 1 rocky rocky 102K Jun 19 23:30 国密-SM3密码杂凑算法.pdf

需要代码请访问:

  • https://github.com/guyongqiangx/cryptography/

其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

国密SM3密码杂凑算法原理及实现(附源码)相关推荐

  1. 原味的SM3密码杂凑算法

    根据国家密码管理局官网发布的规范文档里的算法描述,对SM3密码杂凑算法进行了原汁原味的实现.代码里的函数.变量名称都尽量使用算法描述中的名称,尽量遵循算法描述的原始步骤,不使用算法技巧进行处理. 算法 ...

  2. SHA224和SHA256哈希算法原理及实现(附源码)

    相关文章: SHA224和SHA256哈希算法原理及实现(附源码) 国密SM3哈希算法原理及实现(附源码) SHA1哈希算法原理及实现(附源码) MD5哈希算法原理及实现(附源码) MD4哈希算法原理 ...

  3. SHA3系列(KECCAK)哈希算法原理及实现(附源码)

    相关文章: (本文持续更新中) SHA3系列(KECCAK)哈希算法原理及实现(附源码) SHA512系列哈希算法原理及实现(附源码) SHA224和SHA256哈希算法原理及实现(附源码) 国密SM ...

  4. SHA512系列哈希算法原理及实现(附源码)

    相关文章: SHA512系列哈希算法原理及实现(附源码) SHA224和SHA256哈希算法原理及实现(附源码) 国密SM3哈希算法原理及实现(附源码) SHA1哈希算法原理及实现(附源码) MD5哈 ...

  5. SM3密码杂凑算法源码解析

    1.在SM3算法源文件中主要有以下几个函数: void sm3_starts( sm3_context *ctx ); void sm3_update( sm3_context *ctx, unsig ...

  6. android杂凑算法,SM3密码杂凑算法分析

    SM3密码杂凑算法分析 杂凑函数在密码学中具有重要的地位,被广泛应用在数字签名,消息认证,数据完整性检测等领域.杂凑函数通常被认为需要满足三个基本特性:碰撞稳固性,原根稳固性和第二原根稳固性.2005 ...

  7. linux直流电机测试,带霍尔传感器编码器的直流减速电机测速原理讲解(附源码)...

    查看: 14294|回复: 83 带霍尔传感器编码器的直流减速电机测速原理讲解(附源码) 高级会员, 积分 891, 距离下一级还需 109 积分 积分金钱891 注册时间2019-4-22 在线时间 ...

  8. 超详讲解图像拼接/全景图原理和应用 | 附源码

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 概述 图像拼接是计算机视觉中最成功的应用之一.如今,很难找到不包含 ...

  9. python全景图像拼接_超详讲解图像拼接/全景图原理和应用 | 附源码

    研究好玩又有用的技术第 008 期 在学习中发现快乐,在应用找到价值.这是我第八期分享图像技术应用的文章. 前七期欢迎阅读和分享: 概述 作者:Thalles Silva 编译:AI算法与图像处理 图 ...

最新文章

  1. Ubuntu 系统 在终端中过滤log 特殊的信息
  2. 设计模式之_Iterator_03
  3. ASP.NET MVC中同步与异步
  4. 【Python】PyCharm中Matplotlib绘图不能显示UI效果的问题解决
  5. linux设置windows共享为yum源
  6. 优秀的云存储解决方案Dropbox,现在注册就有2G
  7. “编程能力差,90%会输在这点上!”谷歌开发:方法不对,努力也白费
  8. Luogu3387【模板】缩点(Kosaraju)
  9. Hadoop权威指南
  10. ColorUI开发手册(适用于后端同学使用)
  11. R语言 软件安装 源码安装R
  12. 天堂2单机版进去显示服务器维护,天堂2五章单机版设置完成后却玩不了 高手解决一下...
  13. python numpy.linalg.norm函数的用法
  14. springmvc GET POST 接受参数方式
  15. 可重入锁 ReentrantLock
  16. 今天开通个人博客,值得祝贺!
  17. 黑客利用0day,从General Bytes比特币ATM盗走150万美元
  18. Docker Swarm学习教程【转载】
  19. 按首字母升序输出国家名称
  20. 安天每日安全简讯20160712

热门文章

  1. C# WinForm UI 触摸屏按下和抬起事件处理方法
  2. 基于51单片机的蓝牙控制小车的简单实现(有源代码,无图) (上篇)
  3. 光影mod_效果不俗!《雷神之锤2》贴图/光影重制MOD新截图公布
  4. 远程连接工具putty和winscp
  5. 自动登录Github官网,cookie验证简单模拟登录
  6. Unity中EditorWindow的创建和停靠
  7. SQL中纵表、横表互相转换
  8. Altium Designer入门实战教程-从原理图到印制电路板
  9. vue报错Error in render: TypeError: Cannot read property 'name' of undefined
  10. STM32 TIM定时器的使用(2)——输入捕获