1 Star 2 Fork 1

qomo/ECDSA

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
QomoLiao- QomoLiao kP 2785284 4年前
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
4年前
kP
4年前
Loading...
README

背景

最近有个嵌入式的项目需要做IAP升级,而且要对升级固件进行签名校验,实现安全升级。

之前没有做过固件的签名校验,网上也很少能找到IAP签名校验的教程,于是在摸索干活的过程也顺带一记,做一分享。希望对看到的人和未来的自己有所帮助。

数字签名简介

所谓签名,就是给某段信息做认证,说明段信息是签名者认可的(防抵赖——合同签名),没有被人修改过的(防篡改——邮件帖封条并签名)。

数字世界的签名,也是为了防篡改和防抵赖。在我们的固件升级场景,主要是防止不被信任的攻击者制作恶意固件给我们的系统升级,造成某种不良后果。同时能够校验固件传输过程是否产生数据丢失或变化,防止升级了异常固件导致系统失效。

数字签名原理

数字签名的大体过程如上图,虚线左边是签名过程:

  1. 首先,需要对编译好的固件先做哈希运算,得到一段比较短的摘要信息。
  2. 然后使用我们自己的私钥对摘要进行加密,加密后的这段信息就叫做数字签名。

之后,可以将固件+加密后的摘要一起发给接收方,接收方:

  1. 用公钥对加密后的摘要解密,获得原始摘要。
  2. 用相同的哈希算法对接收到的固件做哈希运算,得到另一份摘要。
  3. 将两种计算方法获得的摘要做对比,若相同,说明固件是可信的,否则不可信。

数字签名有两个关键:

  1. 一是必须采用非对称算法。只有信息发送方掌握私钥,即使攻击者获取到存在MCU里的公钥,也没法伪造加密的摘要。
  2. 二是非对称算法对算力要求比较高,不可能对整个固件做加密。所以需要先用哈希算法计算一个摘要,再对摘要进行加密。

非对称加密算法

非对称加密算法有两类:RSA和ECC。

  1. RSA算法的原理是:根据数论,寻求两个大素数比较简单,而将它们的乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
  2. ECC是建立在基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点G,并选取一个整数k,求解K=kG很容易(注意根据kG求解出来的K也是椭圆曲线上的一个点);反过来,在椭圆曲线上给定两个点K和G,若使K=kG,求整数k是一个难题。ECC就是建立在此数学难题之上,这一数学难题称为椭圆曲线离散对数问题。

具体原理不再展开。

哈希算法

哈希算法有很多,如MD2、MD5、SHA等,目前被认为安全的哈希算法是256位或以上的SHA算法。

公司要求与算法选型

为了密钥安全,我司使用的签名密钥必须通过公司平台申请。申请通过后平台返回一个签名工具和公钥,私钥是存在公司服务器里的,任何人都无法获取。

同时,为了保证加密信息的破解难度,平台限制了加密算法的种类和参数,仅提供有限的几种算法。

若采用RSA算法,必须使用3072位的RSA3072bits;若采用ECC算法,椭圆曲线只能选prime256v1或secp384r1。

考虑到ECC算法的密钥长度比较短,计算资源消耗小,考虑采用ECC算法。SHA+ECC用于数字签名也叫做ECDSA,本项目选用了secp384r1参数的ECDSA签名算法。关于ECDSA算法的原理,可以参考知乎文章《一文读懂ECDSA算法如何保护数据》

用openssl进行签名与校验

先使用openssl工具对固件进行签名,获取签名过程中生成的摘要、最终签名信息。以与开发的算法计算结果做比对,验证算法准确性。

openssl生成的公钥等信息都经过了某种规范的编码,为了能够对比,必须了解这种格式的编码规则,从而提取出算法使用的具体参数。具体的编码规则和解析工具可看考附录。

摘要计算

显示支持的摘要算法列表:
openssl dgst -list

用sha384计算摘要:
openssl dgst -sha384 test.txt

ECC签名

显示支持的ECC曲线:
openssl ecparam -list_curves

选择secp384r1曲线,生成密钥:
openssl ecparam -name secp384r1 -genkey -out ec.key

生成的密钥采用ECC格式,用以下命令获取16进制的私钥和公钥:

root@localhost:~# openssl ec -in ec.key -noout -text
read EC key
Private-Key: (384 bit)
priv:
	fd:6d:22:76:6e:89:34:a1:08:5e:c9:d8:9b:a9:69:
	51:03:ac:0e:bd:14:f0:09:72:fb:5b:1e:ff:33:92:
	fd:d9:f3:b8:a7:26:27:1e:2d:bb:b0:1c:31:f7:00:
	18:7c:28
