如何使用Postman优雅地进行接口自动加密与解密

引言

在上一篇文章中,分享了 Requests 自动加解密的方法,本篇文章分享一下更加方便的调试某个服务端接口。

Postman

Postman 这个工具后端小伙伴应该相当熟悉了,一般情况下我们会在开发和逆向过程中使用它来快速向接口发送请求,通过接口返回值来判断传入的接口参数是否正确。

在开发或者逆向过程中,有的时候接口的参数和返回值是加密的,有的时候还要先调用接口申请一个临时的 token 才能正常发送后续请求,这个时候我们要么就是用 Python 写一小段代码来验证接口逻辑是否正确,要么就是手动加密后再使用 Postman 发送请求,效率比较低,体验不太好。

这个时候,Postman 的自动加解密功能就派上用场了,我们可以使用 Postman 的脚本功能来自动实现这一功能。Postman 的脚本功能在很久之前就提供了,但是大家用的还比较少,我这里分享两个例子供大家参考,大家在用到的时候可以根据我的例子进行修改以实现自己的逻辑。

Pre-request

Pre-request 功能可以在 Postman 发送请求之前提前做一些事情,例如获取 token,计算签名,加密数据等等。

Post-request

Post-request 功能可以在 Postman发送请求收到响应后做一些事情,例如解密数据,验证签名等等。

测试服务端

为了方便验证 Postman 两个脚本的功能,我这里写了一个简单的服务端。只是 demo 代码,请大家不要吐槽哈

python 复制代码
import base64
import json
import time
import uuid
from hashlib import md5

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from flask import Flask, request, Response

app = Flask(__name__)

# 别问我为啥不清缓存,问就是忘了
token_cache = {}


def get_nonce():
    return uuid.uuid4().__str__()


def encrypt(k, data, mode=AES.MODE_ECB):
    aes = AES.new(k.encode(), mode)
    result = aes.encrypt(pad(data.encode(), block_size=16))
    return base64.b64encode(result).decode()


def decrypt(k, data, mode=AES.MODE_ECB):
    aes = AES.new(k.encode(), mode)
    result = aes.decrypt(base64.b64decode(data))
    return unpad(result, block_size=16).decode()


def get_headers():
    return {
        'Content-Type': 'application/json',
        'x-encrypt': 'true',
        'nonce': get_nonce()
    }


@app.get('/get_token')
def get_token():
    token = str(uuid.uuid4())
    token_cache[token] = int(time.time())
    return {'token': token}


def check_token(token):
    if token in token_cache and int(time.time()) - token_cache[token] <= 5:
        return True
    else:
        return False


@app.post('/')
def index():
    if not check_token(request.headers.get('x-token')):
        return Response('token expired!', 401)
    data = request.json
    key = request.headers.get('x-key')
    print(f"get timestamp is :{request.headers.get('x-timestamp')}")
    print(f"get sign is :{request.headers.get('x-sign')}")
    print(f"get key is :{key}")
    print(f'encrypt json data is:{data}')
    decrypt_data = decrypt(key, data['data'])
    print(f'decrypt json data is:{decrypt_data}')
    assert md5(request.headers.get('x-timestamp').encode()).hexdigest() == request.headers.get('x-sign')
    headers = get_headers()
    data = {'data': str(json.loads(decrypt_data)['data'] * 2) + (request.headers.get('x-sign') or '')}
    print(f'before encrypt data is:{data}')
    encrypt_key = headers.get('nonce').replace('-', '')[:16]
    print(f'encrypt key is :{encrypt_key}')
    d = encrypt(encrypt_key, json.dumps(data))
    print(f'after encrypt data is:{d}')
    return Response(json.dumps({'data': d}), headers=headers, mimetype='application/json')


if __name__ == '__main__':
    app.run(debug=True, port=8888)

脚本具有以下功能:

  • 每个请求都要带有一个 token,可以通过 get_token 接口获取 token 值,token 过期时间 5 秒
  • 请求体是加密的,要先解密才能获取原始数据
  • 每个请求都带有 sign 值,获取到请求后要先验证 sign 值是否正确
  • 请求加密密钥在 x-key 请求头上,使用字符串去掉-后的前 16 位来解密数据,相反的,客户端使用同样的方式来加密
  • 处理数据,这里为了演示,只是将收到的数据复制了一份并且加上请求的 x-sign 值然后返回给客户端
  • 数据通过同样的方式进行加密,加密密钥在 nonce 响应头中

Pre-request 脚本

先看发送前的脚本:

