惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
AI
AI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
U
Unit 42
Vercel News
Vercel News
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
Microsoft Security Blog
Microsoft Security Blog
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
F
Full Disclosure
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Help Net Security
Recorded Future
Recorded Future
N
News and Events Feed by Topic
雷峰网
雷峰网
V
Vulnerabilities – Threatpost
Schneier on Security
Schneier on Security
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
O
OpenAI News
Project Zero
Project Zero
罗磊的独立博客
G
GRAHAM CLULEY
腾讯CDC
P
Privacy International News Feed
V
V2EX
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
H
Heimdal Security Blog
L
LINUX DO - 热门话题
Forbes - Security
Forbes - Security
美团技术团队
MongoDB | Blog
MongoDB | Blog
Security Latest
Security Latest
M
MIT News - Artificial intelligence
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 Hugo PaperMod 主题 去长鹿旅游休博园 1Password 自动化登录堡垒机 深入 Hyperf:Inject 注解是如何工作的? 译|理解容器镜像层 使用 Rust 实现高性能预写日志(Write Ahead Log) 译|使用 Linux 命名空间隔离系统 深入 Hyperf:HTTP 服务是如何处理请求的? Linux 文件权限学习笔记 译|了解 Bitcask:基于日志结构的 KV 存储引擎 深入 Hyperf:HTTP 服务启动时发生了什么? 一次 Hyperf 注解失效问题分析 将博客迁移到又拍云 译|PHP 7 新的 Hashtable 实现 排查 ES 查询问题:深入了解 json_encode() 函数 浅析 Redlock 分布式锁实现原理 Yar 源码阅读笔记:RPC 服务端 Yar 源码阅读笔记:客户端的并行调用 Yar 源码阅读笔记:客户端的同步调用 Yar 源码阅读笔记:数据传输模块 Yar 源码阅读笔记:消息编码模块 Yar 源码阅读笔记:RPC 通信协议 Yar 源码阅读笔记:开篇 PHP 8 新特性介绍 基于 OpenSSL 实现国密 SM4 加解密 PHP 源码阅读笔记:编译与调试 PHP 什么是惊群问题 大端模式和小端模式 基于 GitHub Actions 定时推送网址到百度站长平台 聊聊五种 I/O 模型 Workerman 源码分析:文件上传 浅入浅出 HTTP 协议 PHP 网络编程:构建 MySQL 蜜罐获取攻击者微信 ID 使用 Workerman 接入 Bilibili 直播弹幕协议 使用 GitHub Actions 自动部署 Hexo 什么是二进制安全 【转载】PHP 程序员进阶之路 优化 Workerman 检查主进程是否存活的逻辑 基于 Redis 实现分布式锁 《PHP 实现 Base64 编码/解码》笔记 PHP 多进程下载必应壁纸 基于 Redis 实现延迟队列 【转载】PHP 实现 Base64 编码/解码 使用 MySQL FIELD() 函数自定义排序规则 Laravel 的 Facades 实现原理 C语言单链表实现约瑟夫环 数据结构学习笔记:顺序栈和链栈(C语言) 使用 QueryList + Redis 下载壁纸 在 Laravel 登录/注册中使用 mews/captcha 扩展包 使用 mews/captcha 扩展包为 Laravel 应用添加图片验证码 给 YOURLS 短网址系统编写插件《批量生成短网址》 给 YOURLS 短网址系统编写插件《Hello World!》 PHP 7.1 使用 json_encode 函数造成浮点类型数据出现精度问题 Laravel 授权策略(Policy)的基本使用 Ubuntu 系统下 WiFi 频繁掉线解决方法 Laravel 应用部署到 Nginx 服务器上的第一个坑 Ubuntu 16.04 LST 安装 Redis 和 Composer PHP 采集逐浪小说章节列表 Nginx 环境 SSL 强制 HTTP 301 跳转到 HTTPS 【工作&生活】第一周 ServiceStack.Redis 与多线程 Count the number of Duplicates C# Base64 加密及解密 ASP.NET MVC 使用动态类型传递数据 Django 使用 order_by() 对数据进行排序操作 ASP.NET + jQuery + AJAX 实现用户登录 ASP.NET 从客户端中检测到有潜在危险的 Request.Form 值 ASP.NET 使用 FormData 实现 AJAX 上传图片 Python 使用 BeautifulSoup 抓取网页 贪心算法之字符串的完美度 一言不和就造轮子之 CookieHelper ASP.NET 上传图片 DBHelper 类 C# 捕获屏幕源码 用 JavaScript 做一些有趣的事情 第一篇文章 好久不见。 如何防止非法调用 PHP 文件 清明小记。 PHP 生成随机字符串 Z-Blog PHP 程序做 301 跳转 PHP 处理数组常用的几个函数 Sublime Text 3 快捷键总结 PHP 使用 file_get_contents() 函数实现采集网页 使用 C# 编写学生信息管理系统 终于到了。 zblog报错: magic_quotes_gpc 和 safe_mode 世事无常。 如何使用 PHP 上传文件 安卓手机编写和运行 PHP 程序的软件 使用 AJAX 刷新、验证 PHP 图片验证码 PHP 生成图片验证码 PHP 之 session 的使用方法 PHP 之 cookie 的使用方法 PHP 实现分页原理详解 Thinkphp 框架中系统常量在 Js 文件中不解析的解决方法
还原 SM2 压缩公钥的几种方法
她和她的猫 · 2023-06-19 · via 文章列表 - 她和她的猫

