可视化编排添加文本处理,添加堆成和非对称加解密

This commit is contained in:
fofolee 2024-12-30 18:37:42 +08:00
parent 97a0ce2f99
commit 5e2994fe6d
15 changed files with 2127 additions and 94 deletions

150
plugin/lib/RSAEncryption.js Normal file
View File

@ -0,0 +1,150 @@
import NodeForge from "node-forge";
import { forgeBytesToStr, strToForgeBytes, dataConv } from "@/script/Common.js";
const RSAPadDict = {
"PKCS#1": {
getMaxSize: (keySize) => Math.floor((keySize - 1) / 8) - 11,
coding: (chunks, secretKey, method) =>
secretKey[method](chunks, "RSAES-PKCS1-V1_5"),
},
OAEP: {
getMaxSize: (keySize) => Math.floor(keySize / 8) - 42,
coding: (chunks, secretKey, method) =>
secretKey[method](chunks, "RSA-OAEP"),
},
"OAEP/SHA-256": {
getMaxSize: (keySize) => Math.floor(keySize / 8) - 66,
coding: (chunks, secretKey, method) =>
secretKey[method](chunks, "RSA-OAEP", {
md: NodeForge.md.sha256.create(),
}),
},
"OAEP/SHA-256/MGF1-SHA-1": {
getMaxSize: (keySize) => Math.floor(keySize / 8) - 74,
coding: (chunks, secretKey, method) =>
secretKey[method](chunks, "RSA-OAEP", {
md: NodeForge.md.sha256.create(),
mgf1: {
md: NodeForge.md.sha1.create(),
},
}),
},
};
const forgeRSAEncrypt = (bytes, padding, publicKey, outputCodec) => {
publicKey = NodeForge.pki.publicKeyFromPem(publicKey);
let keySize = publicKey.n.bitLength();
let chunks = "";
let encrypted = "";
let maxBlockSize = RSAPadDict[padding].getMaxSize(keySize);
for (let i = 0; i < bytes.length / maxBlockSize; i++) {
chunks = bytes.substring(i * maxBlockSize, (i + 1) * maxBlockSize);
chunks = RSAPadDict[padding].coding(chunks, publicKey, "encrypt");
encrypted += chunks;
}
return outputCodec === "Base64"
? NodeForge.util.encode64(encrypted)
: NodeForge.util.bytesToHex(encrypted);
};
const forgeRSADecrypt = (cipher, padding, privateKey, inputCodec) => {
let bytes =
inputCodec === "Base64"
? NodeForge.util.decode64(cipher)
: NodeForge.util.hexToBytes(cipher);
privateKey = NodeForge.pki.privateKeyFromPem(privateKey);
let keySize = privateKey.n.bitLength();
let maxBlockSize = keySize / 8;
let decrypted = "";
let chunks = "";
for (let i = 0; i < bytes.length / maxBlockSize; i++) {
chunks = bytes.substring(i * maxBlockSize, (i + 1) * maxBlockSize);
chunks = RSAPadDict[padding].coding(chunks, privateKey, "decrypt");
decrypted += chunks;
}
return decrypted;
};
export const RSA = {
name: "RSA",
params: [
{
type: "list",
options: ["PKCS#1", "OAEP", "OAEP/SHA-256", "OAEP/SHA-256/MGF1-SHA-1"],
value: "PKCS#1",
},
{
type: "text",
name: "公钥",
value: ["", "Pem"],
options: ["Pem", "Hex", "Base64"],
id: "publicKey",
},
{
type: "text",
name: "私钥",
value: ["", "Pem"],
options: ["Pem", "Hex", "Base64"],
id: "privateKey",
},
{
type: "list",
options: ["Hex", "Base64"],
value: "Base64",
},
],
actions: [
{
type: "action",
name: "生成密钥",
component: "GenerateKeypair",
},
{
type: "action",
name: "CTF",
component: "RsaCtf",
},
],
encrypt: (
msg,
padding = "PKCS#1",
[pubKey = "", pubKeyCodec = "Pem"],
// eslint-disable-next-line no-unused-vars
[priKey = "", priKeyCodec = "Pem"],
outputCodec = "Base64",
{ inputCodec }
) => {
if (!pubKey) throw "缺少公钥";
if (pubKeyCodec !== "Pem") {
pubKey =
"-----BEGIN PUBLIC KEY-----\n" +
dataConv(pubKey, pubKeyCodec, "Base64") +
"\n-----END PUBLIC KEY-----";
}
pubKey = pubKey.replace(/\\n/g, "\n");
let bytes = strToForgeBytes(msg, inputCodec);
let cipher = forgeRSAEncrypt(bytes, padding, pubKey, outputCodec);
if (!cipher) throw "加密失败";
return cipher;
},
decrypt: (
cipher,
padding = "PKCS#1",
// eslint-disable-next-line no-unused-vars
[pubKey = "", pubKeyCodec = "Pem"],
[priKey = "", priKeyCodec = "Pem"],
codec = "Base64"
) => {
if (!priKey) return "缺少私钥";
if (priKeyCodec !== "Pem") {
priKey =
"-----BEGIN PRIVATE KEY-----\n" +
dataConv(priKey, priKeyCodec, "Base64") +
"\n-----END PRIVATE KEY-----";
}
priKey = priKey.replace(/\\n/g, "\n");
let msg = forgeRSADecrypt(cipher, padding, priKey, codec);
if (!msg) throw "解密失败";
return forgeBytesToStr(msg);
},
};

View File

