Skip to content

实现 AES加解密 工具

分类:developer | 标签:AES、加密、解密 AES 对称加解密

2.1 功能说明

AES 对称加解密

2.1.1 使用指南

功能说明

使用对称加密算法对文本进行加密与解密,支持 AESDESTripleDES(3DES)RC4Rabbit。纯浏览器本地计算,不上传任何数据。

算法说明

  • AES:分组密码,当前主流标准,支持 128/192/256 位密钥。
  • DES:分组密码,56 位有效密钥,已不安全,仅用于兼容老系统。
  • TripleDES(3DES):对 DES 做三重加密,安全性优于 DES,性能较低。
  • RC4 / Rabbit:流密码,逐字节生成密钥流异或,不使用分组模式、填充和 IV,因此这三个选项在选中它们时会自动禁用。

密钥方式

  • 口令派生(OpenSSL 兼容):默认方式。口令经 EVP_BytesToKey 派生出密钥, 每次加密都会生成随机盐,因此同样的明文每次密文都不同,密文自带 Salted__ 头, 可与 openssl enc 互通。
  • 密钥直接使用:把输入内容按 UTF-8 或 Hex 直接解析为密钥,无盐、结果确定, 便于与后端 Java/Go/Python 等实现对接。此时 AES 密钥必须是 16 / 24 / 32 字节, IV 必须是 16 字节(DES/3DES 为 8 字节)。

使用场景

敏感信息临时加密、与后端加解密逻辑联调对拍、教学演示。

注意事项

ECB 模式不需要 IV 且相同明文块会产生相同密文块,安全性较差,生产环境请优先使用 CBC。 解密时算法、模式、填充、IV、密钥方式与密文编码必须与加密时完全一致。

2.2 代码实现

2.2.1 组件结构

本工具是一个基于 Vue 3 <script setup> 语法的单文件组件(SFC),统一包裹在 ToolPageShell 组件内,由它提供页面标题、工具 ID、分类与「使用指南」插槽等通用外壳;核心业务逻辑(响应式状态、计算属性、事件处理函数)全部写在 <script setup> 中,输入/输出通过 el-inputel-button 等 Element Plus 组件与用户交互,所有数据均在浏览器本地处理,不会上传服务器。

2.2.2 核心逻辑概览

<script setup> 中定义的主要函数/方法:

  • parseBy()
  • buildOptions()
  • validate()
  • encCfg()
  • decCfg()
  • encrypt()
  • decrypt()
  • copyC()

2.2.3 关键实现代码

以下是该工具的完整 <script setup> 实现(核心代码)。模板(<template>)与样式(<style>)已省略。

vue
import { ref } from 'vue'
import CryptoJS from 'crypto-js'
import ToolPageShell from '@/components/ToolPageShell.vue'

/* ---------------- 算法与选项定义 ---------------- */
const CIPHERS = {
  AES: CryptoJS.AES,
  DES: CryptoJS.DES,
  TripleDES: CryptoJS.TripleDES,
  RC4: CryptoJS.RC4,
  Rabbit: CryptoJS.Rabbit
}
const ALGO_LIST = [
  { label: 'AES', value: 'AES' },
  { label: 'DES', value: 'DES' },
  { label: '3DES', value: 'TripleDES' },
  { label: 'RC4', value: 'RC4' },
  { label: 'Rabbit', value: 'Rabbit' }
]
const PAD_LIST = [
  { label: 'Pkcs7', value: 'Pkcs7' },
  { label: 'Iso97971', value: 'Iso97971' },
  { label: 'AnsiX923', value: 'AnsiX923' },
  { label: 'Iso10126', value: 'Iso10126' },
  { label: 'ZeroPadding', value: 'ZeroPadding' },
  { label: 'NoPadding', value: 'NoPadding' }
]
// RC4 / Rabbit 为流密码,不支持分组模式、填充与 IV
const STREAM_ALGOS = ['RC4', 'Rabbit']
const isStream = a => STREAM_ALGOS.includes(a)
// 分组长度(字节),即 CBC 模式下 IV 的要求长度
const BLOCK_SIZE = { AES: 16, DES: 8, TripleDES: 8 }
const ivPlaceholder = a => (isStream(a) ? '流密码不需要 IV' : `${BLOCK_SIZE[a]} 字节 IV`)

/* ---------------- 加密 tab 状态 ---------------- */
const mode = ref('enc')
const plain = ref('')
const key = ref('')
const cipherAlgo = ref('AES')
const blockMode = ref('CBC')
const padding = ref('Pkcs7')
const iv = ref('0000000000000000')
const keyMode = ref('passphrase')
const keyEnc = ref('utf8')
const cipherEnc = ref('base64')
const cipher = ref('')
const encError = ref('')

/* ---------------- 解密 tab 状态 ---------------- */
const cipherIn = ref('')
const key2 = ref('')
const cipherAlgo2 = ref('AES')
const blockMode2 = ref('CBC')
const padding2 = ref('Pkcs7')
const iv2 = ref('0000000000000000')
const keyMode2 = ref('passphrase')
const keyEnc2 = ref('utf8')
const cipherEnc2 = ref('base64')
const plainOut = ref('')
const error = ref('')