写这篇文章的起因是朋友让我帮忙解决一个与 SM2 算法加密相关的问题。由于我对 SM2 算法并不熟悉,因此在解决问题的过程中走了很多弯路,花了很多时间去了解 SM2 算法以及如何通过代码还原压缩公钥。随着越来越多的系统采用国密算法,我们在对接的时候难免会遇到类似的问题,网上关于这方面的资料也比较少,因此趁周末有空,将我发现的几种还原压缩公钥的方法记录下来,希望对你有所帮助。

在介绍几种还原 SM2 压缩公钥的方法之前,让我们先了解一下什么是 SM2 压缩公钥。

在 SM2 算法中,公钥的大小为 64 字节,算上前缀 04 的话就是 65 字节。公钥由椭圆曲线上的坐标点(x, y)组成,即每个坐标点都是 32 字节的大数。为了节省存储空间,通常会对公钥进行压缩后使用,也就是压缩公钥。

压缩公钥分别由前缀和坐标点 x 一共 33 字节组成,当坐标点 y 是偶数时,使用 02 作为前缀,否则使用 03 作为前缀。使用 16 进制字符串表示时,字符串长度为 66 个字符。

还原压缩公钥的原理:先通过压缩公钥的前缀,确定坐标点 y 是奇数还是偶数,然后根据椭圆曲线的公式计算得到完整的公钥。

没看懂?没关系,下面我介绍几种还原压缩公钥的方法,让你不需要知道原理也能还原压缩公钥。

第一种方法:使用在线工具

下面是我找到的两个比较好用的在线工具:

第一个网站,只要将压缩公钥粘贴到输入框,点击「还原公钥」按钮就可以得到完整的公钥,该工具输出的结果还需要去除其中的空格才能使用。

第二个网站,除了支持压缩公钥以外,还支持 HEX、PEM 格式的公钥,先将公钥粘贴到输入框,网站会自动将其转换成 PEM 格式(sm2p256v1),然后借助下面这段代码就能得到完整的公钥。

$pemStr = 'PEM 格式的公钥';
$pemStr = str_replace(['-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----', PHP_EOL], '', $pemStr);
$uncompressedPublicKey = substr(bin2hex(base64_decode($pemStr)), -128);
echo "未压缩公钥:" . $uncompressedPublicKey;

第二种方法:使用 Java 的 Bouncy Castle 类库

