前后端RSA双向加解密踩坑记

未完成文章

博主原创文章,转载请说明出处,但是不说我也管不了你🏳️

前言

正文

正文标题1

正文副标题1

crypto web api

https://developer.mozilla.org/zh-CN/docs/Web/API/SubtleCrypto/decrypt#%E8%AF%AD%E6%B3%95

前端加密解密正常

//start

// 将 ArrayBuffer 转换为 Base64 字符串

function arrayBufferToBase64(buffer:any) {

let binary = '';

const bytes = new Uint8Array(buffer);

for (let i = 0; i < bytes.byteLength; i++) {

binary += String.fromCharCode(bytes[i]);

}

return window.btoa(binary);

}

// 将二进制字符串转换为 ArrayBuffer

function str2ab(str:string) {

const buf = new ArrayBuffer(str.length);

const view = new Uint8Array(buf);

for (let i = 0; i < str.length; i++) {

view[i] = str.charCodeAt(i);

}

return buf;

}

// 将 PEM 公钥转换为 SPKI 格式(去掉头尾标识)

function pemToSpki(base64PublicKey:any) {

// 去掉公钥的头尾部分

const publicKeyPem = base64PublicKey.replace(/-----BEGIN PUBLIC KEY-----/g, '').replace(/-----END PUBLIC KEY-----/g, '').trim();

return atob(publicKeyPem); // 解码 Base64 内容

}

// 将 Base64 编码的公钥转换为 CryptoKey 对象

async function importPublicKey(base64PublicKey:string) {

const binaryDerString = window.atob(base64PublicKey); // Base64 解码

const binaryDer = str2ab(binaryDerString); // 将二进制字符串转换为 ArrayBuffer

// 导入公钥

const publicKey = await crypto.subtle.importKey(

'spki', // 这里的 spki 表示公钥

binaryDer, // 公钥的 ArrayBuffer

{

name: 'RSA-OAEP',

hash: 'SHA-256', // 哈希算法

},

true, // 是否可以导出

['encrypt'] // 密钥用途

);

return publicKey;

}

// 使用公钥进行 RSA 加密

async function rsaEncrypt(plaintext:string, publicKey:CryptoKey) {

const encoder = new TextEncoder();

const encodedData = encoder.encode(plaintext);

// 使用公钥进行加密

const encryptedData = await crypto.subtle.encrypt(

{

name: 'RSA-OAEP',

},

publicKey,

encodedData

);

return arrayBufferToBase64(encryptedData); // 转换为 Base64 字符串返回

}

// 将 Base64 编码的私钥转换为 CryptoKey 对象

async function importPrivateKey(base64PrivateKey) {

const binaryDerString = atob(base64PrivateKey); // 将 Base64 解码

const binaryDer = str2ab(binaryDerString); // 将二进制字符串转为 ArrayBuffer

// 导入私钥

const privateKey = await crypto.subtle.importKey(

'pkcs8', // 私钥格式

binaryDer, // 私钥的 ArrayBuffer

{

name: 'RSA-OAEP',

hash: 'SHA-256', // 默认的哈希算法

},

true, // 是否可以导出

['decrypt'] // 密钥用途

);

return privateKey;

}

// 将 Base64 编码的字符串转换为 ArrayBuffer

function base64ToArrayBuffer(base64) {

const binaryString = atob(base64);

const len = binaryString.length;

const buffer = new ArrayBuffer(len);

const view = new Uint8Array(buffer);

for (let i = 0; i < len; i++) {

view[i] = binaryString.charCodeAt(i);

}

console.log(buffer)

return buffer;

}

// 使用私钥进行 RSA 解密

async function rsaDecrypt(encryptedData, privateKeyBase64) {

const binaryData = base64ToArrayBuffer(encryptedData); // 将 Base64 编码的密文转换为 ArrayBuffer

const privateKey = await importPrivateKey(privateKeyBase64)

// 使用私钥进行解密

const decryptedData = await crypto.subtle.decrypt(

{

name: 'RSA-OAEP',

// Web Crypto API 默认使用 PKCS1Padding 填充方式

},

privateKey,

binaryData

);

// 解密后的数据是一个 ArrayBuffer,可以将其转换为字符串

const decoder = new TextDecoder();

return decoder.decode(decryptedData); // 返回解密后的字符串

}

//加密工具

