Swift Python 互通 Json 数据签名

最近很流行自签证书进行 HTTPS 解密,然后就有不少人通过修改苹果的内购回执实现对 App 的破解。实际上验证购买应该是 App 连接服务器,服务器来和苹果的服务器进行通信,然后将结果发送给 App 的——但不少开发者(包括个人开发者以及企业开发者)懒得去专门维护服务器,所以直接用 App 和苹果的服务器进行通信,这就给中间人攻击提供了机会。

苹果官方强调:“不要直接在 App 中验证收据”

苹果官方强调:“不要直接在 App 中验证收据”

中间人攻击,就是说当 A 和 B 进行通信时,C 对 A 假装自己是 B,对 B 假装自己是 A,这样通信过程就从 A <—> B 变成了 A <–> C <–> B, 此时 A 和 B 都还以为对方是可信的,可通信的内容早不知道被 C 改了多少了。

通常来说我们使用 HTTPS 加密传输就已经能够很好的对抗中间人攻击了,但对于这种特殊情况,用户主动去信任一个“不安全”(自己签名)的证书进行 HTTPS 解密,那 HTTPS 加密在客户端就成了不可信的。

这种情况下,我们就需要自己实现对数据的保护。

安全性和完整性

对数据进行保护,主要是两点,“安全性”和“完整性”,前者保护数据不被第三方窥探,后者则保证你的数据在通信的过程当中不会被篡改,当然,通常来讲加密也一定程度保证了数据的完整性,毕竟如果被篡改了,可能也就无法解密了。

但只依靠加密,就给中间人攻击提供了机会。所以,我们要在 HTTPS 的安全性前提下,给自身代码添加完整性验证。

签名

所谓“签名”,和现实中的签名不同,这里的签名是指通过一些特殊的算法,将数据的特征提取出来,算法保证了一旦数据被改变,哪怕是一个符号,那么提取的特征就完全不同了,这样,我们只要在发送数据时包含这个特征,在收到后重新验证它,就能确保数据在传输过程中没有被篡改。

重签名

有一个问题就是如果中间人在改了数据之后重新签名,我们假定选择的特征提取算法被泄露了,中间人在更改了数据之后使用相同的算法重新签名(也就是提取特征),那客户端依然会认为数据是完整的——所以我们要在客户端和服务端约定一个“盐”值,你也可以理解为密码。

它不能让你把数据从特征值里恢复出来(都说了是提取特征,并不是加密,这个过程是不可逆的),但双方约定了密码之后在签名时将这个“密码”追加或者前置在数据中,这样一旦中间人更改了数据,他即使可以使用相同的算法重新签名,由于不知道我们预先设置的密码,他签出来的结果还是不同,这样客户端就知道这个数据是被篡改过的了。

重放攻击

既然无法对数据进行修改,那么骇客还可以抓取原本生效的数据报,重新再来发送,这样服务器或者客户端验证了签名和加密,数据完全正确,但实际上却是来自另一个版本客户端的传输数据——要应对这个情况,就要在之前数据结构的基础上,再增加一个概念——“时间戳”,通常我们使用 utc 的时间戳,其实就是从格林威治时间1970年01月01日到现在的总计秒数——当然你也可以用其他的能精确到秒(甚至毫秒)的时间格式——把时间戳也追加在数据里一起进行签名,避免时间戳被篡改,然后客户端和服务端进行通信时,检查时间戳是否在允许范围内,比如1秒内,或者30秒内等等,如果间隔太久,即时签名验证通过,也不能信任这个数据了,这样,就避免了重放攻击。

代码

签名的算法有很多,比如常见的 MD5、SHA1 等等等等,这些算法都可以直接拿来签名使用,这里我使用了 RSA 证书签名,实际上就是用更长更复杂的密码。

我们把要传输的数据和时间戳放在一起生成一个字符串,然后对字符串进行 SHA256 提取特征,然后将特征用 RSA 证书私钥签名;

等到客户端收到数据,就将数据和时间戳用相同的方式生成字符串,进行 SHA256 提取特征,然后用公钥进行签名验证。

这样公钥私钥的配对,好处在于,不像约定密码那样容易泄露——毕竟密码就写在程序中,很容易被找到——公钥可以随意传播,它的作用仅仅是验证签名而不能对数据进行签名,无需担心泄露问题。

生成密钥

打开任意你喜欢的终端,输入命令 openssl ,然后再输入命令  genrsa -out private.pem 1024 来生成私钥,后边的数字越大就越安全,但你也要权衡签名的时间成本,通常来说,1024 足够了。

最后输入命令 rsa -in private.pem -pubout -out public.pem 生成公钥,这个公钥就是你要放在客户端中分发到最终用户手中的密钥了。(使用命令 exit 来退出 openssl)

实际上,生成的密钥文件其实就是一个纯文本文档,密钥本质上是二进制的,但它们都被 base64 编码成文本保存。

使用命令 cat private.pem 来查看你的私钥:

使用命令 cat public.pem 来查看公钥:

Python

 

为了方便使用,我们直接把上面私钥的代码复制,用一个变量来保存这个私钥:

然后是签名代码:

这里 sign() 直接传入字符串即可,它会将你传入的字符串转换成 UTF-8 编码的二进制数据进行 SHA256 摘要,然后将摘要进行签名,最终将签名结果转换为 base64 编码的字符串输出。

Swift

这里我们同样,将公钥保存到变量中,注意只复制 base64 编码部分,不要开头和结尾的 -----BEGIN PUBLIC KEY----- 和 -----END PUBLIC KEY----- :

上文中输出的密钥都是一行一行的,这里我们将它们无缝拼接在一起,形成一个长串。

记得引用框架 import CommonCrypto

然后我们写一个函数来生成二进制公钥:

最终来对数据进行签名验证:

只要传入被签名的字符串,再传入签名的 base64 编码,函数就会自动对字符串进行 SHA256 摘要,然后签名恢复成二进制数据,最后通过系统内置算法,使用你的公钥,对摘要和签名进行验证,验证通过,返回 true 。

后记

使用不同的代码在不同的平台进行互通是一件很困难的事情,简单的加密算法和方式往往会在不同的平台得出不一样的结果,同一个语言,在同一个平台,是最容易的,但不同的语言就很复杂,具体实现时一定要注意这个问题。在尝试了无数种方式后,最终我还是使用 RSA 签名实现了跨语言平台互通,希望这篇文章对你有用。

参考文献

本文由 落格博客 原创撰写:落格博客 » Swift Python 互通 Json 数据签名

转载请保留出处和原文链接:https://www.logcg.com/archives/3318.html

About the Author

R0uter

如非声明,本人所著文章均为原创手打,转载请注明本页面链接和我的名字。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注