@ -0,0 +1,149 @@
import { strToBytesArray, bytesArrayToStr, dataConv } from "@/script/Common.js";
import { processSecret } from "./SymmetricEncryption.js";
import SM from "sm-crypto";
export const SM4 = {
name: "SM4",
params: [
{
type: "text",
name: "Key",
value: ["", "Utf8"],
options: ["Utf8", "Hex", "Base64"],
},
{
type: "text",
name: "IV",
value: ["", "Utf8"],
options: ["Utf8", "Hex", "Base64"],
id: "IV",
},
{
type: "list",
options: ["ECB", "CBC"],
value: "ECB",
banList: {
ECB: ["IV"],
},
id: "mode",
},
{ type: "list", options: ["pkcs#7", "none"], value: "pkcs#7" },
{ type: "list", options: ["Base64", "Hex"], value: "Base64" },
],
encrypt: (
msg,
key,
iv = "",
mode = "ECB",
padding = "pkcs#7",
outputCodec = "Base64",
{ inputCodec }
) => {
let bytesArray = strToBytesArray(msg, inputCodec);
return dataConv(
SM.sm4.encrypt(bytesArray, processSecret(key, 16, "hex"), {
mode: mode.toLowerCase(),
iv: processSecret(iv, 16, "hex"),
padding: padding,
}),
"Hex",
outputCodec
);
},
decrypt: (
cipher,
key,
iv = "",
mode = "ECB",
padding = "pkcs#7",
codec = "Base64"
) => {
let bytesArray = SM.sm4.decrypt(
dataConv(cipher, codec, "Hex"),
processSecret(key, 16, "hex"),
{
mode: mode.toLowerCase(),
iv: processSecret(iv, 16, "hex"),
padding: padding,
output: "array",
}
);
return bytesArrayToStr(bytesArray);
},
};
export const SM2 = {
name: "SM2",
params: [
{
type: "text",
name: "公钥",
value: ["", "Base64"],
options: ["Hex", "Base64"],
id: "publicKey",
},
{
type: "text",
name: "私钥",
value: ["", "Base64"],
options: ["Hex", "Base64"],
id: "privateKey",
},
{
type: "list",
options: [
{ label: "C1C3C2", value: 1 },
{ label: "C1C2C3", value: 0 },
],
value: 1,
},
{
type: "list",
options: ["Hex", "Base64"],
value: "Base64",
},
],
actions: [
{
type: "action",
name: "生成密钥",
component: "GenerateKeypair",
},
],
encrypt: (
msg,
[pubKey = "", pubKeyCodec = "Base64"],
// eslint-disable-next-line no-unused-vars
[priKey = "", priKeyCodec = "Base64"],
cipherMode = 1,
outputCodec = "Base64",
{ inputCodec }
) => {
if (!pubKey) throw "缺少公钥";
pubKey = dataConv(pubKey, pubKeyCodec, "Hex");
let cipher = SM.sm2.doEncrypt(
strToBytesArray(msg, inputCodec),
pubKey,
cipherMode
);
if (!cipher) throw "加密失败";
return dataConv(cipher, "Hex", outputCodec);
},
decrypt: (
cipher,
// eslint-disable-next-line no-unused-vars
[pubKey = "", pubKeyCodec = "Base64"],
[priKey = "", priKeyCodec = "Base64"],
cipherMode = 1,
codec = "Base64"
) => {
if (!priKey) return "缺少私钥";
priKey = dataConv(priKey, priKeyCodec, "Hex");
cipher = dataConv(cipher, codec, "Hex");
let msg = SM.sm2.doDecrypt(cipher, priKey, cipherMode, {
output: "array",
});
if (!msg?.length) throw "解密失败";
return bytesArrayToStr(msg);
},
};

View File

@ -0,0 +1,280 @@
import CryptoJS from "crypto-js";
import NodeForge from "node-forge";
import {
strToWordArray,
forgeBytesToStr,
wordArrayToStr,
dataConv,
} from "@/script/Common.js";
let symmetricEncryptionParams = [
{
type: "text",
name: "Key",
value: ["", "Utf8"],
options: ["Utf8", "Hex", "Base64"],
},
{
type: "text",
name: "IV",
value: ["", "Utf8"],
options: ["Utf8", "Hex", "Base64"],
// name 会改变元素样式额外增加ID作为唯一标识
id: "IV",
},
{
type: "list",
options: Object.keys(CryptoJS.mode),
value: "ECB",
// 根据banList禁用指定ID的元素例如选中ECB时禁用IVban和被ban的元素必须要有id
banList: {
ECB: ["IV"],
},
id: "mode",
},
{
type: "list",
options: [128, 192, 256],
value: 128,
},
{
type: "list",
options: Object.keys(CryptoJS.pad),
value: "Pkcs7",
id: "padding",
},
{ type: "list", options: ["Base64", "Hex"], value: "Base64" },
];
// AES添加GCM模式
let symmetricEncryptionParamsAes = JSON.parse(
JSON.stringify(symmetricEncryptionParams)
);
symmetricEncryptionParamsAes[2].options.push("GCM");
symmetricEncryptionParamsAes[2].banList.GCM = ["padding"];
// secret 格式为 hex字符串方便补位
const adjustSecLen = (hexSecret, len) => {
return hexSecret.length >= len * 2
? hexSecret.slice(0, len * 2)
: hexSecret + "00".repeat(len - hexSecret.length / 2);
};
export const processSecret = (secretWithCodec, len, to = "wordArray") => {
let [secret, codec] = secretWithCodec;
let hexSecret = dataConv(secret, codec, "Hex");
let filledSecret = adjustSecLen(hexSecret, len);
return to === "wordArray"
? CryptoJS.enc.Hex.parse(filledSecret)
: filledSecret;
};
// key/iv bytes
const forgeAesGcmEncrypt = (msg, key, iv, outputCodec, inputCodec) => {
var cipher = NodeForge.cipher.createCipher("AES-GCM", key);
cipher.start({
iv: iv,
});
cipher.update(NodeForge.util.createBuffer(msg, inputCodec));
cipher.finish();
var encrypted = cipher.output;
return {
enc: dataConv(encrypted.toHex(), "Hex", outputCodec),
tag: dataConv(cipher.mode.tag.toHex(), "Hex", outputCodec),
};
};
// key/iv bytes
const forgeAesGcmDecrypt = (cipher, key, iv, tag, inputCodec) => {
cipher = NodeForge.util.createBuffer(
NodeForge.util.hexToBytes(dataConv(cipher, inputCodec, "Hex"))
);
var decipher = NodeForge.cipher.createDecipher("AES-GCM", key);
decipher.start({
iv: iv,
tag: NodeForge.util.hexToBytes(dataConv(tag, inputCodec, "Hex")),
});
decipher.update(cipher);
var pass = decipher.finish();
if (pass) {
return decipher.output.getBytes();
}
return null;
};
export const AES = {
name: "AES",
params: symmetricEncryptionParamsAes,
encrypt: (
msg,
key,
iv = "",
mode = "ECB",
keySize = 128,
padding = "Pkcs7",
outputCodec = "Base64",
{ inputCodec }
) => {
if (mode === "GCM") {
key = NodeForge.util.hexToBytes(processSecret(key, keySize / 8, "hex"));
iv = NodeForge.util.hexToBytes(processSecret(iv, 16, "hex"));
let result = forgeAesGcmEncrypt(msg, key, iv, outputCodec, inputCodec);
return JSON.stringify(result, null, 2);
} else {
let encrypted = CryptoJS.AES.encrypt(
strToWordArray(msg, inputCodec),
processSecret(key, keySize / 8, "wordArray"),
{
iv: processSecret(iv, 16, "wordArray"),
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
}
);
return CryptoJS.enc[outputCodec].stringify(encrypted.ciphertext);
}
},
decrypt: (
cipher,
key,
iv = "",
mode = "ECB",
keySize = 128,
padding = "Pkcs7",
codec = "Base64"
) => {
if (mode === "GCM") {
let format = `密文格式应为 {"enc":"hex or base64","tag":"hex or base64"}`;
try {
var { enc, tag } = JSON.parse(cipher);
} catch (_) {
throw new Error(format);
}
if (!enc || !tag) {
throw new Error(format);
}
key = NodeForge.util.hexToBytes(processSecret(key, keySize / 8, "hex"));
iv = NodeForge.util.hexToBytes(processSecret(iv, 16, "hex"));
let decrypted = forgeAesGcmDecrypt(enc, key, iv, tag, codec);
return forgeBytesToStr(decrypted);
} else {
cipher = dataConv(cipher, codec, "Base64");
const decrypt = CryptoJS.AES.decrypt(
cipher,
processSecret(key, keySize / 8, "wordArray"),
{
iv: processSecret(iv, 16, "wordArray"),
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
}
);
return wordArrayToStr(decrypt);
}
},
};
export const DES = {
name: "DES",
params: symmetricEncryptionParams.filter((x, i) => i !== 3),
encrypt: (
msg,
key,
iv = "",
mode = "ECB",
padding = "Pkcs7",
outputCodec = "Base64",
{ inputCodec }
) => {
const encrypted = CryptoJS.DES.encrypt(
strToWordArray(msg, inputCodec),
processSecret(key, 8, "wordArray"),
{
iv: processSecret(iv, 8, "wordArray"),
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
}
);
return CryptoJS.enc[outputCodec].stringify(encrypted.ciphertext);
},
decrypt: (
cipher,
key,
iv = "",
mode = "ECB",
padding = "Pkcs7",
codec = "Base64"
) => {
cipher = dataConv(cipher, codec, "Base64");
const decrypt = CryptoJS.DES.decrypt(
cipher,
processSecret(key, 8, "wordArray"),
{
iv: processSecret(iv, 8, "wordArray"),
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
}
);
return wordArrayToStr(decrypt);
},
};
export const TripleDES = {
name: "TripleDES",
params: symmetricEncryptionParams.filter((x, i) => i !== 3),
encrypt: (
msg,
key,
iv = "",
mode = "ECB",
padding = "Pkcs7",
outputCodec = "Base64",
{ inputCodec }
) => {
const encrypted = CryptoJS.TripleDES.encrypt(
strToWordArray(msg, inputCodec),
processSecret(key, 8, "wordArray"),
{
iv: processSecret(iv, 8, "wordArray"),
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
}
);
return CryptoJS.enc[outputCodec].stringify(encrypted.ciphertext);
},
decrypt: (
cipher,
key,
iv = "",
mode = "ECB",
padding = "Pkcs7",
codec = "Base64"
) => {
cipher = dataConv(cipher, codec, "Base64");
const decrypt = CryptoJS.TripleDES.decrypt(
cipher,
processSecret(key, 8, "wordArray"),
{
iv: processSecret(iv, 8, "wordArray"),
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
}
);
return wordArrayToStr(decrypt);
},
};
export const rc4 = {
name: "RC4",
params: [{ type: "text", name: "Key", value: "" }],
encrypt: (msg, key, { inputCodec }) =>
CryptoJS.RC4.encrypt(strToWordArray(msg, inputCodec), key).toString(),
decrypt: (cipher, key) => wordArrayToStr(CryptoJS.RC4.decrypt(cipher, key)),
};
export const rabbit = {
name: "Rabbit",
params: [{ type: "text", name: "Key", value: "" }],
encrypt: (msg, key, { inputCodec }) =>
CryptoJS.Rabbit.encrypt(strToWordArray(msg, inputCodec), key).toString(),
decrypt: (cipher, key) =>
wordArrayToStr(CryptoJS.Rabbit.decrypt(cipher, key)),
};

