1 Star 2 Fork 1

qomo/ECDSA

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

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/qomoliao/ecdsa.git
git@gitee.com:qomoliao/ecdsa.git
qomoliao
ecdsa
ECDSA
master

搜索帮助