正在加载…

JAVA和vue3 RSA加密解密

2025年9月11日 • 分享

2.3k 字-

本文最后更新于 1 年前,文中所描述的信息可能已发生改变。

公司微服务用了那么久,终于到了启用加密请求的这一天,本身内部集成了加密变量,我以为打开开关,这事儿就妥了。但还是年轻了,前端和JAVA单纯RSA加密没问题,问题是切片和解决中文字符集搞了大半天。

ChatGPT给了示例没法用,还是Qoder最后帮我完善的方法,里面base64转来转去的,让我徒手写我还真是没招。我为国内AI打call!

补点基础

想直接要代码,直接往下拉,JAVA和vue都是封装的一个工具文件,直接用就好了。这里先了解下 AESRSA 加密的概念。

🔑 1. AES 加密(Advanced Encryption Standard,高级加密标准)

1.1 定义

AES 是一种 对称加密算法(Symmetric Encryption),由美国国家标准与技术研究院(NIST)在 2001 年发布,替代了早期的 DES(Data Encryption Standard)。
对称加密的特点是:加密和解密使用同一把密钥

1.2 工作原理
  • 明文(Plaintext) + 密钥(Key) → 加密算法 → 密文(Ciphertext)
  • 密文 + 密钥(Key) → 解密算法 → 明文
  • AES 使用分组加密:把数据切分成固定长度的块(block),标准是 128 bit(16 字节)
  • 支持的密钥长度:128 位、192 位、256 位
1.3 加密模式

AES 本身只是一个分组加密算法,需要配合 工作模式(Mode of Operation) 使用,常见有:

  • ECB(Electronic Codebook):每个块单独加密,简单但容易泄露模式。
  • CBC(Cipher Block Chaining):前一个块的密文会影响下一个块,需要初始向量 IV。
  • CFB、OFB、CTR、GCM 等模式,增强安全性和应用灵活性。
1.4 特点

✅ 优点:

  • 计算速度快(适合加密大数据)
  • 安全性高,广泛用于工业界

❌ 缺点:

  • 需要安全地保存密钥(对称密钥分发是难点)
1.5 应用场景
  • HTTPS 中的会话数据加密(TLS 协议里常用 AES-128/256)
  • 数据库、文件加密
  • VPN、磁盘加密软件

🔑 2. RSA 加密(Rivest–Shamir–Adleman)

2.1 定义

RSA 是一种 非对称加密算法(Asymmetric Encryption),由 Ron Rivest、Adi Shamir 和 Leonard Adleman 于 1977 年提出。
非对称加密的特点是:加密和解密使用不同的密钥

  • 公钥(Public Key):可公开,用来加密数据
  • 私钥(Private Key):必须保密,用来解密数据
2.2 工作原理

RSA 基于 大整数质因数分解的数学难题

  • 两个大素数相乘容易
  • 反过来分解非常困难

核心流程:

  1. 生成一对密钥(公钥 + 私钥)
  2. 用公钥加密 → 只有私钥能解密
  3. 反过来,私钥签名 → 只有公钥能验证
2.3 特点

✅ 优点:

  • 不需要提前共享密钥(解决了 AES 的密钥分发问题)
  • 可用于 加密数字签名

❌ 缺点:

  • 运算速度慢(远远慢于 AES)
  • 密文长度受密钥位数限制(例如 1024 位 RSA 最多只能加密 117 字节的明文)
2.4 应用场景
  • HTTPS 中的握手阶段(RSA 用于加密 AES 会话密钥)
  • 数字签名、身份认证
  • 数字证书(CA 体系)

3. AES vs RSA 对比

特性AES(对称)RSA(非对称)
密钥数量1 把(同一密钥加解密)2 把(公钥加密,私钥解密)
速度(适合大数据)(主要加密小数据)
安全性高,但需安全传输密钥高,基于数学难题
常见用途数据传输/存储加密密钥交换、数字签名
缺点密钥分发难性能低,不适合加密大文件

实际应用中的组合

在现实应用中,AES 和 RSA 经常结合使用

  1. 通信双方用 RSA 交换 AES 密钥(解决对称密钥分发问题)。
  2. 后续通信使用 AES 加密大数据(保证性能和安全)。

说明

以上都是AI生成。

RSA的加密长度特性:

  • 1024位 RSA 最多只能加密 117 字节的明文
  • 2048位 RSA 最多只能加密 245 字节的明文