View File

@ -0,0 +1,5 @@
const quickcomposer = {
textProcessing: require("./quickcomposer/textProcessing"),
};
module.exports = quickcomposer;

View File

@ -0,0 +1,383 @@
const sm2 = require("sm-crypto").sm2;
const sm3 = require("sm-crypto").sm3;
const sm4 = require("sm-crypto").sm4;
const CryptoJS = require("crypto-js");
const NodeForge = require("node-forge");
// 数据编码转换
const dataConv = (str, fromCodec, toCodec) => {
// 特殊处理 PEM 格式
if (fromCodec.toLowerCase() === "pem") {
const pemContent = str
.replace(/-----(BEGIN|END)[^-]+-----/g, "")
.replace(/[\r\n]/g, "");
return Buffer.from(pemContent, "base64").toString(toCodec.toLowerCase());
}
// 其他格式直接转换
return Buffer.from(str, fromCodec.toLowerCase()).toString(
toCodec.toLowerCase()
);
};
// 处理密钥和IV
const processSecret = (key, codec, len) => {
// 转换成 hex 并填充到指定长度
const hexStr = dataConv(key, codec, "hex")
.padEnd(len * 2, "0")
.slice(0, len * 2);
return CryptoJS.enc.Hex.parse(hexStr);
};
const textProcessing = {
// base64 编码
base64Encode: function (text) {
return dataConv(text, "utf8", "base64");
},
// base64 解码
base64Decode: function (text) {
return dataConv(text, "base64", "utf8");
},
// URL 编码
urlEncode: function (text) {
return encodeURIComponent(text);
},
// URL 解码
urlDecode: function (text) {
return decodeURIComponent(text);
},
// html 编码
htmlEncode: function (text) {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
},
// html 解码
htmlDecode: function (text) {
return text
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'");
},
// 十六进制
hexEncode: function (text) {
return dataConv(text, "utf8", "hex");
},
// 十六进制解码
hexDecode: function (text) {
return dataConv(text, "hex", "utf8");
},
// 对称加解密
symmetricCrypto: function (config) {
const {
text,
algorithm,
mode = "CBC",
padding = "Pkcs7",
key = { value: "", codec: "Utf8" },
keyLength = 128,
operation = "encrypt",
format = "Base64",
iv = { value: "", codec: "Utf8" },
} = config;
// 处理密钥和IV
const processedKey = processSecret(key.value, key.codec, keyLength / 8);
const processedIV =
mode === "ECB"
? undefined
: processSecret(iv?.value || key.value, iv?.codec || key.codec, 16);
// SM4 使用专门的库
if (algorithm === "SM4") {
const hexKey = processedKey.toString();
// 处理输入文本格式
let inputText = text;
if (operation === "decrypt") {
inputText =
format === "Base64"
? CryptoJS.enc.Base64.parse(text).toString(CryptoJS.enc.Hex)
: text;
}
if (mode === "CBC") {
const hexIv = processedIV ? processedIV.toString() : "0".repeat(32);
const result =
operation === "encrypt"
? sm4.encrypt(inputText, hexKey, {
mode: "cbc",
iv: hexIv,
padding: padding.toLowerCase(),
})
: sm4.decrypt(inputText, hexKey, {
mode: "cbc",
iv: hexIv,
padding: padding.toLowerCase(),
});
// 处理输出格式
return operation === "encrypt" && format === "Base64"
? CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(result))
: result;
}
const result =
operation === "encrypt"
? sm4.encrypt(inputText, hexKey, {
padding: padding.toLowerCase(),
})
: sm4.decrypt(inputText, hexKey, {
padding: padding.toLowerCase(),
});
// 处理输出格式
return operation === "encrypt" && format === "Base64"
? CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(result))
: result;
}
// 准备加密配置
const cryptoConfig = {
mode: CryptoJS.mode[mode],
padding: CryptoJS.pad[padding],
};
// 添加 IV如果需要
if (mode !== "ECB" && processedIV) {
cryptoConfig.iv = processedIV;
}
// 加密/解密操作
if (operation === "encrypt") {
let encrypted;
const inputText = CryptoJS.enc.Utf8.parse(text);
switch (algorithm) {
case "AES":
if (mode === "GCM") {
const cipher = NodeForge.cipher.createCipher(
"AES-GCM",
processedKey.toString()
);
cipher.start({
iv: cryptoConfig.iv ? cryptoConfig.iv.toString() : "",
tagLength: 128,
});
cipher.update(NodeForge.util.createBuffer(text, "utf8"));
cipher.finish();
return {
enc: cipher.output.toHex(),
tag: cipher.mode.tag.toHex(),
};
}
encrypted = CryptoJS.AES.encrypt(
inputText,
processedKey,
cryptoConfig
);
break;
default:
throw "不支持的算法";
}
return encrypted.ciphertext.toString(CryptoJS.enc[format]);
} else {
// 解密
if (algorithm === "AES" && mode === "GCM") {
try {
const { enc, tag } = JSON.parse(text);
const decipher = NodeForge.cipher.createDecipher(
"AES-GCM",
processedKey.toString()
);
decipher.start({
iv: cryptoConfig.iv ? cryptoConfig.iv.toString() : "",
tag: NodeForge.util.createBuffer(tag, "hex"),
});
decipher.update(NodeForge.util.createBuffer(enc, "hex"));
const pass = decipher.finish();
return pass ? decipher.output.toString() : null;
} catch (e) {
throw "解密失败";
}
}
// 将输入转换为 CipherParams 格式
const ciphertext = CryptoJS.enc[format].parse(text);
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: ciphertext,
});
let decrypted;
switch (algorithm) {
case "AES":
decrypted = CryptoJS.AES.decrypt(
cipherParams,
processedKey,
cryptoConfig
);
break;
default:
throw "不支持的算法";
}
return CryptoJS.enc.Utf8.stringify(decrypted);
}
},
// RSA 加密
rsaEncrypt: function (text, key) {
return crypto.publicEncrypt(key, Buffer.from(text)).toString("base64");
},
// RSA 解密
rsaDecrypt: function (text, key) {
return crypto.privateDecrypt(key, Buffer.from(text, "base64")).toString();
},
// SM4 加密
sm4Encrypt: function (text, key) {
// 将密钥转换为 16 进制字符串
const hexKey = Buffer.from(key).toString("hex");
return sm4.encrypt(text, hexKey);
},
// SM4 解密
sm4Decrypt: function (text, key) {
// 将密钥转换为 16 进制字符串
const hexKey = Buffer.from(key).toString("hex");
return sm4.decrypt(text, hexKey);
},
// SM2 加密
sm2Encrypt: function (text, key) {
return sm2.encrypt(text, key);
},
// SM2 解密
sm2Decrypt: function (text, key) {
return sm2.decrypt(text, key);
},
// MD5 哈希
md5Hash: function (text) {
return crypto.createHash("md5").update(text).digest("hex");
},
// SHA256 哈希
sha256Hash: function (text) {
return crypto.createHash("sha256").update(text).digest("hex");
},
// SM3 哈希
sm3Hash: function (text) {
return sm3(text);
},
// 字符串反转
reverseString: function (text) {
return text.split("").reverse().join("");
},
// 字符串替换
replaceString: function (text, oldStr, newStr) {
return text.replace(oldStr, newStr);
},
// 字符串截取
substring: function (text, start, end) {
return text.substring(start, end);
},
// 正则提取
regexExtract: function (text, regex) {
const match = text.match(regex);
return match ? match[0] : "";
},
// 非对称加解密
asymmetricCrypto: function (config) {
const {
text,
algorithm,
operation,
format = "Base64",
publicKey = { key: "", codec: "Pem" },
privateKey = { key: "", codec: "Pem" },
padding = "RSAES-PKCS1-V1_5",
cipherMode = 1,
} = config;
if (algorithm === "SM2") {
if (operation === "encrypt") {
if (!publicKey.key) throw "缺少公钥";
// 转换公钥格式
const hexPubKey =
publicKey.codec === "Hex"
? publicKey.key
: dataConv(publicKey.key, publicKey.codec, "Hex");
// 加密
const cipher = sm2.doEncrypt(text, hexPubKey, cipherMode);
// 转换输出格式
return format === "Base64" ? dataConv(cipher, "Hex", "Base64") : cipher;
} else {
if (!privateKey.key) throw "缺少私钥";
// 转换私钥格式
const hexPriKey =
privateKey.codec === "Hex"
? privateKey.key
: dataConv(privateKey.key, privateKey.codec, "Hex");
// 转换输入格式
const hexCipher =
format === "Base64" ? dataConv(text, "Base64", "Hex") : text;
// 解密
const msg = sm2.doDecrypt(hexCipher, hexPriKey, cipherMode, {
output: "utf8",
});
if (!msg) throw "解密失败";
return msg;
}
} else if (algorithm === "RSA") {
if (operation === "encrypt") {
if (!publicKey.key) throw "缺少公钥";
// 转换公钥格式
let formattedPubKey = publicKey.key;
if (publicKey.codec !== "Pem") {
formattedPubKey =
"-----BEGIN RSA PUBLIC KEY-----\n" +
dataConv(publicKey.key, publicKey.codec, "Base64") +
"\n-----END RSA PUBLIC KEY-----";
}
formattedPubKey = formattedPubKey.replace(/\\n/g, "\n");
// 创建 RSA 公钥对象
const publicKeyObj = NodeForge.pki.publicKeyFromPem(formattedPubKey);
// 将文本转换为二进制数据
const binaryData = NodeForge.util.encodeUtf8(text);
// 使用指定的填充方式加密
const encrypted = publicKeyObj.encrypt(binaryData, padding);
// 转换输出格式
return format === "Base64"
? dataConv(encrypted, "binary", "Base64")
: dataConv(encrypted, "binary", "Hex");
} else {
if (!privateKey.key) throw "缺少私钥";
// 转换私钥格式
let formattedPriKey = privateKey.key;
if (privateKey.codec !== "Pem") {
formattedPriKey =
"-----BEGIN RSA PRIVATE KEY-----\n" +
dataConv(privateKey.key, privateKey.codec, "Base64") +
"\n-----END RSA PRIVATE KEY-----";
}
formattedPriKey = formattedPriKey.replace(/\\n/g, "\n");
// 创建 RSA 私钥对象
const privateKeyObj = NodeForge.pki.privateKeyFromPem(formattedPriKey);
// 转换输入格式
const binary =
format === "Base64"
? dataConv(text, "Base64", "binary")
: dataConv(text, "Hex", "binary");
// 解密
try {
const decrypted = privateKeyObj.decrypt(binary, padding);
// 将二进制数据转换回文本
return NodeForge.util.decodeUtf8(decrypted);
} catch (e) {
console.error(e);
throw "解密失败";
}
}
}
throw "不支持的算法";
},
};
module.exports = textProcessing;

