RSA加密 多线程读写不安全

news2024/10/6 16:19:14

转自:(一场开源 RSA 库引发的“血案”)

导读

RSA 加密算法是一种非对称加密算法,该算法极为可靠,在现有技术条件下,很难破解,因此在软件开发中被广泛使用。你不必担心,本文不会介绍深奥的 RSA 加密算法,也没有复杂的数学公式。本文将结合 58 iOS App 项目实践,分享一次我们奇异的 Bug 排查经历,谈谈 GitHub 上一个知名的 RSA 算法库 Objective-C-RSA 使用过程中遇到的一些暗坑。我们调研发现许多 SDK(如:高德地图、快手激励视频、电信一键登录、 极验等) 和 App(如:百度、京东、快手、美团、淘宝、微信等) 都使用了该库,经反复测试在并发的情况下同时利用该库生成不同的RSA公钥会有30%甚至更高的概率出现冲突,导致加密结果异常。希望本文对读者有所启示,避免出现类似问题。

问题背景

在 GitHub 上搜索 RSA,选择 Objective-C 语言过滤,结果如下:
在这里插入图片描述

在匹配度、点赞、复刻等多维度搜索结果中,Objective-C-RSA 都名列第一,可以称得上是 ObjC 中质量最好的 RSA 算法开源库。在我们事后的调研中,发现快手激励视频 、高德地图、电信免密登录、极验等许多三方 SDK 都使用了该算法库,另外百度、京东、快手、美团、淘宝、微信等大型 App 都直接或间接的依赖了该库。

使用 strings 查找字符串
在这里插入图片描述

使用反编译工具查找 RSAUtil_PubKey 字符串引用

在这里插入图片描述
注:1. 上述分析只查找了各 App 主二进制中是否存在 Objective-C-RSA 库中的 RSAUtil_PubKey 字符串,结果仅供参考,如有出入,实属正常。2. 在并发环境下,RSA 加密会有一定概率出现异常,非并发环境一般不会有问题。各 App 应根据业务场景,具体问题具体分析。

在最近的项目中,我们也有用到 RSA 算法,Objective-C-RSA 作为 iOS 端质量最佳的 RSA 开源库,开发同学优先选择了该库。由于当时项目特殊,开发周期比较紧张,整个开发、测试过程中,都没能发现潜在的问题。待项目上线后,后端同学监控发现,RSA 解密过程出现大量异常数据,主要包括以下两种错误类型:

RSA 加密结果为空

加密数据无法解密

都是并发惹的祸