async function rsaE(s:string,p:string):Promise<string> {

const pulicKey = await importPublicKey('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmZr6zJX28C8adGE7/pxYRZajhdlPXXX6TiVPyKABaN397WfDOfnYGmqDZu+o4xxCyeqckzp0kFTamEQ0BeZXV1W9RdOaspHBnl5dZUbsJpRuBaCnCJyMKeipBEaIZke9GmI8QuzUYuFFM2KV5MF4yj4N+sVLfZWdgAse54JpFHZ53VI7Kmywdcf/U9wNAKKvMTMwgHawv23q7hwGbHEu8omNM/d6OnFPHGWVzxfRxJ/F1Q2BJSRJgkt5Zwvu1r1TWMeHDKgaq6FZBE6C7LE/YeCHacU6ngwrdZZxiYtYoUvCPCPany4zOGojD5+6vJG3x+jZOp7bM+xthz1OdhX3OQIDAQAB');

// const result = await rsaEncrypt(s,pulicKey);

const result = 'AcMAET+gaIJu0VULNKPRW+vldyk9C5dJCzpmlLjtQHqXM59Y5bMxq87q1ogvxRel3FmSlf4lM8RyGSjPg0dJdQ1LjBdEx2lH003FXOdR0WQkggH54j8YkarwTjfM4Qsgfo2RvZevGhrt6uDViekdgL1vetRShoz54oSz6xpWibsJYhrG4eK1WriLLAHATn5QCILGAD5pnA/ARYR/9lAEFwO4OG1nvPAMv0ey9/cQo00kpR1v976y5RpWYv2A1IBX06cJgmG0+qHqu/6zT1SwqdBZm2FFe5MM5pB4qkvCZtpnM/K5VjVq1rumrC/7xOr0frzHhEZjqKxu0G/9Gw2TSQ=='

//const result = 'Sf+aCzDUZ3cIzpeRnLOvoWfUuHH3DlRjk1VnECs5J/F4UIH0+tlYqM8BRHC1NFrjeyTk5k2g+8Oxfj9yN3N3U/EODi7/eK9dF/Q0RlyPXOypUilPtj9l1ig7bRcEv/p4WQO8Bw7V55sIlT2dNGSiiauWcK6oCx9t8VkE9bGTNR772Ojdsbn2Mn3dbUa6u2sv6iUAfoNVIetzrR6U+eOV+Z/O8qP1u9a4alBUtxmJ6YuVijcV5tvDLccziWTwOLIgUYna6A9YgKpi+Ykk2NWy4SOd1SOTbG98+sV/xaotshLVi4lj8vWp0Z9CmgPNl6gOAMknHWWyB2R6wpKb/dZ0BA=='

const cc = await rsaDecrypt(result,'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZmvrMlfbwLxp0YTv+nFhFlqOF2U9ddfpOJU/IoAFo3f3tZ8M5+dgaaoNm76jjHELJ6pyTOnSQVNqYRDQF5ldXVb1F05qykcGeXl1lRuwmlG4FoKcInIwp6KkERohmR70aYjxC7NRi4UUzYpXkwXjKPg36xUt9lZ2ACx7ngmkUdnndUjsqbLB1x/9T3A0Aoq8xMzCAdrC/beruHAZscS7yiY0z93o6cU8cZZXPF9HEn8XVDYElJEmCS3lnC+7WvVNYx4cMqBqroVkEToLssT9h4IdpxTqeDCt1lnGJi1ihS8I8I9qfLjM4aiMPn7q8kbfH6Nk6ntsz7G2HPU52Ffc5AgMBAAECggEAA2/+ERZBj5Q0Rrq205621H7Xo7sOXUByxK0t1QbspSbDpByUeD+MvCGaVXNDT6rOHVPqY1qVaXX2qt3q3p9uILKW4OFIhtq/bx58P1QYHJBpimdzGXycX+M4phN4gbZ1B0o/wD0pzxksuebIxTTlsbv0BuLDqtiubMNC8qoe1SH9PUDMSUqf0oYAEF718a9mCiMFl5I0Xw+6Hg4ecznYG5hbaK+5/Ph19BVDl2FXY6BlYNvg3PjwHnsZCI/rfj0FZbgtzefS0AKZ4Wwzza3s/pvNWBShbJkrkMr/JV6nkH2EWOYtgZblQf5oVBSLNqmByrfI9u2md0K5QdKITqk8QQKBgQDKsH8a0rocfy+qBrXziTx8UaiCb6UjXt7955DBSxsL2YPGCTXpcXZaVyLZ58tdUnWw6lTCGpL16EhqzIl5/lWczA5dpnNYxns1FTjDyOmQBmx8j8+lR0yob03flidVnHPq7VwkTxbZn5rE09i+fsO3YxIOaHVl5WjsORiyvPYceQKBgQDCAY0wsOiPRKv/np2tTpT1+IN2K+jMoIkibrADEXHVFZTSQ+O5o50VRd3Kpk/rsPl9iYSJ6q515KNV4SUr4KDaytmUjvaOvLvbtaS1JURvtK8dOLNzIXM5+0w5ehzqco5AtQpdAWhu+hI66Tcl7Et3ua3OeJj4hMmgrDJrNT6AwQKBgQC3LJhBym0NcdfGhbgLtlumUjnCX1lTRNqrhYlpVhHEPLf80bgaF0MJU7QYT4WW+f2Ie81Lea6Uc252rhAT4sjGIhddhR66/Oy9xUb+kOSbF2MVkvXp/SyHD8fFpzI3CWQszIO4AI268KVy+azoQzFm2y6nPLnT82ZGDTeH2INmqQKBgBVB8EOOjtzr7GDE84gC3UWaZmUzaH3LdVtFf5nGdo+fnr/vWmuD5vlmv2euE4HC0YOU5uXpxhX66H7HfbgyE84FfhPOGmwHeBoZ7CNBIx+OT8XG6brKxoJq6648mCCETtv+OlV4iwXl9stntHg+TQKgYNAH3NMWUbV+SMQAgInBAoGBAKP2SXkuHI/ZUFIIvIMwr67TpHWLMtgAl8laV2sjuTX7f9I3Yj+RQvwxaWPvSUGZXrsx9jTsMe6KZbqEZi+ksism5ayC6DujNve5gZDGZBvfT4lnjh0XPU1mGxS8bM6e4h+T5Rvra5Z/AByk3FvgkY9EDgFAqSch7/SmPieVqrQP')

console.log(cc)

console.log(result)

return cc;

}

