如何使用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 脚本来自动加解密的方法,有需要的小伙伴可以自行修改代码以提升接口调试的效率。

相关推荐
SomeB1oody7 小时前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
Profile排查笔记8 小时前
指纹浏览器哪个好?从 Profile、代理、权限和自动化能力判断是否适合
前端·人工智能·后端·自动化
用户938515635078 小时前
用 AI 结对编程从 0 搭一个"单词后台管理系统":Next.js + Supabase + Drizzle + shadcn/ui 全记录
后端·postgresql·next.js
爱学习的小邓同学9 小时前
Golang --- (1)第一个Golang程序
开发语言·后端·golang
小灰灰搞电子10 小时前
Rust+Slint 实现的“DNA双螺旋”加载动画源码分享
后端·rust·slint·加载动画
面向Google编程10 小时前
向量库不再囤数据:Milvus 3.0 零拷贝直读数据湖
后端
IT_陈寒11 小时前
Redis内存警告竟是因为这个不起眼的配置项
前端·人工智能·后端
Python私教12 小时前
AI 漫剧角色一进分镜就变脸?把提示词升级成“角色 ID + 镜头合同”
后端
Python私教12 小时前
一次生成 20 个角色却全都撞脸:我用“角色合同”重做了批量生成流程
后端
用户5944041035612 小时前
Go 语言高性能 Web 服务开发:基于 Gin + GORM + Redis 构建 RESTful API
后端