详解Node.js 中使用 ECDSA 签名遇到的坑

最近有个朋友问我关于 Node.js 下使用 ECDSA 的问题,主要是使用 Node.js 的 Crypto 模块无法校验网络传输过来的签名结果。在踩坑无数后,终于搞清楚了原因。

坑 0x00:签名输出格式

在排除了证书、消息不一致的可能之后,我开始对比使用 Node.js 签名的结果与网络传输过来的签名,发现长度不一致,大约差了5~7个字节。于是去网上搜索了一下,才知道原来 Node.js (基于 OpenSSL)签名得到的是 DER 格式的内容,而网络上常用的 ECDSA 签名结果是 IEEE P1363 格式的。(也可以写作 R|S)

参考:https://stackoverflow.com/a/39575576

知道问题了就好解决了。但是,DER 和 IEEE P1363 两个格式互转也不是那么容易的。

简单科普一下,ECDSA 是指基于 ECC 椭圆加密算法的签名方式,签名结果是两个整数 R 和 S。 R 和 S 一般长度相同,或者接近。如果长度不同,在各自前面补字节 0x00 直到等长。把 R 和 S 以大头字节序表示,然后依次前后拼接,就是所谓 IEEE P1363 格式。

坑 0x01:DER 的整数问题

先来了解一下 ECDSA 的 DER 输出格式,大概如下:

SEQUENCE <LENGTH>
 INTEGER <INTEGER_LENGTH> <INTEGER_VALUE...> # 整数 R
 INTEGER <INTEGER_LENGTH> <INTEGER_VALUE...> # 整数 S

其中

SEQUENCE 是 DER 数组(串?)标头,用一个字节 0x30 表示

<LENGTH> 是 SEQUENCE 的长度,用一个字节表示,不包括标头和这个长度本身

INTEGER 是整数标头,用一个字节 0x02 表示

<INTEGER_LENGTH> 是整数的字节长度,用一个字节表示。

<INTEGER_VALUE> 是整数的内容,以大头字节序表示。

另一个坑我也已经写出来了,不知道有人发现没有?没想到的话,继续往下。

IEEE P1363 格式下,R 和 S 都是等长的。所以只要把 IEEE P1363 格式的签名从中间切分就可以得到 R 和 S 的内容了。而且 IEEE P1363 格式下,R 和 S 也是以大头字节序表示的,因此没有字节序转换问题了。现在,只需要按上面的格式构造一个 DER 即可。

坑 0x01.0:缺少整数前置字节 0x00

我第一次尝试将 IEEE P1363 格式的签名转换成 DER 格式,并没有失败,但是当我换一个签名结果,却失败了……我对比了 DER 和 IEEE P1363 的区别,发现了一个特点,在 DER 格式下,R 和 S 偶尔会有前置字节 0x00,但不是一定的。

查资料后才明白,DER 下没有“无符号整数”之说,也就是说整数都是有符号的。如果 INTEGER 所表示的整数最高字节大于 0x7F,也就是最高位(符号位)为 1,则表示负数。如果要表示正数,必须在前面补一个字节 0x00……

参考 https://bitcointalk.org/index.php?topic=215205.msg2258789#msg2258789

坑 0x01.1:多余的整数前置字节 0x00

在我修改代码后,虽然提高了成功率,可仍然有失败的情况,仔细看了下,原来是因为 IEEE P1363 格式里,R 和 S 可能被补了不止 1 个字节 0x00……

而 DER 下虽然要求补字节 0x00,却是有且只能有一个字节 0x00。

到此,问题都解决了——直到我测试了 521-bit (是的,你没看错,不是 512) 长度的密钥时,完全失败,毫无例外。

坑 0x02:DER SEQUENCE 的长度超过 0x7F

前面说了,<LENGTH> 只能用一个字节表示,这是一个整数,前文我提到的整数正负问题,这里也存在!

即是说,ECDSA 签名使用 DER 输出格式时,如果使用 521-bit (是的,你没看错,不是 512) 长度的密钥时,DER的长度将超出 0x7F,使得 <LENGTH> 变成了负数!

而解决方案不是补字节 0x00,而是用字节 0x81 填充 <LENGTH>,再在下一个字节用一个无符号整数的表示长度(0 ~ 255)。