/* ---------------- 公共辅助 ---------------- */
function parseBy(text, enc) {
  return enc === 'hex' ? CryptoJS.enc.Hex.parse(text) : CryptoJS.enc.Utf8.parse(text)
}

function buildOptions(cfg) {
  if (isStream(cfg.algo)) return {}
  const opt = {
    mode: CryptoJS.mode[cfg.blockMode],
    padding: CryptoJS.pad[cfg.padding]
  }
  if (cfg.blockMode !== 'ECB') opt.iv = parseBy(cfg.iv, cfg.keyEnc)
  return opt
}

// 校验密钥 / IV 长度,返回中文错误信息(无问题返回空串)
function validate(cfg) {
  if (!cfg.key) return '请输入' + (cfg.keyMode === 'passphrase' ? '口令' : '密钥')
  if (cfg.keyMode === 'raw') {
    let kw
    try {
      kw = parseBy(cfg.key, cfg.keyEnc)
    } catch {
      return '密钥不是合法的 Hex 字符串'
    }
    if (cfg.algo === 'AES' && ![16, 24, 32].includes(kw.sigBytes)) {
      return `AES 直接使用密钥时长度必须为 16 / 24 / 32 字节,当前为 ${kw.sigBytes} 字节`
    }
    if (kw.sigBytes === 0) return '密钥不能为空'
  }
  if (!isStream(cfg.algo) && cfg.blockMode !== 'ECB') {
    let ivw
    try {
      ivw = parseBy(cfg.iv, cfg.keyEnc)
    } catch {
      return 'IV 不是合法的 Hex 字符串'
    }
    const need = BLOCK_SIZE[cfg.algo]
    if (ivw.sigBytes !== need) {
      return `${cfg.algo} 在 CBC 模式下 IV 必须为 ${need} 字节,当前为 ${ivw.sigBytes} 字节`
    }
  }
  return ''
}

function encCfg() {
  return {
    algo: cipherAlgo.value,
    blockMode: blockMode.value,
    padding: padding.value,
    iv: iv.value,
    key: key.value,
    keyMode: keyMode.value,
    keyEnc: keyEnc.value,
    cipherEnc: cipherEnc.value
  }
}
function decCfg() {
  return {
    algo: cipherAlgo2.value,
    blockMode: blockMode2.value,
    padding: padding2.value,
    iv: iv2.value,
    key: key2.value,
    keyMode: keyMode2.value,
    keyEnc: keyEnc2.value,
    cipherEnc: cipherEnc2.value
  }
}

/* ---------------- 加解密 ---------------- */
function encrypt() {
  encError.value = ''
  cipher.value = ''
  if (!plain.value) return
  const cfg = encCfg()
  const msg = validate(cfg)
  if (msg) { encError.value = msg; return }
  try {
    const keyArg = cfg.keyMode === 'raw' ? parseBy(cfg.key, cfg.keyEnc) : cfg.key
    const cp = CIPHERS[cfg.algo].encrypt(plain.value, keyArg, buildOptions(cfg))
    if (cfg.keyMode === 'passphrase') {
      // 口令派生会生成随机盐,盐封装在 OpenSSL 格式中,必须连同盐一起输出
      const openssl = cp.toString()
      cipher.value = cfg.cipherEnc === 'hex'
        ? CryptoJS.enc.Base64.parse(openssl).toString(CryptoJS.enc.Hex)
        : openssl
    } else {
      cipher.value = cfg.cipherEnc === 'hex'
        ? cp.ciphertext.toString(CryptoJS.enc.Hex)
        : cp.ciphertext.toString(CryptoJS.enc.Base64)
    }
  } catch (e) {
    encError.value = '加密失败:' + e.message
  }
}

function decrypt() {
  error.value = ''
  plainOut.value = ''
  if (!cipherIn.value) return
  const cfg = decCfg()
  const msg = validate(cfg)
  if (msg) { error.value = msg; return }
  try {
    const keyArg = cfg.keyMode === 'raw' ? parseBy(cfg.key, cfg.keyEnc) : cfg.key
    const text = cipherIn.value.trim()
    let input
    if (cfg.keyMode === 'passphrase') {
      input = cfg.cipherEnc === 'hex'
        ? CryptoJS.enc.Hex.parse(text).toString(CryptoJS.enc.Base64)
        : text
    } else {
      const ct = cfg.cipherEnc === 'hex'
        ? CryptoJS.enc.Hex.parse(text)
        : CryptoJS.enc.Base64.parse(text)
      input = CryptoJS.lib.CipherParams.create({ ciphertext: ct })
    }
    const bytes = CIPHERS[cfg.algo].decrypt(input, keyArg, buildOptions(cfg))
    const t = bytes.toString(CryptoJS.enc.Utf8)
    if (!t) { error.value = '解密失败:口令、密文或参数不正确'; return }
    // ZeroPadding / NoPadding 可能残留尾部 \0
    plainOut.value = cfg.padding === 'ZeroPadding' || cfg.padding === 'NoPadding'
      ? t.replace(/\0+$/, '')
      : t
  } catch (e) {
    error.value = '解密失败:' + e.message
  }
}

function copyC() { if (cipher.value) navigator.clipboard?.writeText(cipher.value) }

2.3 效果截图

AES加解密 效果截图

工具访问地址:https://www.i91tools.com/tools/aes-crypto