# SaaS版本API
# 一. 交付方式
邮箱画像部署在云端,通过HTTPS的RESTful接口查询使用。每个注册客户会分配一个snuser和snkey。(在控制台-配置 (opens new window)页面可以查看)
邮箱画像服务端对查询请求实行IP白名单机制,只有白名单中IP地址允许查询。支持添加IP段,如:“127.0.0.1/16 ”。(配置管理地址控制台-配置 (opens new window))
# 二. 接口说明
# 2.1请求详情
# 2.1.1 请求参数说明
请求参数 | 说明 |
---|---|
请求方法 | POST |
请求地址 | https://black-mailbox.yazx.com/v2/api/check/mailbox |
并发 | 200 |
QPS | 1000 |
非深度引擎延时情况 | 100ms以内 |
深度引擎延时情况 | 400ms以内 |
# 2.1.2 请求说明
请求体
参数名称 | 参数类型 | 是否必传 | 参数说明 |
---|---|---|---|
snuser | string | 是 | 用户识别id(配置管理页面中可以查看) |
data | string | 是 | 由AES的CFB加密的json数据,密钥是SNKEY |
data内容如下
参数名称 | 参数类型 | 是否必传 | 参数说明 |
---|---|---|---|
string | 是 | 邮箱明文,可传完整邮箱或者是只传邮箱后缀 | |
open_depth_engine | bool | 否 | 是否开启深度引擎匹配模式,开启此模式可增加邮箱识别率,但会增加查询延迟。默认值为true。 |
# 2.2 返回详情
# 2.2.1 返回参数说明
参数名称 | 参数类型 | 是否必传 | 参数说明 |
---|---|---|---|
snuser | string | 是 | 用户识别id |
status | int | 是 | 状态码 |
data | string | 是 | 由AES的CFB加密的json数据,密钥是SNKEY |
errmsg | string | 是 | 对应状态码返回提示信息 |
data内容如下
参数名称 | 参数类型 | 是否必传 | 参数说明 |
---|---|---|---|
string | 是 | 查询的完整邮箱或邮箱后缀 | |
type | int | 是 | 邮箱类型 0 未知邮箱:未能识别该邮箱类型 1 公共邮箱:指在邮箱门户网站注册的邮箱,使用者多为正常用户。 2 临时邮箱:指无需注册登录且只有几分钟到几小时不等有效性即可收发邮件的邮箱,使用者多为恶意用户。 3 企业邮箱:指通过企业认证的邮箱 ,使用者为企业在职员工。 4 校园邮箱:指通过校园认证的邮箱 ,使用者为学校的校园师生。 5 无效邮箱:指不能收发邮件的邮箱。 6 自建邮箱:指私人搭建邮件服务器,所有者可以随意增加或更换邮箱账号。 |
risk_info | json_object | 是 |
risk_info内容如下
参数名称 | 参数类型 | 是否必传 | 参数说明 |
---|---|---|---|
risk_level | int | 是 | 0:无风险,该邮箱未命中邮箱黑名单库且邮箱类型非临时邮箱 1:有风险,该邮箱命中邮箱黑名单库或邮箱类型为临时邮箱 |
risk_tag | string | 是 | 恶意邮箱:该邮箱明确是黑产持有的邮箱 临时邮箱:该邮箱的类型为临时邮箱,常被恶意用户使用 |
# 2.3 错误码
错误码表
Code | 说明 |
---|---|
200 | 正常状态 |
501 | 请求参数错误(请检查请求参数以及aes加密实现) |
502 | 错误的请求类型 |
503 | 无权限 |
504 | 账号额度不足 |
505 | 当前请求IP不在白名单中 (配置管理地址控制台-配置 (opens new window)) |
506 | 未开通服务 |
507 | 账号不可用 |
508 | 账号已过期 |
511 | 请求的json格式错误 |
# 三. 使用建议
3.1 临时邮箱:risk_info.risk_level返回“1”且risk_info.risk_tag返回“临时邮箱”
由于临时邮箱具有一次性、用完即毁的特点,决定了使用该类邮箱的用户绝非对业务有价值的用户。 同时由于临时邮箱的创建成本极其低廉,黑灰产很容易就能批量创建大量临时邮箱账号从而进行垃圾注册 。
3.2 命中邮箱黑名单:risk_info.risk_level返回“1”且risk_info.risk_tag返回“恶意邮箱”
该类邮箱是我们明确发现在被黑产持有,而非被自然人所持有的电子邮箱,产生的误判概率极低。
综上所述,对risk_info.risk_level返回值为“1”的用户可以考虑在单一策略下直接拦截。对risk_info.risk_level返回值为“0”的用户可以使用邮箱类型补充用户的画像。
# 四. 代码示例
# python
# -*- encoding: utf-8 -*-
"""
python 版本
# 3.7及以上
# install requirement
pip install requests
pip install pycryptodome
"""
import requests
import json
import base64
from Crypto.Cipher import AES
from Crypto import Random
POST_URL = "https://black-mailbox.yazx.com/v2/api/check/mailbox"
SNUSER = "*****"
SNKEY = "*****"
def encrypt(encrypt_str: str, cecret: str):
"""
aes加密数据后,再进行base54编码后返回
:param encrypt_str:
:param cecret:
:return:
"""
remainder = len(encrypt_str) % AES.block_size
if remainder:
padded_value = encrypt_str + '\0' * (AES.block_size - remainder)
else:
padded_value = encrypt_str
# a random 16 byte key
iv = Random.new().read(AES.block_size)
# CFB mode
cipher = AES.new(bytes(cecret, encoding="utf-8"), AES.MODE_CFB, iv, segment_size=128)
# drop the padded value(phone number length is short the 16bytes)
value = cipher.encrypt(bytes(padded_value, encoding="utf8")[:len(encrypt_str)])
ciphertext = iv + value
return str(base64.encodebytes(ciphertext).strip(), encoding="utf8")
def decrypt(encrypt_str: str, cecret: str):
"""
base64解码后,再进行aes解密
:param encrypt_str:
:param cecret:
:return:
"""
data = base64.decodebytes(bytes(encrypt_str, encoding="utf8"))
cihpertxt = data[AES.block_size:]
remainder = len(cihpertxt) % AES.block_size
if remainder:
padded_value = cihpertxt + b'\0' * (AES.block_size - remainder)
else:
padded_value = cihpertxt
cryptor = AES.new(bytes(cecret, encoding="utf-8"), AES.MODE_CFB, data[0:AES.block_size], segment_size=128)
plain_text = cryptor.decrypt(padded_value)
return str(plain_text[0:len(cihpertxt)], encoding="utf8")
def TestCheckMailBox(email: str, open_depth_engine: bool):
data = {
"email": email,
"open_depth_engine": open_depth_engine
}
# user list origin text
pstr = json.dumps(data)
print("====>request plain<====")
print(pstr)
# encrypt the origin text
cstr = encrypt(pstr, SNKEY)
payload = {
"snuser":SNUSER,
"data":cstr
}
print("====>request body<====")
print(payload)
r = requests.post(POST_URL, data=json.dumps(payload), verify=True)
rjson = json.loads(r.text)
print("====>response body<====")
print(rjson)
if rjson["status"] == 200:
print("====>response plain<====")
print(decrypt(rjson["data"], SNKEY))
else:
print("if status is 503, please check you user, snkey, and if ip in white list")
print("如果返回是503,请确认你的用户,密钥是否正确,以及ip是否在白名单内")
if __name__ == "__main__":
email = "beilf1gx@truthfinderlogin.com"
open_depth_engine = True
TestCheckMailBox(email, open_depth_engine)
# java
import java.util.Base64;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import net.sf.json.JSONObject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.RequestBody;
import okhttp3.MediaType;
public class Main {
private static String URL = "https://black-mailbox.yazx.com/v2/api/check/mailbox";
private static String SNUSER = "*****";
private static String SNKEY = "*****";
private static byte[] encryptAES(String phoneno) {
try {
SecretKeySpec key = new SecretKeySpec(SNKEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
byte[] phonenobyteContent = phoneno.getBytes();
byte[] ivbytes = new byte[16];
Random rand = new Random();
rand.nextBytes(ivbytes);
IvParameterSpec ivSpec = new IvParameterSpec(ivbytes);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec );
byte[] result = cipher.doFinal(phonenobyteContent);
byte[] finalbytes = new byte[ivbytes.length + result.length];
System.arraycopy(ivbytes, 0, finalbytes, 0, ivbytes.length);
System.arraycopy(result, 0, finalbytes, ivbytes.length, result.length);
return finalbytes;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static byte[] decryptAES(String phoneno) {
try {
byte[] asBytes = Base64.getDecoder().decode(phoneno.toString().replace("\n", ""));
byte[] iv = new byte[16];
byte[] encryptBytes = new byte[asBytes.length - iv.length];
System.arraycopy(asBytes, 0, iv, 0, iv.length);
System.arraycopy(asBytes, iv.length, encryptBytes, 0, encryptBytes.length);
SecretKeySpec key = new SecretKeySpec(SNKEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivspec);
byte[] result = cipher.doFinal(encryptBytes);
return result;
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static void TestCheckMailBox(String email, boolean openDepthEngine) {
JSONObject jsObject = new JSONObject();
jsObject.put("email", email);
jsObject.put("open_depth_engine", openDepthEngine);
System.out.println("request plain =>" + jsObject);
String encoded = Base64.getEncoder().encodeToString(encryptAES(jsObject.toString()));
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType,
"{\"snuser\": \"" + SNUSER + "\", \"data\": \"" + encoded + "\"}");
Request request = new Request.Builder()
.url(URL)
.post(body)
.addHeader("content-type", "application/json")
.build();
try (Response response = client.newCall(request).execute()) {
String jsonData = response.body().string();;
System.out.println("response plain =>" + jsonData);
JSONObject object = JSONObject.fromObject(jsonData);
String encryptData = object.getString("data");
byte[] deDataBytes = decryptAES(encryptData);
System.out.println("result => success" + new String(deDataBytes, "utf-8"));
} catch (Exception e) {
System.out.println("result => failed");
e.printStackTrace();
}
}
public static void main(String[] args) {
String email = "beilf1gx@truthfinderlogin.com";
boolean openDepthEngine = true;
TestCheckMailBox(email, openDepthEngine);
}
}
# go
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
mrand "math/rand"
"net/http"
"strings"
"time"
)
const (
c_url = "https://black-mailbox.yazx.com/v2/api/check/mailbox"
c_snuser = "*****"
c_snkey = "*****"
)
type req struct {
Email string `json:"email"`
OpenDepthEngine bool `json:"open_depth_engine"`
}
type resp struct {
SnUser string `json:"snuser"`
Status int `json:"status"`
Data string `json:"data"`
ErrMsg string `json:"errmsg"`
}
func AESDecrypt(ciphertxt string, keystr string) string {
// prepare cipher text
cipherbyte, _ := base64.StdEncoding.DecodeString(ciphertxt)
// prepare the key
block, _ := aes.NewCipher([]byte(keystr))
if len(cipherbyte) < aes.BlockSize {
return ""
}
// split iv and ciphertext
iv := cipherbyte[:aes.BlockSize]
cipherbyte = cipherbyte[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
// XORKeyStream can work in-place if the two arguments are the same.
stream.XORKeyStream(cipherbyte, cipherbyte)
return string(cipherbyte)
}
func AESEncrypt(plaintxt string, keystr string) string {
block, _ := aes.NewCipher([]byte(keystr))
ciphertext := make([]byte, aes.BlockSize+len(plaintxt))
iv := ciphertext[:aes.BlockSize]
io.ReadFull(rand.Reader, iv)
randobj := mrand.New(mrand.NewSource(time.Now().UnixNano()))
randobj.Read(iv)
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plaintxt))
return base64.StdEncoding.EncodeToString([]byte(ciphertext))
}
func TestCheckMailBox(email string, openDepthEngine bool) {
reqData := req{
Email: email,
OpenDepthEngine: openDepthEngine,
}
jsData, _ := json.Marshal(reqData)
encryptData := AESEncrypt(string(jsData), c_snkey)
payload := strings.NewReader("{\"snuser\": \"" + c_snuser + "\", \"data\": \"" + encryptData + "\"}")
req, _ := http.NewRequest("POST", c_url, payload)
req.Header.Add("content-type", "application/json")
req.Header.Add("cache-control", "no-cache")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
var response resp
if err := json.Unmarshal(body, &response); err != nil {
panic(err)
}
if response.Status != 200 {
fmt.Println("get status failed")
} else {
fmt.Println("get status success =>>>")
fmt.Println(AESDecrypt(response.Data, c_snkey))
}
}
func main() {
var (
email = "beilf1gx@truthfinderlogin.com"
openDepthEngine = true
)
TestCheckMailBox(email, openDepthEngine)
}