参考:https://stackoverflow.com/a/47099047

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Node.js 中使用 ECDSA 签名遇到的坑

    最近有个朋友问我关于 Node.js 下使用 ECDSA 的问题,主要是使用 Node.js 的 Crypto 模块无法校验网络传输过来的签名结果.在踩坑无数后,终于搞清楚了原因. 坑 0x00:签名输出格式 在排除了证书.消息不一致的可能之后,我开始对比使用 Node.js 签名的结果与网络传输过来的签名,发现长度不一致,大约差了5~7个字节.于是去网上搜索了一下,才知道原来 Node.js (基于 OpenSSL)签名得到的是 DER 格式的内容,而网络上常用的 ECDSA 签名结果是 IE

  • 详解node.js中的npm和webpack配置方法

    概述 Node.js用c++语言编写而成的,是一个基于chrome V8引擎的javascript运行环境,让javaScript的运行脱离浏览器服务端,可以使用javaScript语言书写服务器端代码 1.使用node来实现一个http服务器 下面创建了一个端口为8787的服务器.他与php,java等不同,像php本地还要基于阿帕奇服务器,node.js能用代码快速搭建一个服务器. // 引入http模块 var http = require("http"); // 调用http的

  • 详解Node.js中的Async和Await函数

    在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise. 异步语言结构在其他语言中已经存在了,像c#的async/await.Kotlin的coroutines.go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了. Node中的async函数是什么? 当函数声明为一个Async函数它会返回一个 AsyncFunction 对象,它们类似于 Generator 因为执可以被暂停.唯

  • 详解Node.js中的事件机制

    前言 在前端编程中,事件的应用十分广泛,DOM上的各种事件.在Ajax大规模应用之后,异步请求更得到广泛的认同,而Ajax亦是基于事件机制的. 通常js给我们的第一印象就是运行在客户端浏览器上面的脚本,通过node.js我们可以在服务端运行javascript. node.js是基于单线程无阻塞异步式的I/O,异步式的I/O指的是当遇到I/O操作的时候,线程不阻塞而是进行下面的操作,那么I/O操作完成之后,线程时如何知道该操作完成的呢? 当操作完成耗时的I/O操作之后,会以事件的形式通知I/O操

  • 详解Node.js中path模块的resolve()和join()方法的区别

    关于Node.js中path模块的resolve()和join()方法的比较,对照着总结看例子差不多以后在写模块的时候思路就能很清晰了 resolve 作用:path.resolve() 该方法将一些的 路径/路径段 解析为绝对路径. 语法:path.resolve([...paths]) 说明: ...paths <string> 一个路径或路径片段的序列 如果没有传入 path 片段,或者path 片段长度为零(空字符),则 path.resolve() 会返回当前工作目录的绝对路径(相当

  • 详解Node.js中的模块化方法

    目录 一.模块化的基本概念 1.什么是模块化 2.模块化规范 二.Node.js 中的模块化 1.Node.js 中的模块化分类 2.加载模块 3.向外共享模块作用域中的成员 4.Node.js 中的模块化规范 (1)什么是CommonJS模块规范 (2)CommonJS 模块化规范 (3)每个导出的模块都有一个moudle对象,该对象包含的属性有: 三.npm 与 包 1.包的概念 2.包的安装与卸载 3.包管理配置文件 总结 一.模块化的基本概念 1.什么是模块化 模块化是指解决一个复杂问题

  • 详解Node.js中exports和module.exports的区别

    今天看了下node.js的require方法的源码,终于搞清楚exports和module.exports的区别了. 我们知道,node.js的模块暴露有两种方法. 1. 方式一:用exports //a.js exports.log =function (str) { console.log(str); } //b.js var s = require("./a"); s.log("哈哈哈哈"); 2. 方式二:用module.exports //a.js mod

  • 详解Node.js中间件是怎样工作的

    目录 什么是 Express 中间件? 编写 Express 中间件的要求 Express中间件:基础 中间件解决什么问题?为什么要用它? 中间件函数是什么样的? 中间件链 Express中间件的类型 内置中间件 错误处理中间件 第三方级别的中间件 总结 什么是 Express 中间件? 中间件在字面上的意思是你在软件的一层和另一层中间放置的任何东西. Express 中间件是在对 Express 服务器请求的生命周期内所执行的函数. 每个中间件都可以访问其被附加到的所有路由的 HTTP 请求和

  • 详解node.js 事件循环

    Node.js 是单进程单线程应用程序,但是因为 V8 引擎提供的异步执行回调接口,通过这些接口可以处理大量的并发,所以性能非常高. Node.js 几乎每一个 API 都是支持回调函数的. Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现. Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数. 事件驱动程序 Node.js 使用事件驱动模型,当web server接收到请

  • 详解Node.JS模块 process

    process 模块是 nodejs 提供给开发者用来和当前进程交互的工具,它的提供了很多实用的 API.从文档出发,管中窥豹,进一步认识和学习 process 模块: 如何处理命令参数? 如何处理工作目录? 如何处理异常? 如何处理进程退出? process 的标准流对象 深入理解 process.nextTick 如何处理命令参数? 命令行参数指的是 2 个方面: 传给 node 的参数.例如 node --harmony script.js --version 中,--harmony 就是

随机推荐