const y = rsaE('123456','1');

后端加密解密正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public static final Integer PUBLIC_CODE = 0;
public static final Integer PRIVATE_CODE = 1;
/**
* rsa非对称加密算法,获取公钥私钥对.
* @return map形式公钥私钥对,键值:PUBLIC_CODE、PRIVATE_CODE
*/
public static Map<Integer,String> string2RSA4KeyPair() throws NoSuchAlgorithmException {
// 初始化 Key 生成器,指定算法类型为 RSA
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
// 密钥长度为 2048 位,最小为 512
keyPairGenerator.initialize(2048);
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
// 获取私钥
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<Integer,String> keyMap = new HashMap<>();
keyMap.put(PUBLIC_CODE, Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded()));
keyMap.put(PRIVATE_CODE, Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded()));
return keyMap;
}

/**
* rsa非对称加密,加密.
*
* @param puKey 公钥
* @param content 待加密内容
* @return 输出加密后的密文
*/
public static String RSAEncode(String puKey, String content) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//先用BASE64转码秘钥
byte[] bytesKey = Base64.getDecoder().decode(puKey);
System.out.println(ArrayUtil.toString(bytesKey));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytesKey);
PublicKey publicKey = keyFactory.generatePublic(keySpec);

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(content.getBytes()));
}

/**
* rsa非对称加密,解密.
*
* @param prKey 私钥
* @param content 加密后的内容
* @return 解密后的内容
*/
public static String RSADecode(String prKey,String content) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//先用BASE64转码秘钥
byte[] bytesKey = Base64.getDecoder().decode(prKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytesKey);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
//再BASE64转码密文
byte[] bytesContent = Base64.getDecoder().decode(content);
System.out.println(ArrayUtil.toString(bytesContent));

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);

return new String(cipher.doFinal(bytesContent),StandardCharsets.UTF_8);
}

前端后端对相同内容加密后的base64编码长度相同

目前可能性最大的问题所在:前端后端加密解密时所使用的填充方式或者其他具体细节不一样,前端目前没有找到方式选项(默认OAEP),后端可选:RSA/ECB/OAEPWithSHA1AndMGF1Padding、RSA/ECB/PKCS1Padding、RSA/ECB/OAEPWithSHA-256AndMGF1Padding

附录

附录1

附录2

参考资料

  1. 作者、其他主要作者...,题名[文献类型标识]:引用页码,出版地:出版者,获取和访问路径,文献更新日期或者出版年 ↩︎