虽然在线工具非常便捷,并且也能够达到我们想要的目的,但还是缺乏一些安全性,因为我们不清楚这些在线工具是否会收集信息,所以最好还是使用本地运行的代码来还原压缩公钥。然后我开始在网上搜索还原压缩公钥的相关资料,但找了很久都没有找到。于是我修改了搜索词,最后,我在某个使用 Java 基于 Bouncy Castle 封装 SM2 工具类的文章中找到了一些思路,也就有了 Java 版还原压缩公钥的代码。

import cn.hutool.core.util.HexUtil;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

public class Main {
    public static void main(String[] args) {
        String compressedPublicKey = "02 或 03 开头的压缩公钥";
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        // 提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(compressedPublicKey));
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        String uncompressedPublicKey = HexUtil.encodeHexStr(publicKeyParameters.getQ().getEncoded(false));
        System.out.println("未压缩公钥:" + uncompressedPublicKey);
    }
}

第三种方法:使用 PHP 的 lpilp/guomi 包

实际上,一开始就只有上面 Java 版本的代码,在准备写这篇文章的时候,突然想到 PHP 应该也能实现才对,然后我又去研究了下怎么在 PHP 中实现还原压缩公钥。

在 PHP 中对于国密算法相关的操作,一般都是使用 lpilp/guomi 这个包,它实现了 SM2、SM3、SM4 国密算法,其中 SM2 算法是基于 mdanter/ecc 这个包实现的。虽然 lpilp/guomi 支持 SM2 算法相关的操作,但是对外提供的方法都只支持未压缩公钥,对于压缩公钥只能我们自己想办法。

于是,我开始研究它对外提供的这几个方法,最后在 RtSm2::verifySignOutKey 方法中找到了一些蛛丝马迹。

public function verifySignOutKey( $document, $sign, $publickeyFile, $userId = null ) {  
	...
    // Parse signature  
    $sigSerializer = new DerSignatureSerializer();  
    $sig = $sigSerializer->parse( $sigData );  
  
    // Parse public key  
    $keyData = file_get_contents( $publickeyFile );  
    $derSerializer = new DerPublicKeySerializer( $adapter );  
    $pemSerializer = new PemPublicKeySerializer( $derSerializer );  
    $key = $pemSerializer->parse( $keyData );  
  
    $pubKeyX = $this->decHex( $key->getPoint()->getX() );  
    $pubKeyY = $this->decHex( $key->getPoint()->getY() );  
    $hash = $this->_doS3Hash( $document, $pubKeyX, $pubKeyY, $generator, $userId );  
    $signer = new Sm2Signer( $adapter );  
  
    return $signer->verify( $key, $sig, $hash );  
}

上面这段代码中的 $pubKeyY 不就是 SM2 算法中公钥的另一个坐标点 y 的值吗?将 $pubKeyX 和 $pubKeyY 拼接在一起就可以得到完整的公钥。经过一顿调试并将上面的代码进行简化后,就有了 PHP 版本的实现。

use Mdanter\Ecc\Serializer\Point\CompressedPointSerializer;
use Mdanter\Ecc\Serializer\Point\UncompressedPointSerializer;
use Rtgm\ecc\RtEccFactory;

$adapter = RtEccFactory::getAdapter();
$curve = RtEccFactory::getSmCurves()->curveSm2();

$compressedPublicKey = '02 或 03 开头的压缩公钥';

$compressedPointSerializer = new CompressedPointSerializer($adapter);
$point = $compressedPointSerializer->unserialize($curve, $key);

$uncompressedPointSerializer = new UncompressedPointSerializer();
$uncompressedPublicKey = $uncompressedPointSerializer->serialize($point);

echo "未压缩公钥:" . $uncompressedPublicKey;

总结

在本文中,我向你介绍了三种还原压缩公钥的方法。首先是使用在线工具,它们可以直接将压缩公钥转换为完整的公钥,使用起来比较方便,但缺乏了安全性。其次是使用 Java 的 Bouncy Castle 类库,通过编写代码来还原压缩公钥,保证了安全性和隐私性。最后是使用 PHP 的 lpilp/guomi 包,其效果与 Java 版本的一致。在实际使用中,你可以根据自己的需求来选择合适的方法。