import NodeRSA from 'node-rsa'; import { writeFile, readFile } from '../utils/fs.promise'; import { defaults } from 'lodash'; export interface CreateKeyOptions { /** * 密钥类型 * @default rsa2 */ type?: 'rsa2' | 'rsa'; /** * 密钥格式 * @default pkcs8 */ scheme?: 'pkcs8' | 'pkcs1'; /** * 公钥保存地址 */ pubPath?: string; /** * 私钥保存地址 */ priPath?: string; } export interface CreateKeyResult { /** * 私钥 */ privateKey?: string; /** * 公钥 */ publicKey?: string; /** * 私钥, 兼容 alipaydev 之前的返回 */ privatePem?: string; /** * 公钥, 兼容 alipaydev 之前的返回 */ publicPem?: string; } /** * 生成密钥 */ export async function createKey(options?: CreateKeyOptions): Promise { const params = defaults(options, { type: 'rsa2', scheme: 'pkcs8', }); const { type, scheme, pubPath, priPath } = params; let keySize; //type 和密钥位数映射 if (type == 'rsa2') { keySize = { b: 2048 }; } else if (type == 'rsa') { keySize = { b: 1024 }; } const key = new NodeRSA(keySize); const keyNamePri = scheme + '-private-pem'; const privateKey = trimKey(key.exportKey(keyNamePri)); const publicKey = trimKey(key.exportKey('pkcs8-public-pem')); if (priPath && pubPath) { await Promise.all([writeFile(pubPath, publicKey), writeFile(priPath, privateKey)]); } return { privateKey, publicKey, privatePem: privateKey, publicPem: publicKey, }; } /** * 密钥匹配检测 */ export function matchKey(options: CreateKeyResult): boolean { const { publicKey, privateKey, publicPem, privatePem } = options; // 兼容 alipaydev const publicStr = publicKey || (publicPem as string); const privateStr = privateKey || (privatePem as string); const pubScheme = checkRsaType(publicStr); const priScheme = checkRsaType(privateStr); let pubKey; let priKey; try { pubKey = new NodeRSA(publicStr, pubScheme); priKey = new NodeRSA(privateStr, priScheme); } catch (e) { return false; } if (!(pubKey.isPublic(true) && !priKey.isPublic(true))) { return false; } //强制都转成pkcs8来对比 return pubKey.exportKey('pkcs8-public-pem') == priKey.exportKey('pkcs8-public-pem'); } export interface ConvertKeyOptions { /** * 密钥格式 * @default pkcs8 */ scheme: 'pkcs8' | 'pkcs1'; /** * 私钥地址 */ priPath: string; } /** * 密钥转换 */ export async function convertKey(options: ConvertKeyOptions): Promise { const { scheme = 'pkcs8', priPath } = options; const content = await readFile(priPath); try { const priKey = new NodeRSA(content, checkRsaType(content)); const keyNamePub = scheme + '-public-pem'; const keyNamePri = scheme + '-private-pem'; const privateKey = trimKey(priKey.exportKey(keyNamePri)); const publicKey = trimKey(priKey.exportKey(keyNamePub)); console.log('已转换 ' + scheme + ' 应用私钥:'); console.log(privateKey); console.log('已转换 ' + scheme + ' 应用公钥:'); console.log(publicKey); } catch (e) { console.log('转换失败'); } } function trimKey(keyContent: string): string { if (keyContent === undefined) { return ''; } return keyContent .replace(/-----BEGIN RSA PRIVATE KEY-----/g, '') .replace(/-----END RSA PRIVATE KEY-----/g, '') .replace(/-----BEGIN PRIVATE KEY-----/g, '') .replace(/-----END PRIVATE KEY-----/g, '') .replace(/-----BEGIN PUBLIC KEY-----/g, '') .replace(/-----END PUBLIC KEY-----/g, '') .replace(/-----BEGIN RSA PUBLIC KEY-----/g, '') .replace(/-----END RSA PUBLIC KEY-----/g, '') .replace(/[\r\n]/g, '') .replace(/\t/g, '') .replace(/\s*/g, ''); } function checkRsaType(keyContent: string | Buffer): string { // 1,原始1024位的私钥 ; 2,pkcs8格式1024位私钥 ; 3,1024位的公钥; // 4,原始2048位的私钥 ; 5,pkcs8格式2048位私钥 ; 6,2048位的公钥; if (keyContent.length >= 810 && keyContent.length <= 820) { return 'pkcs1-private-pem'; } else if (keyContent.length >= 840 && keyContent.length <= 860) { return 'pkcs8-private-pem'; } else if (keyContent.length >= 210 && keyContent.length <= 220) { return 'pkcs8-public-pem'; } else if (keyContent.length >= 1580 && keyContent.length <= 1600) { return 'pkcs1-private-pem'; } else if (keyContent.length >= 1620 && keyContent.length <= 1630) { return 'pkcs8-private-pem'; } else if (keyContent.length >= 390 && keyContent.length <= 400) { return 'pkcs8-public-pem'; } else { return 'unknown'; } }