js 复制代码
const CryptoJS = require('crypto-js');
const { v4: uuidv4 } = require('uuid');
let token='';
try {
    const response = await pm.sendRequest({
        url: "http://127.0.0.1:8888/get_token",
        method: "GET"
    });
    console.log(response.json().token);
   token=response.json().token;
} catch (err) {
    console.error(err);
}
var timestamp = Math.floor(Date.now() / 1000);
const bodyObject = JSON.parse(pm.request.body.raw);
console.log(timestamp);
const uuid = uuidv4();
console.log(uuid);
const uuid_key=uuid.replace(/-/g,'').substring(0,16);
console.log(uuid_key);
pm.request.headers.add({
    "key": "x-token",
    "value": token
});
pm.request.headers.add({
    "key": "x-timestamp",
    "value": timestamp.toString()
});
pm.request.headers.add({
    "key":"x-sign",
    "value": CryptoJS.MD5(timestamp.toString()).toString()
})
pm.request.headers.add({
    "key":"x-key",
    "value": uuid_key
})
const key = CryptoJS.enc.Utf8.parse(uuid_key);

const encryptedData = CryptoJS.AES.encrypt(JSON.stringify(bodyObject), key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
}).toString();
console.log(encryptedData);
const newBody = {
    "data": encryptedData
};

pm.request.body.raw = JSON.stringify(newBody);

Pre 脚本具有以下逻辑:

  • 先访问 get_token 接口获取临时的 token
  • 获取时间戳,解析请求的 body
  • 获取 uuid 按照正常的规则生成密钥
  • token,时间戳,signkey 字段设置到请求头上
  • 加密数据,然后替换原始的请求 body

Post-request 脚本

来看收到响应后的脚本:

js 复制代码
const CryptoJS = require('crypto-js');
pm.test("Status code is 200", function () {
  pm.response.to.have.status(200);
});
const bodyObject = pm.response.json();
const encryptedData=bodyObject['data']
console.log(encryptedData)

const nonce=pm.response.headers.get('nonce');
console.log(nonce);
const key=nonce.replace(/-/g,'').substring(0,16)
console.log(key);

const decryptedData = CryptoJS.AES.decrypt(encryptedData, 
CryptoJS.enc.Utf8.parse(key), {
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7
    }).toString(CryptoJS.enc.Utf8);

console.log(decryptedData);

var template = `
        {{response}}  
`;

// Set visualizer
pm.visualizer.set(template, {
    // Pass the response body parsed as JSON as `data`
    response:decryptedData
});

Post 脚本具有以下逻辑:

  • 先通过 Postman 自带的test 方法判断状态码是否是 200
  • 获取加密后的 data 数据,获取请求头中的 nonce 字段,计算出密钥
  • 使用计算出来的密钥解密上面获取到的加密数据,得到原始数据
  • 使用 Postman 自带的 visualizer 方法来展示解密后的数据

以上两个脚本大家可以当做参考,这里只是作为一个示例。

请求演示

先看效果,请求的时候 body 是明文的,请求后获取到的响应是加密的,scripts 就是脚本的位置,右边还有一些代码片段可以供参考, 脚本的功能还有很多,例如获取环境变量,设置全局变量,发送请求等等,大家可以自行选择。可以在 Console 界面查看脚本的日志。

那在哪里查看解密后的数据呢,在 Visualize 中。平时大家基本上都是在 Pretty 中查看响应,响应可以被自动格式化,但是目前为止,我还没查到如何能修改 Postman 的响应,只能通过 Visualize 的方式将解密后的响应展示出来,如果有知道的小伙伴可以留言评论。

抓包验证

从抓包的结果来看,数据在请求和响应中都是加密的状态,但是在 Postman 中可以实时看到解密后的响应。

总结

以上就是通过 Postman 脚本来自动加解密的方法,有需要的小伙伴可以自行修改代码以提升接口调试的效率。

相关推荐
萧萧玉树42 分钟前
分布式在线评测系统
前端·c++·后端·负载均衡
桃园码工1 小时前
第一章:Go 语言概述 2.安装和配置 Go 开发环境 --Go 语言轻松入门
开发语言·后端·golang
hummhumm2 小时前
第 36 章 - Go语言 服务网格
java·运维·前端·后端·python·golang·java-ee
凡人的AI工具箱2 小时前
40分钟学 Go 语言高并发:Pipeline模式(一)
开发语言·后端·缓存·架构·golang
南鸳6102 小时前
Scala:根据身份证号码,输出这个人的籍贯
开发语言·后端·scala
小扳3 小时前
微服务篇-深入了解使用 RestTemplate 远程调用、Nacos 注册中心基本原理与使用、OpenFeign 的基本使用
java·运维·分布式·后端·spring·微服务·架构
ᝰꫝꪉꪯꫀ3613 小时前
JavaWeb——SpringBoot原理
java·开发语言·后端·springboot
LightOfNight3 小时前
Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)
服务器·数据库·redis·分布式·后端·缓存·中间件
刽子手发艺3 小时前
云服务器部署springboot项目、云服务器配置JDK、Tomcat
java·后端·部署