pub:
	04:5f:50:a4:36:4a:e9:a2:78:f6:0f:56:78:5b:11:
	b8:92:0b:98:5e:9b:a0:69:6a:88:11:3f:5f:21:b3:
	80:08:bc:05:53:c8:12:dc:e0:02:af:c4:d2:79:4c:
	35:a9:6f:be:6d:0f:78:b1:3d:3e:f9:5c:45:24:53:
	76:31:3b:22:1c:64:1b:8c:42:f6:84:75:68:14:14:
	5b:96:34:f3:de:2d:b9:0f:46:2d:9f:2c:fd:d4:28:
	b2:0f:ea:ee:ab:7d:c1
ASN1 OID: secp384r1
NIST CURVE: P-384

参考一文读懂ECDSA算法如何保护数据 ,ECC加密的依据是公式Qa = dA × G。其中,G是椭圆曲线的起始点,dA是一个随机数(也即私钥),Qa是G与dA进行乘法运算后得到的点(也即公钥)。

对于secp384r1曲线

  • 私钥就是一个48bytes的16进制数,如上priv字段所示。
  • 公钥的表达格式有两种,压缩格式或非压缩格式。如上pub字段采用了非压缩格式:前缀04+x坐标+y坐标。可以转换为压缩格式的公钥:前缀03+x(如果y是奇数),前缀02+x(如果y是偶数)。

后面可以看到,Easy-ECC采用了压缩格式公钥作为参数。

生成公钥:
openssl ec -in ec.key -pubout -out ec.pub

使用哈希sha384算法,和ECC密钥对test.txt文件签名,生成签名文件ec.sig:
openssl dgst -sha384 -sign ec.key -out ec.sig test.txt

ec.sig是一个二进制文件,用ASN.1语言与DER编码规则描述。用ASN.1在线解析工具可以看到,其实就是两个384bits长度的整数,也即ECDSA签名原理中的值对(R, S)。

注意:ASN.1 规定整型 INTEGER 需要支持正整数、负整数和零。BER/DER 使用大端模式存储 INTEGER,并通过最高位来编码正负数(最高位0为正数,1为负数)。 如果密钥参数值最高位为 1,则 BER/DER 编码会在参数前额外加 0x00 以表示正数,这也就是为什么有时候密钥参数前面会多出1字节 0x00 的原因。

ec.sig签名示例

为了方便的将签名的数据拷贝到我们的工程里,也可以使用如下指令输出十六进制格式的签名:

$ xxd -i ec.sig 
unsigned char ec_sig[] = {
  0x30, 0x66, 0x02, 0x31, 0x00, 0xaa, 0xc6, 0x8c, 0x0d, 0x2f, 0x18, 0x87,
  0xbc, 0x96, 0x1c, 0xa4, 0x40, 0x01, 0xec, 0x7a, 0x71, 0xbd, 0x13, 0x3c,
  0xd4, 0x74, 0xfc, 0xce, 0x22, 0xbf, 0x64, 0x57, 0x8a, 0x86, 0xde, 0x1e,
  0xc4, 0x50, 0x75, 0x7b, 0x47, 0xc2, 0x0f, 0x2f, 0x31, 0x47, 0xa2, 0xa9,
  0x0b, 0xba, 0xe0, 0x40, 0xdb, 0x02, 0x31, 0x00, 0xd9, 0x5a, 0xff, 0xf3,
  0xe0, 0x08, 0xf8, 0x2c, 0x1b, 0x45, 0x0c, 0x2d, 0xcf, 0x4a, 0x57, 0xa7,
  0x97, 0x49, 0x32, 0x17, 0x27, 0x63, 0x19, 0x8c, 0x9c, 0x2f, 0x04, 0x88,
  0x61, 0x52, 0x55, 0x55, 0x8c, 0xc2, 0xda, 0x92, 0xca, 0x56, 0xc0, 0x1b,
  0xfd, 0x43, 0x1e, 0xf9, 0x59, 0x24, 0x13, 0x20
};
unsigned int ec_sig_len = 104;

校验

openssl dgst -sha384 -verify ec.pub -signature ec.sig test.txt

签名校验的C语言实现

至此,我们知道:

  • 签名的基本原理与流程
  • 如何使用openssl计算文件的Hash
  • 如何使用openssl生成密钥对
  • openssl生成的密钥对的格式,如何提取私钥和公钥的十六进制表示
  • 公钥的非压缩格式和压缩格式转换关系
  • 用openssl对文件签名
  • ECDSA签名文件的格式
  • 用openssl进行验签