View File

@ -4,13 +4,15 @@
"requires": true,
"packages": {
"": {
"name": "plugin",
"dependencies": {
"axios": "^1.7.9",
"crypto-js": "^4.2.0",
"iconv-lite": "^0.6.3",
"jimp": "^0.22.12",
"lodash": "^4.17.21",
"node-forge": "^1.3.1",
"ses": "^1.10.0",
"sm-crypto": "^0.3.13",
"tree-kill": "^1.2.2"
}
},
@ -554,6 +556,12 @@
"node": ">= 0.8"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -724,6 +732,12 @@
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
"license": "BSD-3-Clause"
},
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"license": "MIT"
},
"node_modules/load-bmfont": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/load-bmfont/-/load-bmfont-1.4.2.tgz",
@ -806,6 +820,15 @@
}
}
},
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/omggif": {
"version": "1.0.10",
"resolved": "https://registry.npmmirror.com/omggif/-/omggif-1.0.10.tgz",
@ -992,6 +1015,15 @@
"@endo/env-options": "^1.1.8"
}
},
"node_modules/sm-crypto": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/sm-crypto/-/sm-crypto-0.3.13.tgz",
"integrity": "sha512-ztNF+pZq6viCPMA1A6KKu3bgpkmYti5avykRHbcFIdSipFdkVmfUw2CnpM2kBJyppIalqvczLNM3wR8OQ0pT5w==",
"license": "MIT",
"dependencies": {
"jsbn": "^1.1.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
@ -1512,6 +1544,11 @@
"delayed-stream": "~1.0.0"
}
},
"crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -1626,6 +1663,11 @@
"resolved": "https://registry.npmmirror.com/jpeg-js/-/jpeg-js-0.4.4.tgz",
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="
},
"jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
},
"load-bmfont": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/load-bmfont/-/load-bmfont-1.4.2.tgz",
@ -1680,6 +1722,11 @@
"whatwg-url": "^5.0.0"
}
},
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"omggif": {
"version": "1.0.10",
"resolved": "https://registry.npmmirror.com/omggif/-/omggif-1.0.10.tgz",
@ -1803,6 +1850,14 @@
"@endo/env-options": "^1.1.8"
}
},
"sm-crypto": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/sm-crypto/-/sm-crypto-0.3.13.tgz",
"integrity": "sha512-ztNF+pZq6viCPMA1A6KKu3bgpkmYti5avykRHbcFIdSipFdkVmfUw2CnpM2kBJyppIalqvczLNM3wR8OQ0pT5w==",
"requires": {
"jsbn": "^1.1.0"
}
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",

