最近开发了一个小功能,用于从微信返回的数据中解密获得信息。流程上不太复杂,但因为一开始设计和后续debug过程中出现了一些问题,因此记录一下,以为后人鉴。
按照我的设计,【API】的工作流程是:
- 从微信那里获取
密文
(以下称encryptedData
)、密钥
(以下称iv
)、会话凭证
(以下称session_key
) - 后使用后两者来解开密文,获取到需要的信息
- 根据获取到的信息进行后续处理
这里要解释一下【解密】的工作流程:
- 在腾讯给的解密样例中,直接给出了
encryptedData
、iv
、session_key
的字符串,这可直接通过测试 - 在实际工作过程中,我们只能直接从腾讯服务器得到前两者;至于
session_key
,我们无法直接得到,而通常是先得到一份代码
(以下称code
),然后用code
去向腾讯发起请求、得到一份session_key
;再用通过code
换取到的session_key
结合得到的iv
去解密得到的encryptedData
问题就出在 code
->session_key
这里:
- 在最初的设计中,我设计的解密函数
decrypt
接受的三个参数中,第三个参数是code
,也就是说换取session_key
这件事放到了decrypt
里面 - 而
code
是一次性的——每次登录小程序服务能得到一个code
,但只要用code
去和腾讯换取session_key
后,该code
就失效了——这样的话,原先设计的函数就不方便进行测试 - 于是我试图修改
decrypt
的接口:在decrypt
之外先用code
换取到session_key
后,再用得到的session_key
在decrypt
内部参与解密工作
找理来说这好像也没啥大问题:无非是进行了一次功能分离,让 decrypt
更「专注」了。这里还要继续展开上面的 【解密】工作流中:即获取到 iv
和 session_key
后,并不能直接用于解密,因为腾讯服务器给出的这两个字符串本身经过一次编码处理,我需要对腾讯返回的两样东西进行解码处理后,才能用于解密 encryptedData
。坑就是出在这里了。
在第一次设计时,我的解密函数 decrypt
接口是:
def decrypt(encrypted_data, iv, code)
解密函数内部是用的类似下述语句:
# 在解密函数中用 code 去换取 session_key
session_key = base64.b64decode(get_session_key(code))
iv = base64.b64decode(iv)
encrypted_data = base64.b64decode(encrypted_data)
# 用 session_key, iv, encrypted_data解密……
这套语句是完全copy自腾讯官方给的解密样例 demo。设计完成后(即获取到号码,并将其成功写入了数据库),我开始写单元测试。写测试时我发现了上面所说的 decrypt
的接口不适合测试的问题,然后开始改 decrypt
的接口,改成了
def decrypt(encrypted_data, iv, session_key)
解密函数内部则改成了:
# 直接使用 session_key 去做解密工作,因此不在内部对得到的 session_key 进行处理
iv = base64.b64decode(iv)
encrypted_data = base64.b64decode(encrypted_data)
# 用 session_key, iv, encrypted_data解密……
注意到了第一行:我在修改函数的过程中,把这句 session_key = base64.b64decode(get_session_key(code))
直接就删掉了——而正常工作过程中,应该是改成 session_key = base64.b64decode(session_key)
。但我没有做此处理,于是导致直接用编码过的 session_key
去做解密工作,解密不出正常的内容,得到的东西一直提示不能用 utf-8
进行解码,而我一直都没注意到解码问题在解密函数中就出了错,而是一直在解密函数外部寻找问题,甚至在出错当天对照微信官方 demo 代码也没发现自己这个问题;直到第二天S哥看不下去了,自己也跑通了一遍官方 demo,我在他跑通的官方 demo 和自己的代码上对比了几次,才发现自己忘了 decode session_key
这一事实。
我现在把问题流程写出来,连我都觉得这东西怎么会犯错,本来就应该是用 session_key
去替代掉 get_session_key(code)
的位置。是不是我太粗心了?我觉得想到这一点还不够:「粗心」可能是指「没注意到某些细节」,而为什么我会没有注意到这个细节?是不是我的某些工作流程出了问题,我有没有可能通过改变工作流程来保证我下一次不会再出类似的错?和S、Q、W等人讨论后,我觉得以后可以尝试以下方法:
- 先写接口与文档,明确指出自己传入函数中的东西是什么,返回的是什么。比如在文档中就指出:传入的是编码过的
session_key
之类的 - 在写接口的时候可能就要开始从测试的角度来思考接口和内部工作流,以免后续重构时还要改动接口,造成整个函数内部工作内容改动太大
- 在命名变量时就给出一些提示,这样在使用函数时就会得到提醒。例如函数头应该写成
def decrypt(b64_encrypted_data, b64_iv, b64_session_key)
而非def decrypt(encrypted_data, iv, session_key)
,这样在内部使用接收到的参数时就会明确得到的字符串是需要解码的,即便忘记,后面排查起来也会很快
总结暂时就这些。不知道有没有其他小伙伴遇到过类似的坑,有的话你们是否还有其他思路,或者对上面提出的 3 种解决方案有什么想法?