在分析服务器端提供的错误日志后,客户端研发同学第一反应可能是并发引起的问题。仔细阅读Objective-C-RSA 说明文档后,发现作者在 README 文档中简单提到线程不安全,但具体原因未写。而后又翻阅了相关 issue,曾有人提到过该问题(详见 issue#50),作者回复给出的方案是“避免在业务层多线程调用加密方法”。作为一千多 Star 的开源库,无论在说明文档,还是 issue 解决中,如此草率,真是坑人,开发同学看完直欲喷人。要是 Linus Torvalds 看到这样的代码,画面简直不敢想象。

问题至此,只怪自己学艺不精,喷亦无用,还是要优先解决问题。开发同学通过编写并发测试用例,发现在 addPublicKey: 方法中,调用SecItemCopyMatching 方法时,会出现查询数据失败的情况,错误码为-25300,具体错误原因为The specified item could not be found in the keychain,即『在 Keychain 中未找到指定的数据项』。

线程不安全,难道 Keychain 中的 API 线程不安全吗?这锅应该苹果来背,开始我们也是这样认为的。之后又查阅的了更多资料,发现许多开发者都有 Keychain API 是否线程安全的困惑。苹果在官方文档 Certificate, Key, and Trust Services 并发章节中提到:
In macOS, some of the functions of this API block while waiting for input from the user (for example, when the user is asked to unlock a keychain or give permission to change trust settings). In general, it is safe to use this API in threads other than your main thread, but avoid calling the functions from multiple operations, work queues, or threads concurrently. Instead, serialize function calls or confine them to a single thread.In iOS, all the functions in this API are thread-safe and reentrant.

大意翻译一下:

在 macOS 中,证书、密钥、信任服务 API 中的一些函数在等待用户输入时会被阻塞(例如,当用户被要求解锁钥匙串或允许改变信任设置时)。一般来说,在线程中使用该 API 是安全的,不仅仅是主线程,但要避免在多个操作、工作队列或线程中同时调用这些函数。相反,要将函数调用序列化,或将其限制在一个线程中。在 iOS 中,该 API 中的所有函数都是线程安全和可重入的。

从苹果官方文档看来,Keychain 中相关的 API 都是线程安全的,那么问题又出自哪里?我们来看下 +[RSA addPublicKey:] 方法的具体实现:
在这里插入图片描述
上述方法使用钥匙串 API 将 RSA 公钥字符串,添加到钥匙串中,最后从钥匙串中读取返回 SecKeyRef 结构。我们知道 Keychain 中的数据存储于系统提供的共享数据库中,是位于磁盘上的数据。在越狱设备中使用Keychain-Dumper工具可以查看其中数据。
在这里插入图片描述
作者在生成 RSA 公钥数据结构 SecKeyRef 过程中,同时使用了钥匙串的删除(SecItemDelete)、添加(SecItemAdd)、查询(SecItemCopyMatching)接口,其中每个 API 都是线程安全的。真正的问题在于,多线程环境中组合使用上述 API,同时读写钥匙串中的数据,并不能保证数据正确性。当一个线程在读取钥匙串中数据时,另外一个线程碰巧将数据删除,这时会出现读取钥匙串数据为空的情况,此正是上文提到的-25300错误的原因。

命名的艺术

搞明白问题出现的原因,处理完并发问题,你以为问题就解决了!曾以为这段代码的坑在第一层,谁也料不到坑在地下十八层。软件开发中命名确实是让很多人头疼的事情,即使我们不追求优雅的艺术,但也要留神避免冲突,那么问题与命名又有何关系?
上文提到服务器端反馈过两种错误类型,解决完并发问题,RSA 加密结果为空的数据得到解决。但另外一个问题,客户端 RSA 加密数据,服务器端解密异常,在我们上百万并发测试的过程中,始终没能复现(当时使用了测试 Demo 验证,与工程环境有所差异)。曾一度提出各种猜想:双端 RSA Padding 不一致、ObjC 与 Java 端 RSA 算法兼容性有问题、后端的解密代码有问题、亦或数据传输过程中有丢失。通过仔细分析,这些假定都被我们一一排除。在始终找不到头绪的情况下,又让后端提供了数万条错误日志,一位细心的同事在其中发现了端倪,拨开层层迷雾,看到一缕曙光。

在后端提供的错误日志中,少量解密失败的数据,长度恰好是正常数据的一半,加密结果莫明其妙短了一半,这仅仅是巧合吗?我们知道 RSA 加密后的密文位长跟密钥的位长度是相同的,2048 位的密钥长度,为何会出现 1024 位的加密结果?再次细读 Objective-C-RSA 加密实现代码,还是在 +[RSA addPublicKey:] 方法中,读写钥匙串数据相关代码,使用了一个 TAG 标记(kSecAttrApplicationTag),其默认值为 RSAUtil_PubKey,灵光乍现,拍案而起,莫非与其他 SDK 有冲突!

为了验证我们的猜测,需要确认其他 SDK 中是否使用了同样的 TAG。结合以往的逆向经验,我们首先想到的方法是反编译 App,搜索字符串,查找数据引用,结果如下图所示:
在这里插入图片描述

其中一个方法,由集团内部 SDK 实现,与相应研发人员沟通,使用对方私钥,测试验证可以解密部分错误日志,在实践层面进一步证实我们的猜测:各 SDK 之间使用相同 TAG 读写 RSA 公钥,并发环境下会出现脏读,导致加密数据混乱。
为解决工程中其他 SDK 相互影响出现问题,我们进一步排查哪些 SDK 使用过默认的 TAG 值。在 Hopper 中反编译,可以查找引用函数,但不易判断函数出自哪个 SDK。想到 strings 命令可以在二进制文件中查找字符串,我们编写了一个脚本,输入文件目录,可以批量查找 .framework 和 .a 库中是否存在指定的字符串。
脚本核心实现如下,完整脚本可移步 (WBBlades) 下载。

search_liba() {
 dir=$1
 for lib in $(find $dir -type f -name "*.a");
 do
 cnt=$(strings $lib | grep $keyword -wc)
 if [[ $cnt -gt 0 ]]; then
 echo "$lib -> $keyword($cnt)"
 fi
 done
}

search_framework() {
 dir=$1
 for lib in $(find $dir -type d -name "*.framework");
 do
 lib_name=${lib##*/}
 lib_name_without_ext=${lib_name%.framework}
 lib_full_name=$lib/$lib_name_without_ext
 if [[ -e "$lib_full_name" ]]; then
 cnt=$(strings $lib_full_name | grep $keyword -wc)
 if [[ $cnt -gt 0 ]]; then
 echo "$lib_full_name -> $keyword($cnt)"
 fi
 fi
 done
}

使用脚本搜索字符串 RSAUtil_PubKey,部分查找结果如下:

the keyword you input: RSAUtil_PubKey

/AMapFoundationKit.framework/AMapFoundationKit -> RSAUtil_PubKey(4)
/AnyThinkSDK.framework/AnyThinkSDK -> RSAUtil_PubKey(5)
/EAccountApiSDK.framework/EAccountApiSDK -> RSAUtil_PubKey(4)
/GT3Captcha.framework/GT3Captcha -> RSAUtil_PubKey(4)
/KSAdSDK.xcframework/ios-arm64_armv7/KSAdSDK.framework/KSAdSDK -> RSAUtil_PubKey(2)
......

搜索结果着实让人吃惊,本以为是我们一时疏忽导致的问题,没想到是高德地图、快手激励视频、电信一键登录、极验等诸多 SDK 都使用了默认的 TAG 值(注:上述结果只表明 SDK 中存在 RSAUtil_PubKey 字符串,是否会引起冲突,需要进一步调试验证。限于 SDK 版本,结果仅供参考),很容易与调用方出现冲突,这也正是本文写作初衷,希望能够引起大家重视。

至此问题出现的根源我们已剖析完成,机智的同学可能已经发现,这两个问题本质上可以归结为同一个问题,都是由于并发导致的数据脏读。多个 SDK 在构造 RSA 公钥数据结构,读取 Keychain 共享资源时都使用了同一个 TAG,相互之间产生影响,不仅自己代码并发会导致错乱,还会与其他 SDK 发生冲突。再进一步分析这个问题,即便每个SDK的TAG值不同,但每个SDK内只要存在同一时间获取不同公钥的逻辑,依然有可能使SDK内部的不同公钥出现冲突。

这里提供一种最简单的解决方案,修改 +[RSA addPublicKey:] 方法的 TAG为随机值,例如使用 UUID,保证每次读写 Keychain 数据时的唯一性,不仅可以避免自己代码并发产生的脏数据,还可以避免与其他 SDK 发生冲突。另外 RSA 加密是一个相对耗时的操作,该方法在每次加密时调用,同时执行钥匙串的删除、添加、读取操作,设计是否合理也有待商榷。

总结

本文分享了 58 iOS App 项目使用 Objective-C-RSA 三方库,遇到的一些坑,详细剖析了问题出 现的原因。 在分析过程中我们发现,此现象并非个例,许多 SDK 都存在同 样的漏洞,或许由于使用场景限制,暂时没有发现潜在的问题,希望能够引起诸位同行重视。
客户端研发过程中,并发问题容易被人忽略,QA 测试环节也很难发现。代码中涉及操作共享数据(内存数据、磁盘数据)时,一定要多考虑线程安全、资源竞争问题,建议开发者自己编写并发测试 case 验证。

在分析定位问题时,我们使用了逆向技术、脚本等多种手段,还分析了上万条错误日志,通过分析数据发现问题,灵活运用工具解决问题,有助于快速定位疑难问题,提升工作效率。

开源项目让我们普通开发者受益匪浅,在此感谢开源作者的无私奉献。与此同时,开源项目也深深影响着每一位用户,想起几年前 Ant Design 圣诞彩蛋事件和最近的 log4j 漏洞,诚可畏也!

在软件开发中,我们不提倡重复造轮子,但在使用轮子的过程中,一定要了解其中构造,知其然知其所以然。死搬硬套,一不小心就会被带到沟里,勿谓言之不预也。
参考资料:

1.(Objective-C-RSA)

2.(is keychain in ios threadsafe?)

3.(Working with Concurrency)
4.(WBBlades - search symbol script)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/589148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何Debug调试Android程序

当开发过程中遇到一些奇怪的bug,但又迟迟定位不出来原因是什么的时候,最好的解决办法就是调试了。调试允许我们逐行地执行代码,并可以实时观察内存中的数据,从而能够比较轻易地查出问题的原因。总结一下使用Android Studio来调试A…

又双叒添新证书:上海斯歌通过ISO9001和ISO27001认证

近期,上海斯歌顺利通过权威机构审查,正式成为ISO9001质量管理体系和ISO27001信息管理安全体系双重认证企业。 可以说ISO9001及ISO27001的认证,既是斯歌坚持管理标准化、程序化、规范化的成果;也是国际标准化组织(ISO&a…

Vue3通透教程【十六】TS自动编译

文章目录 🌟 写在前面🌟 自动编译🌟 编译器的配置文件🌟 写在最后 🌟 写在前面 专栏介绍: 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章,应粉丝要求开始更新 Vue3 的相关技术文章&#xff0…

Apache的配置与应用(构建web、日志分割及AWStats分析系统)

Apache的配置与应用 一、构建虚拟Web主机二、httpd服务支持的三种虚拟机类型1、基于域名的虚拟主机2、基于IP地址的虚拟主机3、基于端口的虚拟主机 三、构建web虚拟目录与用户授权限制1、创建用户认证数据文件2、添加用户授权配置3、验证用户访问权限4、在客户机中浏览器访问 四…

F牌独立站都有哪些收款方式?各有什么优缺点?

最近几个月以来,FP独立站的收款支付问题变得非常焦灼,不少跨境卖家忧心忡忡,害怕自己收不了款血本无归。今天,我跟大家介绍几种FP独立站的收款方式,以及解析他们各有哪些优缺点,方便卖家选择。 一、TT电汇 …

Go GPM 调度器介绍

Go GPM 调度器介绍 1 简介 ​ 这几天在学习Go的GPM机制,于是就整理了一下收集的资料分享给大家,文章末尾有原文链接。主要介绍了Go在运行时调度器的基本实现逻辑和演变过程。 ​ 2 什么是Go调度器 ​ Go调度器很轻量也很简单,足以撑起gorout…

数据结构基础-数组

2.1 数组 概述 定义 在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a collection of elements (values or v…

在 Linux 中启动时自动启动 Docker 容器的 2 种方法

Docker 是一种流行的容器化平台,允许开发人员将应用程序及其依赖项打包成一个独立的容器,以便在不同环境中运行。在 Linux 系统中,我们可以通过配置来实现在系统启动时自动启动 Docker 容器。本文将详细介绍两种方法,以便您了解如…

软件测试面试了一个00后,让我见识到了什么是内卷届的天花板

公司前段缺人,也面了不少测试,结果竟然没有一个合适的。一开始瞄准的就是中级的水准,也没指望来大牛,提供的薪资也不低,面试的人很多,但平均水平很让人失望。令我印象最深的是一个00后测试员,他…

Android bitmap保姆级教学

1. 认识Bitmap Bitmap是一个final类,因此不能被继承。Bitmap只有一个构造方法,且该构造方法是没有任何访问权限修饰符修饰,也就是说该构造方法是friendly,但是谷歌称Bitmap的构造方法是private(私有的)&am…

【C++】this 指针的概念

欢迎来到博主 Apeiron 的博客,祝您旅程愉快 ! 时止则止,时行则行。动静不失其时,其道光明。 目录 1、缘起 2、this 指针的用途 2.1、用途 1 2.1.1 解法 1 2.1.2 解法 2 2.2、用途 2 2.3、用途 3 3、总结 1、缘起 我…

网络编程_TCP/IP四层协议分层

网络编程_TCP/IP四层协议分层 1. OSI七层协议模型 (open system interconnection)与TCP/IP四层协议分层2. 协议封装3. TCP 协议头部4.三次握手5.滑动窗口正常情况丢包情况 6.四次挥手 1. OSI七层协议模型 (open system interconnection)与TCP/IP四层协议分层 OSI七层协议模型 (…

珞珈一号夜间灯光数据校正流程

一、前言 随着珞珈一号夜间灯光数据的发射,其高分辨率等优异性能,可以为我国相关部门监测国内和全球宏观经济运行情况,为政府决策提供客观依据,珞珈一号理想情况下荷在15天内完成绘制全球夜光影像,提供我国或者全球GDP指数、碳排放指数、城市住房空置率指数等专题产品。 …

larvael dcat-admin 表单设置自定义样式

表单有些不是自己想要的样式想要覆写或者增加 可以如下 public function form() {​​​​​​​$this->column(6, function () {$this->dateRange(order_created_at.start, order_created_at.end)->label(下单时间)->setLabelClass([input-group]) // 设置样式-&…

盘点几个实现VLAN间路由的好方法

在真实的网络中,常常需要跨VLAN通信。 许多网络工作者通常选择一些方法来实现不同VLAN中的主机之间的相互访问,如单臂路由。 然而,由于单臂路由技术的一些限制,如带宽和转发效率,这种技术是很少使用。 三层交换机在…

SpringBoot 集成WebSocket详解

感谢参考文章的博主,关于WebSocket概述和使用写的都很详细,这里结合自己的理解,整理了一下。 一、WebSocket概述 1、WebSocket简介 WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器…

一文详解 Sa-Token 中的 SaSession 对象

Sa-Token 是一个轻量级 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。 Gitee 开源地址:https://gitee.com/dromara/sa-token 本文将详细介绍 Sa-Token 中的不同 SaSession 对象的区别&#x…

由jar包冲突导致的logback日志不输出

一、前言 最近升级一个老项目,发面日志没有按照预期的生成。 1、resource下面有logback配置但没有生成日志 检查resource目录下,发现有logback.xml配置,但部署在服务器的项目没有按配置生成日志。于是启动本地tomcat发现日志按logback配置…

【创造一个源点去建图】【有等级限制的dijkstra(采用多次dijk方法处理)】昂贵的聘礼

昂贵的聘礼 题意分析 原题链接 题意分析 本题需要注意: 等级限制比较复杂,可以最后考虑本题说 由 B物品 可以换 A物品,想到了B节点可以走到A节点,所以构建图由于我们是要买一个点再开始换的,所以我们可以构建一个源点…

bird 2023 比赛总结

1. 引言 📌 参加这场比赛的时间,应该是还剩一个月不到了,本来没啥想法,因为在忙一些其它的比赛或者是工作和个人上的烦心事,不过在看过了赛题分析后,整体给我感观是一道挺有意思的学习赛,不仅仅…