View File

@ -1,10 +1,13 @@
{
"dependencies": {
"axios": "^1.7.9",
"crypto-js": "^4.2.0",
"iconv-lite": "^0.6.3",
"jimp": "^0.22.12",
"lodash": "^4.17.21",
"node-forge": "^1.3.1",
"ses": "^1.10.0",
"sm-crypto": "^0.3.13",
"tree-kill": "^1.2.2"
}
}

View File

@ -39,6 +39,7 @@ window.convertFilePathToUtoolsPayload = convertFilePathToUtoolsPayload;
window.getuToolsLite = require("./lib/utoolsLite");
window.quickcommand = require("./lib/quickcommand");
window.quickcomposer = require("./lib/quickcomposer");
window.getQuickcommandTempFile = require("./lib/getQuickcommandTempFile");
window.imageProcessor = require("./lib/imageprocessor");
window.showUb = require("./lib/showDocs");
@ -97,6 +98,7 @@ window.removeHtmlTags = (value) => {
return quickcommand.htmlParse(value).querySelector("body").innerText;
};
window.hexEncode = (text) => Buffer.from(text, "utf8").toString("hex");
window.hexDecode = (text) => Buffer.from(text, "hex").toString("utf8");
window.base64Decode = (text) => Buffer.from(text, "base64").toString("utf8");
@ -123,6 +125,7 @@ let getSandboxFuns = () => {
path,
os,
child_process,
quickcomposer,
};
Object.keys(shortCodes).forEach((f) => {
sandbox[f] = shortCodes[f];

View File

@ -163,16 +163,23 @@ export default defineComponent({
/* 布局更加紧凑 */
/* 输入框高度及字体 */
.command-composer :deep(.q-field--filled .q-field__control),
.command-composer :deep(.q-field--filled .q-field__control > *),
.command-composer :deep(.q-field--filled:not(.q-textarea) .q-field__control),
.command-composer
:deep(.q-field--filled:not(.q-field--labeled) .q-field__native) {
border-radius: 5px;
font-size: 12px;
:deep(.q-field--filled:not(.q-textarea) .q-field__control > *),
.command-composer
:deep(.q-field--filled:not(.q-field--labeled):not(.q-textarea)
.q-field__native) {
max-height: 36px !important;
min-height: 36px !important;
}
.command-composer :deep(.q-field--filled .q-field__control),
.command-composer :deep(.q-field--filled .q-field__control > *),
.command-composer :deep(.q-field--filled .q-field__native) {
border-radius: 5px;
font-size: 12px;
}
/* 输入框图标大小 */
.command-composer :deep(.q-field--filled .q-field__control .q-icon) {
font-size: 18px;

View File

@ -130,6 +130,8 @@ import KeyEditor from "./KeyEditor.vue";
import UBrowserEditor from "./ubrowser/UBrowserEditor.vue";
import VariableInput from "./VariableInput.vue";
import AxiosConfigEditor from "./http/AxiosConfigEditor.vue";
import SymmetricCryptoEditor from "./crypto/SymmetricCryptoEditor.vue";
import AsymmetricCryptoEditor from "./crypto/AsymmetricCryptoEditor.vue";
import { validateVariableName } from "js/common/variableValidator";
export default defineComponent({
@ -139,6 +141,8 @@ export default defineComponent({
UBrowserEditor,
VariableInput,
AxiosConfigEditor,
SymmetricCryptoEditor,
AsymmetricCryptoEditor,
},
props: {
command: {
@ -351,7 +355,7 @@ export default defineComponent({
.command-item:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
/* transform: translateY(-1px); */
}
.composer-card :deep(.q-field__label) {

View File

@ -0,0 +1,398 @@
<template>
<div class="asymmetric-crypto-editor">
<!-- 加密/解密切换 -->
<q-btn-toggle
v-model="operation"
:options="[
{ label: '加密', value: 'encrypt' },
{ label: '解密', value: 'decrypt' },
]"
spread
dense
no-caps
unelevated
toggle-color="primary"
/>
<!-- 文本输入 -->
<div class="row">
<VariableInput
v-model="text"
:label="operation === 'encrypt' ? '要加密的文本' : '要解密的文本'"
:command="{
icon:
operation === 'encrypt' ? 'enhanced_encryption' : 'no_encryption',
}"
class="col-12"
@update:model-value="updateConfig"
/>
</div>
<div class="row">
<!-- 算法选择 -->
<q-select
v-model="algorithm"
:options="algorithms"
label="加密算法"
dense
filled
class="col-grow"
emit-value
map-options
/>
<!-- RSA填充选择 -->
<q-select
v-if="algorithm === 'RSA'"
v-model="padding"
:options="paddings"
label="填充方式"
dense
filled
class="col-grow"
emit-value
map-options
/>
<!-- SM2密文格式选择 -->
<q-select
v-if="algorithm === 'SM2'"
v-model="cipherMode"
:options="[
{ label: 'C1C3C2', value: 1 },
{ label: 'C1C2C3', value: 0 },
]"
label="密文格式"
dense
filled
class="col-grow"
emit-value
map-options
/>
<!-- 格式选择 -->
<q-select
v-model="format"
:options="operation === 'encrypt' ? outputFormats : inputFormats"
:label="operation === 'encrypt' ? '输出格式' : '输入格式'"
dense
filled
class="col-grow"
emit-value
map-options
/>
</div>
<div class="row">
<!-- 密钥输入区域 -->
<div class="col-6 key-input">
<div class="key-wrapper">
<q-input
v-model="publicKey"
type="textarea"
filled
autogrow
label="公钥"
class="key-textarea"
@update:model-value="updateConfig"
/>
<q-btn-dropdown
flat
dense
:label="publicKeyCodec"
class="codec-dropdown"
>
<q-list>
<q-item
v-for="codec in keyCodecs"
:key="codec.value"
clickable
v-close-popup
@click="publicKeyCodec = codec.value"
>
<q-item-section>
<q-item-label>{{ codec.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
<div class="col-6 key-input">
<div class="key-wrapper">
<q-input
v-model="privateKey"
type="textarea"
filled
autogrow
label="私钥"
class="key-textarea"
@update:model-value="updateConfig"
/>
<q-btn-dropdown
flat
dense
:label="privateKeyCodec"
class="codec-dropdown"
>
<q-list>
<q-item
v-for="codec in keyCodecs"
:key="codec.value"
clickable
v-close-popup
@click="privateKeyCodec = codec.value"
>
<q-item-section>
<q-item-label>{{ codec.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import VariableInput from "../VariableInput.vue";
import { formatJsonVariables } from "js/composer/formatString";
export default defineComponent({
name: "AsymmetricCryptoEditor",
components: {
VariableInput,
},
data() {
return {
operation: "encrypt",
text: "",
algorithm: "RSA",
padding: "RSAES-PKCS1-V1_5",
cipherMode: 1,
publicKey: "",
privateKey: "",
publicKeyCodec: "Pem",
privateKeyCodec: "Pem",
format: "Base64",
algorithms: [
{ label: "RSA", value: "RSA" },
{ label: "SM2", value: "SM2" },
],
paddings: [
{ label: "PKCS#1 v1.5", value: "RSAES-PKCS1-V1_5" },
{ label: "OAEP", value: "RSA-OAEP" },
{ label: "OAEP/SHA-256", value: "RSA-OAEP-256" },
],
keyCodecs: [
{ label: "PEM", value: "Pem" },
{ label: "Base64", value: "Base64" },
{ label: "Hex", value: "Hex" },
],
outputFormats: [
{ label: "Base64", value: "Base64" },
{ label: "Hex", value: "Hex" },
],
inputFormats: [
{ label: "Base64", value: "Base64" },
{ label: "Hex", value: "Hex" },
],
};
},
methods: {
updateConfig() {
const code = `quickcomposer.textProcessing.asymmetricCrypto(${formatJsonVariables(
{
text: this.text,
algorithm: this.algorithm,
operation: this.operation,
format: this.format,
publicKey: {
key: this.publicKey,
codec: this.publicKeyCodec,
},
privateKey: {
key: this.privateKey,
codec: this.privateKeyCodec,
},
padding: this.algorithm === "RSA" ? this.padding : undefined,
cipherMode: this.algorithm === "SM2" ? this.cipherMode : undefined,
},
["text"]
)})`;
this.$emit("update:model-value", code);
},
},
watch: {
operation() {
this.format = "Base64";
this.updateConfig();
},
algorithm() {
if (this.algorithm === "RSA") {
this.padding = "PKCS#1";
} else {
this.cipherMode = 1;
}
this.updateConfig();
},
//
text() {
this.updateConfig();
},
padding() {
this.updateConfig();
},
cipherMode() {
this.updateConfig();
},
publicKey() {
this.updateConfig();
},
privateKey() {
this.updateConfig();
},
publicKeyCodec() {
this.updateConfig();
},
privateKeyCodec() {
this.updateConfig();
},
format() {
this.updateConfig();
},
},
});
</script>
<style scoped>
.asymmetric-crypto-editor {
display: flex;
flex-direction: column;
gap: 8px;
}
.row {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: stretch;
}
.col-grow {
flex: 1 1 0;
min-width: 150px;
}
/* 密钥输入区域样式 */
.key-input {
display: flex;
flex-direction: column;
flex: 1;
min-width: calc(50% - 8px);
max-width: calc(50% - 8px);
}
.key-wrapper {
position: relative;
height: 100%;
}
.key-textarea {
height: 100%;
width: 100%;
}
/* 确保输入框占满容器 */
.key-textarea :deep(.q-field) {
width: 100%;
height: 100%;
}
/* 确保文本区域占满输入框 */
.key-textarea :deep(.q-field__native) {
min-height: 120px;
resize: none;
}
/* 编码选择下拉按钮样式 */
.codec-dropdown {
min-width: 45px;
max-width: 45px;
font-size: 10px;
padding: 2px 4px;
height: 20px;
line-height: 16px;
border-radius: 4px;
color: rgba(0, 0, 0, 0.6);
z-index: 1;
position: absolute;
right: 8px;
bottom: 8px;
}
/* 下拉菜单项样式 */
.codec-dropdown :deep(.q-btn-dropdown__arrow) {
font-size: 10px;
margin-left: 1px;
}
.codec-dropdown :deep(.q-list) {
min-width: 60px;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.codec-dropdown :deep(.q-item) {
min-height: 24px;
padding: 2px 6px;
}
.codec-dropdown :deep(.q-item__label) {
font-size: 10px;
}
.body--dark .codec-dropdown {
color: rgba(255, 255, 255, 0.7);
}
.body--dark .codec-dropdown :deep(.q-list) {
background: #1d1d1d;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* 确保选择器行在空间不够时换行美观 */
@media (max-width: 600px) {
.col-grow {
flex: 1 1 calc(50% - 8px);
max-width: none;
}
.key-input {
min-width: 100%;
max-width: 100%;
margin-bottom: 8px;
}
}
.q-btn-toggle {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
}
.body--dark .q-btn-toggle {
border-color: rgba(255, 255, 255, 0.12);
}
/* 确保下拉按钮内容垂直居中 */
.codec-dropdown :deep(.q-btn__content) {
min-height: unset;
padding: 0;
}
/* 调整下拉按钮的内容间距 */
.codec-dropdown :deep(.q-btn__wrapper) {
padding: 0 4px;
min-height: unset;
}
</style>

View File

@ -0,0 +1,432 @@
<template>
<div class="symmetric-crypto-editor q-gutter-y-sm">
<!-- 加密/解密切换 -->
<q-btn-toggle
v-model="operation"
:options="[
{ label: '加密', value: 'encrypt' },
{ label: '解密', value: 'decrypt' },
]"
spread
dense
no-caps
unelevated
toggle-color="primary"
/>
<!-- 文本输入 -->
<div class="row">
<VariableInput
v-model="text"
:label="operation === 'encrypt' ? '要加密的文本' : '要解密的文本'"
:command="{
icon:
operation === 'encrypt' ? 'enhanced_encryption' : 'no_encryption',
}"
class="col-8"
@update:model-value="updateConfig"
/>
<q-select
v-model="format"
:options="operation === 'encrypt' ? outputFormats : inputFormats"
:label="operation === 'encrypt' ? '输出格式' : '输入格式'"
dense
filled
class="col-4"
emit-value
map-options
/>
</div>
<div class="row">
<!-- 算法选择 -->
<q-select
v-model="algorithm"
:options="algorithms"
label="加密算法"
dense
filled
class="col-select"
emit-value
map-options
/>
<!-- AES密钥长度选择 -->
<q-select
v-if="showKeyLength"
v-model="keyLength"
:options="[
{ label: '128位', value: 128 },
{ label: '192位', value: 192 },
{ label: '256位', value: 256 },
]"
label="密钥长度"
dense
filled
class="col-select"
emit-value
map-options
/>
<!-- 模式选择 -->
<q-select
v-model="mode"
:options="modes"
label="加密模式"
dense
filled
class="col-select"
emit-value
map-options
/>
<!-- Padding选择 -->
<q-select
v-model="padding"
:options="paddings"
label="填充方式"
dense
filled
class="col-select"
emit-value
map-options
/>
</div>
<div class="row">
<!-- 密钥输入区域 -->
<div class="col-grow key-input">
<div class="key-wrapper">
<q-input
v-model="key"
filled
label="密钥"
class="key-input"
@update:model-value="updateConfig"
/>
<q-btn-dropdown flat dense :label="keyCodec" class="codec-dropdown">
<q-list>
<q-item
v-for="codec in keyCodecs"
:key="codec.value"
clickable
v-close-popup
@click="keyCodec = codec.value"
>
<q-item-section>
<q-item-label>{{ codec.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
<!-- IV输入区域 -->
<div v-if="showIV" class="col-grow key-input">
<div class="key-wrapper">
<q-input
v-model="iv"
filled
label="IV"
class="key-input"
@update:model-value="updateConfig"
/>
<q-btn-dropdown flat dense :label="ivCodec" class="codec-dropdown">
<q-list>
<q-item
v-for="codec in keyCodecs"
:key="codec.value"
clickable
v-close-popup
@click="ivCodec = codec.value"
>
<q-item-section>
<q-item-label>{{ codec.label }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from "vue";
import VariableInput from "../VariableInput.vue";
import { formatJsonVariables } from "js/composer/formatString";
export default defineComponent({
name: "SymmetricCryptoEditor",
components: {
VariableInput,
},
props: {
modelValue: {
type: [String, Object],
default: "",
},
},
data() {
return {
operation: "encrypt",
text: "",
algorithm: "AES",
keyLength: 128,
mode: "CBC",
padding: "Pkcs7",
key: "",
keyCodec: "Utf8",
iv: "",
ivCodec: "Utf8",
format: "Base64",
keyCodecs: [
{ label: "UTF-8", value: "Utf8" },
{ label: "Base64", value: "Base64" },
{ label: "Hex", value: "Hex" },
],
algorithms: [
{ label: "AES", value: "AES" },
{ label: "SM4", value: "SM4" },
],
outputFormats: [
{ label: "Base64", value: "Base64" },
{ label: "Hex", value: "Hex" },
],
inputFormats: [
{ label: "Base64", value: "Base64" },
{ label: "Hex", value: "Hex" },
],
};
},
computed: {
modes() {
// SM4 ECB/CBC
if (this.algorithm === "SM4") {
return [
{ label: "ECB", value: "ECB" },
{ label: "CBC", value: "CBC" },
];
}
// AES/DES/3DES
return [
{ label: "ECB", value: "ECB" },
{ label: "CBC", value: "CBC" },
{ label: "CFB", value: "CFB" },
{ label: "OFB", value: "OFB" },
{ label: "CTR", value: "CTR" },
{ label: "GCM", value: "GCM" },
];
},
paddings() {
// SM4
if (this.algorithm === "SM4") {
return [
{ label: "PKCS#7", value: "pkcs#7" },
{ label: "None", value: "none" },
];
}
// AES/DES/3DES
return [
{ label: "PKCS7", value: "Pkcs7" },
{ label: "Zero Padding", value: "ZeroPadding" },
{ label: "No Padding", value: "NoPadding" },
{ label: "ISO-10126", value: "Iso10126" },
{ label: "ANSI X.923", value: "AnsiX923" },
{ label: "ISO-97971", value: "Iso97971" },
];
},
showIV() {
return this.mode !== "ECB";
},
showKeyLength() {
return this.algorithm === "AES";
},
},
methods: {
updateConfig() {
const code = `quickcomposer.textProcessing.symmetricCrypto(${formatJsonVariables(
{
text: this.text,
algorithm: this.algorithm,
mode: this.mode,
padding: this.padding,
key: {
value: this.key,
codec: this.keyCodec,
},
keyLength: this.keyLength,
operation: this.operation,
format: this.format,
iv:
this.mode !== "ECB"
? {
value: this.iv,
codec: this.ivCodec,
}
: undefined,
},
["text"]
)})`;
this.$emit("update:model-value", code);
},
},
watch: {
operation() {
//
this.format = "Base64";
this.updateConfig();
},
text() {
this.updateConfig();
},
algorithm() {
//
if (this.algorithm === "SM4") {
this.mode = "ECB";
this.padding = "pkcs#7";
} else {
this.mode = "CBC";
this.padding = "Pkcs7";
}
this.updateConfig();
},
mode() {
this.updateConfig();
},
padding() {
this.updateConfig();
},
format() {
this.updateConfig();
},
keyLength() {
this.updateConfig();
},
key() {
this.updateConfig();
},
iv() {
this.updateConfig();
},
},
});
</script>
<style scoped>
.crypto-editor {
display: flex;
flex-direction: column;
gap: 8px;
}
.row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.col-select {
flex: 1;
min-width: 120px;
max-width: 200px;
}
.col-grow {
flex: 1 1 0;
min-width: 150px;
}
/* 确保第一行的输入框和格式选择器的比例固定 */
.row:first-of-type .col-8 {
flex: 4;
min-width: 200px;
}
.row:first-of-type .col-4 {
flex: 1;
min-width: 120px;
}
/* 确保选择器行在空间不够时换行美观 */
@media (max-width: 600px) {
.col-select {
flex: 1 1 calc(50% - 8px);
max-width: none;
}
}
.q-btn-toggle {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 4px;
}
.body--dark .q-btn-toggle {
border-color: rgba(255, 255, 255, 0.12);
}
.key-input {
position: relative;
}
.key-wrapper {
position: relative;
}
/* 编码选择下拉按钮样式 */
.codec-dropdown {
min-width: 45px;
max-width: 45px;
font-size: 10px;
padding: 2px 4px;
height: 20px;
line-height: 16px;
border-radius: 4px;
color: rgba(0, 0, 0, 0.6);
z-index: 1;
position: absolute;
right: 8px;
bottom: 8px;
}
/* 下拉菜单项样式 */
.codec-dropdown :deep(.q-btn-dropdown__arrow) {
font-size: 10px;
margin-left: 1px;
}
.codec-dropdown :deep(.q-list) {
min-width: 60px;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.codec-dropdown :deep(.q-item) {
min-height: 24px;
padding: 2px 6px;
}
.codec-dropdown :deep(.q-item__label) {
font-size: 10px;
}
.body--dark .codec-dropdown {
color: rgba(255, 255, 255, 0.7);
}
.body--dark .codec-dropdown :deep(.q-list) {
background: #1d1d1d;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* 确保下拉按钮内容垂直居中 */
.codec-dropdown :deep(.q-btn__content) {
min-height: unset;
padding: 0;
}
/* 调整下拉按钮的内容间距 */
.codec-dropdown :deep(.q-btn__wrapper) {
padding: 0 4px;
min-height: unset;
}
</style>

View File

@ -1,85 +0,0 @@
export const encodeCommands = {
label: "编码解码",
icon: "code",
defaultOpened: false,
commands: [
{
value: "(text=>Buffer.from(text).toString('base64'))",
label: "Base64编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "lock",
},
],
},
{
value: "(text=>Buffer.from(text,'base64').toString())",
label: "Base64解码",
config: [
{
key: "text",
label: "要解码的Base64文本",
type: "input",
defaultValue: "",
icon: "lock_open",
},
],
},
{
value: "(text=>Buffer.from(text).toString('hex'))",
label: "十六进制编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "lock",
},
],
},
{
value: "(text=>Buffer.from(text,'hex').toString())",
label: "十六进制解码",
config: [
{
key: "text",
label: "要解码的十六进制文本",
type: "input",
defaultValue: "",
icon: "lock_open",
},
],
},
{
value: "encodeURIComponent",
label: "URL编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "link",
},
],
},
{
value: "decodeURIComponent",
label: "URL解码",
config: [
{
key: "text",
label: "要解码的URL编码文本",
type: "input",
defaultValue: "",
icon: "link_off",
},
],
},
],
};

View File

@ -2,7 +2,7 @@ import { fileCommands } from "./fileCommands";
import { networkCommands } from "./networkCommands";
import { systemCommands } from "./systemCommands";
import { notifyCommands } from "./notifyCommands";
import { encodeCommands } from "./encodeCommands";
import { textProcessingCommands } from "./textProcessingCommands";
import { otherCommands } from "./otherCommands";
import { keyCommands } from "./keyCommands";
@ -11,7 +11,7 @@ export const commandCategories = [
networkCommands,
systemCommands,
notifyCommands,
encodeCommands,
textProcessingCommands,
otherCommands,
keyCommands,
];

View File

@ -0,0 +1,249 @@
export const textProcessingCommands = {
label: "文本处理",
icon: "code",
defaultOpened: false,
commands: [
{
value: "quickcomposer.textProcessing.base64Encode",
label: "Base64编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "lock",
},
],
},
{
value: "quickcomposer.textProcessing.base64Decode",
label: "Base64解码",
config: [
{
key: "text",
label: "要解码的Base64文本",
type: "input",
defaultValue: "",
icon: "lock_open",
},
],
},
{
value: "quickcomposer.textProcessing.hexEncode",
label: "十六进制编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "lock",
},
],
},
{
value: "quickcomposer.textProcessing.hexDecode",
label: "十六进制解码",
config: [
{
key: "text",
label: "要解码的十六进制文本",
type: "input",
defaultValue: "",
icon: "lock_open",
},
],
},
{
value: "quickcomposer.textProcessing.urlEncode",
label: "URL编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "link",
},
],
},
{
value: "quickcomposer.textProcessing.urlDecode",
label: "URL解码",
config: [
{
key: "text",
label: "要解码的URL编码文本",
type: "input",
defaultValue: "",
icon: "link_off",
},
],
},
{
value: "quickcomposer.textProcessing.htmlEncode",
label: "HTML编码",
config: [
{
key: "text",
label: "要编码的文本",
type: "input",
defaultValue: "",
icon: "code",
},
],
},
{
value: "quickcomposer.textProcessing.htmlDecode",
label: "HTML解码",
config: [
{
key: "text",
label: "要解码的HTML文本",
type: "input",
defaultValue: "",
icon: "code_off",
},
],
},
{
value: "quickcomposer.textProcessing.reverseString",
label: "字符串反转",
config: [
{
key: "text",
label: "要反转的文本",
type: "input",
defaultValue: "",
icon: "swap_horiz",
},
],
},
{
value: "quickcomposer.textProcessing.replaceString",
label: "字符串替换",
config: [
{
key: "text",
label: "原始文本",
type: "input",
defaultValue: "",
icon: "text_fields",
},
{
key: "oldStr",
label: "要替换的文本",
type: "input",
defaultValue: "",
icon: "find_replace",
},
{
key: "newStr",
label: "替换为",
type: "input",
defaultValue: "",
icon: "text_fields",
},
],
},
{
value: "quickcomposer.textProcessing.substring",
label: "字符串截取",
config: [
{
key: "text",
label: "原始文本",
type: "input",
defaultValue: "",
icon: "text_fields",
},
{
key: "start",
label: "起始位置",
type: "input",
inputType: "number",
defaultValue: "0",
icon: "first_page",
},
{
key: "end",
label: "结束位置",
type: "input",
inputType: "number",
defaultValue: "",
icon: "last_page",
},
],
},
{
value: "quickcomposer.textProcessing.regexExtract",
label: "正则提取",
config: [
{
key: "text",
label: "原始文本",
type: "input",
defaultValue: "",
icon: "text_fields",
},
{
key: "regex",
label: "正则表达式",
type: "input",
defaultValue: "",
icon: "regex",
},
],
},
{
value: "quickcomposer.textProcessing.symmetricCrypto",
label: "对称加解密",
component: "SymmetricCryptoEditor",
},
{
value: "quickcomposer.textProcessing.asymmetricCrypto",
label: "非对称加解密",
component: "AsymmetricCryptoEditor",
},
{
value: "quickcomposer.textProcessing.md5Hash",
label: "MD5哈希",
config: [
{
key: "text",
label: "要哈希的文本",
type: "input",
defaultValue: "",
icon: "enhanced_encryption",
},
],
},
{
value: "quickcomposer.textProcessing.sha256Hash",
label: "SHA256哈希",
config: [
{
key: "text",
label: "要哈希的文本",
type: "input",
defaultValue: "",
icon: "enhanced_encryption",
},
],
},
{
value: "quickcomposer.textProcessing.sm3Hash",
label: "SM3哈希",
config: [
{
key: "text",
label: "要哈希的文本",
type: "input",
defaultValue: "",
icon: "enhanced_encryption",
},
],
},
],
};