下面将寻找合适嵌入式环境的哈希算法和ECC算法库,并用openssl对两个算法库计算效果进行验证。相关代码链接请看gitee工程。主要验证了:

  1. libhash库的Hash算法的准确性
  2. Easy-ECC库的密钥对生成,签名,校验流程
  3. Easy-ECC使用openssl生成的密钥对用于签名与校验
  4. Easy-ECC使用openssl生成的公钥对openssl签名的文件进行校验

libhash算法库

libhash是C语言实现的小型hash算法,它支持sha1、sha224、sha256、sha384和sha512。我们将用到他的sha384算法。

用以下代码片段即可计算hello字符串的哈希,与openssl dgst -sha384 test.txt命令生成的哈希比对。

test.txt文件里也保存了hello字符串,注意文件的最后不要有换行。(如果用echo "hello" > test.txt命令生成test.txt文件会在字符串后面自动加一个换行符)

    printf("\nCalculate the hash of \"hello\" string\n");
    sha384(test, 5, hash);
    PrintHexBuf("Hash", hash, ECC_BYTES)

Easy-ECC算法库

easy-ecc是C语言实现的ECC算法,只有一个头文件和一个源码文件,很容易迁移到嵌入式环境中。它支持secp128r1、secp192r1、secp256r1、secp384r1四种椭圆曲线。我们将用到secp384r1曲线。

先测试一下这个库能否工作

    printf("\nGenerate keys\n");
    ecc_make_key(pubkey, prikey);
    printf("\nCalculate ECDSA sign\n");
    ecdsa_sign(prikey, hash, signature);
    printf("\nVerify the sign\n");
    if (ecdsa_verify(pubkey, hash, signature))
        printf("Verify OK\n");
    else
        printf("Verify FAIL\n");

再将openssl生成的密钥对转换成库需要的格式

void ReadOpensslPriKey(uint8_t prikey[ECC_BYTES], char *prikey_str)
{
    for (int i = 0; i < ECC_BYTES; i++) {
        sscanf(prikey_str, "%2x:", &prikey[i]);
        prikey_str += 3;
    }
}

void ReadOpensslPubKey(uint8_t pubkey[ECC_BYTES + 1], char *pubkey_str)
{
    uint8_t last_data;
    pubkey_str += 3;

    for (size_t i = 0; i < ECC_BYTES; i++) {
        sscanf(pubkey_str, "%2x:", &pubkey[i + 1]);
        pubkey_str += 3;
    }
    for (size_t i = 0; i < ECC_BYTES; i++) {
        sscanf(pubkey_str, "%2x:", &last_data);
        pubkey_str += 3;
    }
    if (last_data % 2 == 1)
        pubkey[0] = 0x03;
    else
        pubkey[0] = 0x02;
}

void main(void) {
...
    printf("\nRead prikey and pubkey from openssl generation format\n");
    ReadOpensslPriKey(prikey, openssl_prikey);
    ReadOpensslPubKey(pubkey, openssl_pubkey);
    PrintHexBuf("PrivKey", prikey, ECC_BYTES);
    PrintHexBuf("PubKey", pubkey, ECC_BYTES + 1);
...
}

用openssl生成的密钥对进行签名与校验

    printf("\nCalculate ECDSA sign\n");
    ecdsa_sign(prikey, hash, signature);
    PrintHexBuf("Signature", signature, ECC_BYTES * 2);

    printf("\nVerify the sign\n");
    if (ecdsa_verify(pubkey, hash, signature))
        printf("Verify OK\n");
    else
        printf("Verify FAIL\n");

用openssl生成的公钥对openssl的签名进行验证

    printf("\nVerify the sign from openssl\n");

    for (int i = 0; i < ECC_BYTES; i++) {
        hash_reverse[i] = hash[ECC_BYTES - 1 - i];
        ssl_sig_reverse[i] = ssl_sig[ECC_BYTES - 1 - i];
        ssl_sig_reverse[i + ECC_BYTES] = ssl_sig[2 * ECC_BYTES - 1 - i];
    }
    if (ecdsa_verify(pubkey, hash, ssl_sig))
        printf("Verify OK\n");
    else
        printf("Verify FAIL\n");

如此,验证了这两个算法库的可用性与使用方法。

附录

空文件

简介

取消

发行版

暂无发行版

贡献者 (1)

全部

近期动态

4年前推送了新的提交到 master 分支,5ead8fb...2785284
4年前推送了新的提交到 master 分支,a83cee2...5ead8fb
4年前推送了新的提交到 master 分支,c23d0ad...a83cee2
4年前推送了新的提交到 master 分支,b16893b...c23d0ad
4年前推送了新的提交到 master 分支,f10d721...b16893b
加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/qomoliao/ecdsa.git
git@gitee.com:qomoliao/ecdsa.git
qomoliao
ecdsa
ECDSA
master

搜索帮助