Node.js RSA加密解密与Java实现的对比

近期在对接一个后端服务时,要求请求过程中需要使用RSA加密。请求体需要使用公钥加密,返回体需要使用私钥解密。

接口方提供了Java代码作为参考, 本以为很简单,只是做一下代码翻译。开干的过程才发现没有想象中的容易。 时间主要花在了对加密这一块不熟悉,然后很难判断证书使用不了的原因。整体网上资料也很少, 最后只能通过同时debug Java代码以及Nodejs代码,逐步排查变量。辛酸。。

省流总结:

  1. Java代码在读取公钥私钥的过程中, 不需要添加行首行尾, 类似 -----BEGIN PRIVATE KEY----- 以及 -----END PRIVATE KEY-----, 而Node.js 需要.
  2. Java默认使用的padding 与nodejs不同, nodejs在加载秘钥时需要指定padding. 具体padding 可以从crypto.constants.RSA_PKCS1_PADDING 获取,使用下面类似代码。
    const encryptedBlock = crypto.publicEncrypt(
          {
            key: publicKey,
            padding: crypto.constants.RSA_PKCS1_PADDING,
          },
          dataBuffer.slice(offset, inputLen + offset),
        );
    

接下来是完整的请求保证代码。

  import axios from 'axios';
import crypto from 'crypto';
import md5 from 'md5';

interface RequestBody {
account: string;
data: string;
ts: number;
}

interface SignedRequestBody extends RequestBody {
sign: string;
}

interface SignedResponseBody {
account: string;
encrypt: boolean;
data: string;
ts: number;
sign: string;
}

const RESERVE_BYTES = 11;

export class RequestExecutor {
public host: string;
public publicKey = ‘’;
public privateKey = ‘’;
public account = ‘’;

public decryptBlock = 256;
public encryptBlock = this.decryptBlock - RESERVE_BYTES;

public constructor(
host: string,
account: string,
publickKey: string,
privateKey: string,
) {
this.host = host;
this.account = account;
this.publicKey = publickKey;
this.privateKey = privateKey;
}

public async request<T = any>(api: string, data: Record<string, any> = {}) {
const res = await axios.request<SignedResponseBody>({
url: ${this.host}${api},
method: ‘POST’,
data: this.getRequestBody(data),
});

const resBody = this.decryptBody&lt;T&gt;(res.data.data);

return resBody;

}

private getRequestBody(body: Record<string, string>) {
const requestBody: RequestBody = {
ts: Date.now(),
account: this.account,
data: this.encryptBody(body),
};

const sign = this.getSignature(requestBody);
return {
  ...requestBody,
  sign,
} as SignedRequestBody;

}

private getSignature(body: RequestBody) {
let plainText = ‘’;
Object.keys(body)
.sort()
.forEach(
key =>
(plainText = ${plainText}${key}${body[key as keyof RequestBody]}),
);

return md5(plainText);

}

private encryptBody(body: Record<string, string>) {
const publicKey = -----BEGIN PUBLIC KEY-----\n${this.publicKey}\n-----END PUBLIC KEY-----;

if (body !== undefined) {
  const dataBuffer = Buffer.from(JSON.stringify(body), 'utf-8');

  const dataLen = dataBuffer.length;

  let encryptedBuffer = Buffer.alloc(0);

  for (let offset = 0; offset &lt; dataLen; offset += this.encryptBlock) {
    // block大小: encryptBlock 或 剩余字节数
    let inputLen = dataLen - offset;
    if (inputLen &gt; this.encryptBlock) {
      inputLen = this.encryptBlock;
    }

    const encryptedBlock = crypto.publicEncrypt(
      {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PADDING,
      },
      dataBuffer.slice(offset, inputLen + offset),
    );

    encryptedBuffer = Buffer.concat(
      [encryptedBuffer, encryptedBlock],
      encryptedBuffer.length + encryptedBlock.length,
    );
  }

  return encryptedBuffer.toString('base64');
}

throw new Error('Set request data before request');

}

private decryptBody<T>(encryptedStr: string) {
const privateKey = \n-----BEGIN PRIVATE KEY-----\n${this.privateKey}\n-----END PRIVATE KEY-----\n;

const encryptedBuffer = Buffer.from(encryptedStr, 'base64');

const dataLen = encryptedBuffer.length;

let decryptedBuffer = Buffer.alloc(0);

for (let offset = 0; offset &lt; dataLen; offset += this.decryptBlock) {
  // block大小: encryptBlock 或 剩余字节数
  let inputLen = dataLen - offset;
  if (inputLen &gt; this.decryptBlock) {
    inputLen = this.decryptBlock;
  }

  const dencryptedBlock = crypto.privateDecrypt(
    {
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_PADDING,
    },
    encryptedBuffer.slice(offset, inputLen + offset),
  );

  decryptedBuffer = Buffer.concat(
    [decryptedBuffer, dencryptedBlock],
    decryptedBuffer.length + dencryptedBlock.length,
  );
}

return JSON.parse(decryptedBuffer.toString('utf8')) as T;

}
}


Node.js RSA加密解密与Java实现的对比
https://www.ifelse.cc/2022/03/10/2022-03-10_java-nodejs-jia-mi-jie-mi/
作者
Kevin Law
发布于
2022年3月10日
许可协议