JAVA自己写的分段加密,VUE的也得自己写。

中文在RSA加密不受待见,我试过前端加密前端解没有问题,JAVA加密JAVA解密也没问题,但是他们俩一交互,全是问题。只能先base64,把中文解决了,这事儿才算解决了。

以下工具类没有提供base64处理,也就是json字符串进来你得单独处理base64。如下:

js
import { Base64 } from 'js-base64';
// 加密前 encode
let data = Base64.encode(JSON.stringify(jsonObject));

// 解密后 decode
let data = Base64.decode(encryptData);
java
// 加密前 encode
String data = java.util.Base64.getEncoder().encodeToString(jsonString.getBytes(Charsets.UTF_8));

// 解密后 decode 
String data = new String(java.util.Base64.getDecoder().decode(encrypt));

6. 工具类

VUE3的工具类

javascript
/** rsa.js */

import JSEncrypt from 'jsencrypt';

/**
 * 规范化 Base64(去空白,URL-safe -> 标准,补齐 =)
 */
function normalizeBase64(s) {
  if (!s) return s;
  s = s.replace(/\s+/g, "");           // 去掉换行/空白
  s = s.replace(/-/g, "+").replace(/_/g, "/"); // url-safe -> standard
  // 补齐 =
  while (s.length % 4 !== 0) s += "=";
  return s;
}

/** base64 -> Uint8Array */
function base64ToUint8Array(b64) {
  const binStr = atob(b64);
  const len = binStr.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) bytes[i] = binStr.charCodeAt(i);
  return bytes;
}

/** Uint8Array -> base64 (安全小分块转换以避免 apply 限制) */
function uint8ArrayToBase64(u8arr) {
  const CHUNK = 0x8000; // 32KB chunks (我们实际每块很小,所以只是保险)
  let index = 0;
  let binary = "";
  while (index < u8arr.length) {
    const slice = u8arr.subarray(index, Math.min(index + CHUNK, u8arr.length));
    binary += String.fromCharCode.apply(null, slice);
    index += CHUNK;
  }

  return btoa(binary);
}

const chunkStringByByteLimit=(str, limit)=> {
  const encoder = new TextEncoder();
  const chunks = [];
  let cur = '';
  let curLen = 0;
  for (const ch of str) {
    const chLen = encoder.encode(ch).length;
    if (curLen + chLen > limit) {
      // flush
      chunks.push(cur);
      cur = ch;
      curLen = chLen;
    } else {
      cur += ch;
      curLen += chLen;
    }
  }

  if (cur.length > 0) chunks.push(cur);

  return chunks;
}

// 加密
export const rsaEncrypt = (plainText, publicKeyPem) => {
  // 默认 1024-bit key 的最大明文字节
  const MAX_ENCRYPT_BLOCK = 117; // 如果你的密钥是 2048-bit,请改为 245

  // chunk 明文(按 UTF-8 字节数)
  const parts = chunkStringByByteLimit(plainText, MAX_ENCRYPT_BLOCK);

  // 使用 JSEncrypt 逐块加密(每块返回 base64)
  // eslint-disable-next-line no-undef
  const jse = new JSEncrypt();
  jse.setPublicKey(publicKeyPem);

  const encryptedChunksBase64 = [];
  for (const part of parts) {
    const enc = jse.encrypt(part); // 返回 base64 或 null
    if (!enc) {
      throw new Error('jsencrypt 加密失败,可能公钥格式不对或分块过长');
    }
    encryptedChunksBase64.push(enc);
  }

  // 把每块 Base64 -> Uint8Array,然后拼接所有 bytes
  let totalLen = 0;
  const arrays = encryptedChunksBase64.map(b64 => {
    const u8 = base64ToUint8Array(b64);
    totalLen += u8.length;
    return u8;
  });

  const result = new Uint8Array(totalLen);
  let offset = 0;
  for (const arr of arrays) {
    result.set(arr, offset);
    offset += arr.length;
  }

  // 最后把拼接后的 bytes 再 base64(与 Java 端行为一致)
  return uint8ArrayToBase64(result);
}

/**
 * 分片解密主函数
 * @param {String} fullBase64Cipher 后端返回的整体 Base64 密文(多个密文块拼接后再Base64)
 * @param {String} privateKeyPem 私钥(PEM 格式,包括 -----BEGIN PRIVATE KEY-----)
 * @param {Number} keySizeBits RSA 密钥位数,默认 1024(如果你是 2048,传 2048)
 * @returns {String} 明文(UTF-8 正确)
 */
