temp
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
.env
|
||||||
3
packages/decrypt-phone-by-data/.env.example
Normal file
3
packages/decrypt-phone-by-data/.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
APP_ID=68c47321131b9b0001166b7d
|
||||||
|
APP_SECRET=a9f02a4bef2e29b2b6f866d9d2aff2e6
|
||||||
|
CODE=ecd82af4267247b1a1a59b6df7fdd320
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"encryptedData": "NpiIHuKkhY2S8pCG9WShtDcCLmBhnKkU+m0BLUHJKWkUFrkPay6j3Lc7wkzKat8U/gWsP2WfcMHS6nz48VvjyyKAeXrAxQKEFIxLKslRqntEJnvu5HmANssOypHHk7Y7ovij1QvY+Od/pTBKL73i2AzLoNtXubMTWSPcqG0A/Ov1uy4U7zRZpUIa5otQ+o5dqHc4+rj5EnT2MQ73+QGcFA==",
|
|
||||||
"errMsg": "getPhoneNumber:ok",
|
|
||||||
"iv": "aUFhbEhJbWFydjVNQlE4cg=="
|
|
||||||
}
|
|
||||||
6
packages/decrypt-phone-by-data/demo.ts
Normal file
6
packages/decrypt-phone-by-data/demo.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const demoData = {
|
||||||
|
encryptedData:
|
||||||
|
'HdZv6GsUqB68aoLf+SqSaGSqffOTMHHW89DTjh7suCIz9gsdK0wUTpL8+60+zqKcL3b1jNxrRdmZxXGHO67eH1HG5WSoJU7Jlu1WjUWzK9ZhBuPJNMP7Z0aHx9aPOyk1grlZNHOA51/i+AlKywm9QY3RLRTtPQpoSTcjJud1iWhF+aYBrI8pS/rw/AYQuRvOuODjQXinyAC6pJPqZzfs5w==',
|
||||||
|
errMsg: 'getPhoneNumber:ok',
|
||||||
|
iv: 'N2dPdU9wT0k3bnNKZlhsZw==',
|
||||||
|
};
|
||||||
@@ -10,5 +10,10 @@
|
|||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"packageManager": "pnpm@10.14.0",
|
"packageManager": "pnpm@10.14.0",
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.3.3",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"dotenv": "^17.2.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
packages/decrypt-phone-by-data/src/decrypt.ts
Normal file
62
packages/decrypt-phone-by-data/src/decrypt.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
// 解密数据
|
||||||
|
// sessionKey: 微信/小红书登录后获取的 session_key
|
||||||
|
// encryptedData: 小程序通过 wx.getPhoneNumber 获取的 encryptedData
|
||||||
|
// iv: 小程序通过 wx.getPhoneNumber 获取的 iv
|
||||||
|
|
||||||
|
// @links
|
||||||
|
// https://miniapp.xiaohongshu.com/doc/DC591932 // 小红书小程序解密数据
|
||||||
|
// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html // 微信小程序解密数据
|
||||||
|
|
||||||
|
export class BizDataCrypt {
|
||||||
|
private appId: string;
|
||||||
|
private sessionKey: string;
|
||||||
|
|
||||||
|
constructor(appId: string, sessionKey: string) {
|
||||||
|
this.appId = appId;
|
||||||
|
this.sessionKey = sessionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptData(encryptedData: string, iv: string) {
|
||||||
|
// base64 decode
|
||||||
|
const sessionKeyWA = CryptoJS.enc.Base64.parse(this.sessionKey);
|
||||||
|
const encryptedDataWA = CryptoJS.enc.Base64.parse(encryptedData);
|
||||||
|
const ivWA = CryptoJS.enc.Base64.parse(iv);
|
||||||
|
|
||||||
|
// 确保 AES-128 使用 16 字节 key(微信/小红书 session_key base64 解码应为 16 字节)
|
||||||
|
let keyWA = sessionKeyWA;
|
||||||
|
if (sessionKeyWA.sigBytes !== 16) {
|
||||||
|
if (sessionKeyWA.sigBytes < 16) {
|
||||||
|
throw new Error('Invalid session_key length (<16 bytes)');
|
||||||
|
}
|
||||||
|
// 如果大于 16 字节,截取前 16 字节
|
||||||
|
keyWA = CryptoJS.lib.WordArray.create(sessionKeyWA.words.slice(0, 4), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 解密
|
||||||
|
const cipherParams = CryptoJS.lib.CipherParams.create({
|
||||||
|
ciphertext: encryptedDataWA,
|
||||||
|
});
|
||||||
|
const decipher = CryptoJS.AES.decrypt(cipherParams, keyWA, {
|
||||||
|
iv: ivWA,
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
|
||||||
|
const decoded = decipher.toString(CryptoJS.enc.Utf8);
|
||||||
|
if (!decoded) throw new Error('Decryption produced empty result');
|
||||||
|
|
||||||
|
const decodedData = JSON.parse(decoded);
|
||||||
|
const waterAppId = decodedData?.watermark?.appid || decodedData?.watermark?.appId;
|
||||||
|
if (!decodedData || !decodedData.watermark || waterAppId !== this.appId) {
|
||||||
|
throw new Error('Invalid appId in decrypted data');
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedData;
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error('Decrypt failed' + (err?.message ? ': ' + err.message : ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
packages/decrypt-phone-by-data/src/get-session-key.ts
Normal file
45
packages/decrypt-phone-by-data/src/get-session-key.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const testBASEURL = 'miniapp-sandbox.xiaohongshu.com';
|
||||||
|
const prodBASEURL = 'miniapp.xiaohongshu.com';
|
||||||
|
const baseURL = 'https://miniapp.xiaohongshu.com/api/rmp/session?app_id=${app_id}&access_token=${access_token}&code=${code}'.replace(prodBASEURL, testBASEURL);
|
||||||
|
const accessTokenURL = 'https://miniapp.xiaohongshu.com/api/rmp/token'.replace(prodBASEURL, testBASEURL);
|
||||||
|
const getAccessToken = async (appId: string, appSecret: string) => {
|
||||||
|
const response = await fetch(accessTokenURL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
appid: appId,
|
||||||
|
secret: appSecret,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
console.log('Access token response status:', appId, appSecret);
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Access token response data:', data);
|
||||||
|
if (data.err_no !== 0) {
|
||||||
|
throw new Error(`Error getting access token: ${data.err_msg}`);
|
||||||
|
}
|
||||||
|
console.log('Access token data:', data);
|
||||||
|
return data.access_token;
|
||||||
|
};
|
||||||
|
export const getSessionKey = async (appId: string, accessToken: string, code: string) => {
|
||||||
|
const url = baseURL.replace('${app_id}', appId).replace('${access_token}', accessToken).replace('${code}', code);
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Session key data:', data);
|
||||||
|
if (data.err_no !== 0) {
|
||||||
|
throw new Error(`Error getting session key: ${data.err_msg}`);
|
||||||
|
}
|
||||||
|
return data.session_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
const appId = process.env.APP_ID || 'your_app_id';
|
||||||
|
const appSecret = process.env.APP_SECRET || 'your_app_secret';
|
||||||
|
const authorization_code = process.env.CODE || 'your_code';
|
||||||
|
// const accessToken = await getAccessToken(appId, appSecret);
|
||||||
|
const accessToken = 'c525e1c9125c429e83b699acd00ac858';
|
||||||
|
getSessionKey(appId, accessToken, authorization_code);
|
||||||
19
packages/decrypt-phone-by-data/src/index.ts
Normal file
19
packages/decrypt-phone-by-data/src/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { demoData } from './../demo';
|
||||||
|
import { BizDataCrypt } from './decrypt';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const decryptPhoneNumber = (e: any, config?: { appId: string; sessionKey: string }) => {
|
||||||
|
const { encryptedData, iv } = e.detail;
|
||||||
|
console.log('Config:', config);
|
||||||
|
const appId = config?.appId || 'your_app_id';
|
||||||
|
const sessionKey = config?.sessionKey || 'your_session_key';
|
||||||
|
const wxBizDataCrypt = new BizDataCrypt(appId, sessionKey);
|
||||||
|
const decryptedData = wxBizDataCrypt.decryptData(encryptedData, iv);
|
||||||
|
|
||||||
|
console.log('Decrypted data:', decryptedData);
|
||||||
|
};
|
||||||
|
const appId = process.env.APP_ID;
|
||||||
|
const sessionKey = 'TjJLWmxUaGdDMmFZYVE2eQ==';
|
||||||
|
|
||||||
|
decryptPhoneNumber({ detail: demoData }, { appId, sessionKey });
|
||||||
48
pnpm-lock.yaml
generated
Normal file
48
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.: {}
|
||||||
|
|
||||||
|
packages/decrypt-phone-by-data:
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^24.3.3
|
||||||
|
version: 24.3.3
|
||||||
|
crypto-js:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
|
dotenv:
|
||||||
|
specifier: ^17.2.2
|
||||||
|
version: 17.2.2
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@types/node@24.3.3':
|
||||||
|
resolution: {integrity: sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==}
|
||||||
|
|
||||||
|
crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
|
||||||
|
dotenv@17.2.2:
|
||||||
|
resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
undici-types@7.10.0:
|
||||||
|
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@types/node@24.3.3':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 7.10.0
|
||||||
|
|
||||||
|
crypto-js@4.2.0: {}
|
||||||
|
|
||||||
|
dotenv@17.2.2: {}
|
||||||
|
|
||||||
|
undici-types@7.10.0: {}
|
||||||
Reference in New Issue
Block a user