正在加载…

JAVA和vue3 RSA加密解密

2025年9月11日 • 分享

2.3k 字-

公司微服务用了那么久,终于到了启用加密请求的这一天,本身内部集成了加密变量,我以为打开开关,这事儿就妥了。但还是年轻了,前端和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

远山

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

云存储提供商
热门标签
WiFi6
E2633
Mesh
caddy
acme
群辉
RSA
非对称加密
AES
对称加密
Docker
Umami
数据分析
云存储
CDN
七牛云
EdgeOne
S3
本地服务器
Artalk
图床
upgit
静态博客
Valaxy