export function rsaDecrypt(fullBase64Cipher, privateKeyPem, keySizeBits = 1024) {
  if (!fullBase64Cipher) return "";

  // 规范化 base64(去空白、url-safe、补齐)
  const normalized = normalizeBase64(fullBase64Cipher);

  // 把整体 base64 解成 bytes,再按密钥字节长度分片(例如 1024 -> 128 字节)
  const keyByteLen = Math.floor(keySizeBits / 8);
  const cipherBytes = base64ToUint8Array(normalized);

  const decryptor = new JSEncrypt();
  decryptor.setPrivateKey(privateKeyPem);

  let offset = 0;
  const parts = [];

  while (offset < cipherBytes.length) {
    const end = Math.min(offset + keyByteLen, cipherBytes.length);
    const chunk = cipherBytes.subarray(offset, end);

    // 单块重新 base64 编码,传给 jsencrypt.decrypt(它期望单块 base64 密文)
    const chunkB64 = uint8ArrayToBase64(chunk);
    const decrypted = decryptor.decrypt(chunkB64);

    if (decrypted === false || decrypted === null) {
      throw new Error(`RSA 解密失败(chunk offset=${offset})—— 请确认私钥与公钥匹配、分片长度设置是否正确。`);
    }
    
    parts.push(decrypted);
    offset += keyByteLen;
  }

  // 拼接所有分片明文
  return parts.join("");
}

JAVA的工具类

java
public class RsaUtils {

    /**
     * 生成密钥对
     *
     * @return
     */
    public static Map<String, String> generateRasKey() {
        Map<String, String> rs = new HashMap<>();
        try {
            KeyPairGenerator keyPairGen = null;
            keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(1024, new SecureRandom());
            KeyPair keyPair = keyPairGen.generateKeyPair();
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
            String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
            rs.put("public_key", publicKeyString);
            rs.put("private_key", privateKeyString);
        } catch (Exception e) {
            throw new RuntimeException("RsaUtils 生成公钥失败...");
        }
        return rs;
    }

    /**
     * 公钥加密
     *
     * @param str
     * @param publicKey
     * @return 密文
     */
    public static String publicKeyEncrypt(String str, String publicKey) throws Exception {
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        //当长度过长的时候,需要分割后加密 117个字节
        byte[] resultBytes = getMaxResultEncrypt(str, cipher);
        String outStr = Base64.encodeBase64String(resultBytes);
        return outStr;
    }

    /**
     * 分组加密
     *
     * @param str
     * @param cipher
     * @return
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    private static byte[] getMaxResultEncrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
        byte[] inputArray = str.getBytes(StandardCharsets.UTF_8);
        int inputLength = inputArray.length;
        int MAX_ENCRYPT_BLOCK = 117;
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }


    /**
     * 私钥解密
     */
    public static String privateKeyDecrypt(String str, String privateKey) {
        try {
            byte[] decoded = Base64.decodeBase64(privateKey);
            RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            String outStr = new String(getMaxResultDecrypt(str, cipher));
            return outStr;
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 分组解密
     *
     * @param str
     * @param cipher
     * @return
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     */
    private static byte[] getMaxResultDecrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        byte[] inputArray = Base64.decodeBase64(str.getBytes("UTF-8"));
        int inputLength = inputArray.length;
        int MAX_ENCRYPT_BLOCK = 128;
        int offSet = 0;
        byte[] resultBytes = {};
        byte[] cache = {};
        while (inputLength - offSet > 0) {
            if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                offSet += MAX_ENCRYPT_BLOCK;
            } else {
                cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                offSet = inputLength;
            }
            resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
            System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
        }
        return resultBytes;
    }
}
在转载或引用本文时,请务必遵守许可协议并注明来源
远山's Avatar

远山

那远山呼唤我,曾千百次走过。

云存储提供商
热门标签
S3
Silo
MinIO
Docker
电视盒子
AI
Skills
Stitch
Google AI Studio
Trae IDE
claude code
cc-switch
nvidia api
openrouter
ai
coding plan
WiFi6
E2633
Mesh
caddy
acme
群辉
RSA
非对称加密
AES
对称加密
Umami
数据分析
云存储
CDN
七牛云
EdgeOne
本地服务器
Artalk
图床
upgit
静态博客
Valaxy