245 lines
7.3 KiB
JavaScript
245 lines
7.3 KiB
JavaScript
const crypto = require('crypto');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
class KeyPairGenerator {
|
||
constructor(options = {}) {
|
||
this.options = {
|
||
modulusLength: 2048, // 2048位在安全性和性能间取得平衡
|
||
publicKeyEncoding: {
|
||
type: 'spki',
|
||
format: 'pem'
|
||
},
|
||
privateKeyEncoding: {
|
||
type: 'pkcs8',
|
||
format: 'pem',
|
||
cipher: 'aes-256-cbc', // 可选加密私钥
|
||
passphrase: options.passphrase || '' // 私钥密码
|
||
},
|
||
...options
|
||
};
|
||
}
|
||
|
||
// 生成密钥对
|
||
generateKeyPair() {
|
||
return new Promise((resolve, reject) => {
|
||
crypto.generateKeyPair('rsa', this.options, (err, publicKey, privateKey) => {
|
||
if (err) {
|
||
reject(err);
|
||
return;
|
||
}
|
||
resolve({ publicKey, privateKey });
|
||
});
|
||
});
|
||
}
|
||
|
||
// 保存密钥到文件
|
||
saveKeyToFile(key, filename, directory = './keys') {
|
||
if (!fs.existsSync(directory)) {
|
||
fs.mkdirSync(directory, { recursive: true });
|
||
}
|
||
|
||
const filePath = path.join(directory, filename);
|
||
fs.writeFileSync(filePath, key);
|
||
console.log(`✅ ${filename} 已保存到: ${filePath}`);
|
||
return filePath;
|
||
}
|
||
|
||
// 验证密钥对
|
||
validateKeyPair(publicKey, privateKey, passphrase = '') {
|
||
try {
|
||
// 使用公钥加密测试数据
|
||
const testData = 'test-signature-validation';
|
||
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(testData));
|
||
|
||
// 使用私钥解密
|
||
const decrypted = crypto.privateDecrypt(
|
||
{ key: privateKey, passphrase },
|
||
encrypted
|
||
);
|
||
|
||
return decrypted.toString() === testData;
|
||
} catch (error) {
|
||
console.error('❌ 密钥对验证失败:', error.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 生成完整的密钥对文件
|
||
async generateAndSaveKeyPair(outputDir = './keys', keyName = 'app') {
|
||
try {
|
||
console.log('🔐 正在生成 RSA 密钥对...');
|
||
|
||
// 生成密钥对
|
||
const { publicKey, privateKey } = await this.generateKeyPair();
|
||
|
||
// 验证密钥对
|
||
console.log('🔍 验证密钥对...');
|
||
const isValid = this.validateKeyPair(publicKey, privateKey, this.options.privateKeyEncoding.passphrase);
|
||
|
||
if (!isValid) {
|
||
throw new Error('密钥对验证失败');
|
||
}
|
||
|
||
console.log('✅ 密钥对验证成功');
|
||
|
||
// 保存文件
|
||
const timestamp = new Date().toISOString().split('T')[0]+"-"+(new Date()).getTime();
|
||
const publicKeyFile = this.saveKeyToFile(
|
||
publicKey,
|
||
`${keyName}-public-key-${timestamp}.pem`,
|
||
outputDir
|
||
);
|
||
|
||
const privateKeyFile = this.saveKeyToFile(
|
||
privateKey,
|
||
`${keyName}-private-key-${timestamp}.pem`,
|
||
outputDir
|
||
);
|
||
|
||
// 生成配置文件示例
|
||
this.generateConfigExample(publicKey, privateKey, outputDir, keyName);
|
||
|
||
return {
|
||
publicKey,
|
||
privateKey,
|
||
publicKeyFile,
|
||
privateKeyFile,
|
||
isValid
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('❌ 生成密钥对失败:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 生成配置文件示例
|
||
generateConfigExample(publicKey, privateKey, outputDir, keyName) {
|
||
const frontendConfig = `
|
||
// 前端配置 (JavaScript/TypeScript)
|
||
const RSA_PUBLIC_KEY = \`${publicKey}\`;
|
||
|
||
// 或者从文件导入
|
||
// import publicKey from './${keyName}-public-key.pem';
|
||
`;
|
||
|
||
const backendConfig = `
|
||
// 后端配置 (Go)
|
||
package config
|
||
|
||
const (
|
||
RSAPrivateKey = \`${privateKey}\`
|
||
)
|
||
|
||
// 或者从环境变量读取
|
||
// privateKey := os.Getenv("RSA_PRIVATE_KEY")
|
||
`;
|
||
|
||
const envExample = `
|
||
# 环境变量示例
|
||
RSA_PRIVATE_KEY="你的私钥内容"
|
||
RSA_PUBLIC_KEY="你的公钥内容"
|
||
KEY_PASSPHRASE="你的私钥密码(如果有)"
|
||
`;
|
||
|
||
const timestamp = new Date().toISOString().split('T')[0]+"-"+(new Date()).getTime();
|
||
this.saveKeyToFile(frontendConfig.trim(), 'frontend-config-example-'+timestamp+'.js', outputDir);
|
||
this.saveKeyToFile(backendConfig.trim(), 'backend-config-example-'+timestamp+'.go', outputDir);
|
||
this.saveKeyToFile(envExample.trim(), '.env.example', outputDir);
|
||
|
||
console.log('📝 配置文件示例已生成');
|
||
}
|
||
|
||
// 显示密钥信息
|
||
displayKeyInfo(publicKey, privateKey) {
|
||
const publicKeyInfo = crypto.createPublicKey(publicKey);
|
||
const privateKeyInfo = crypto.createPrivateKey({
|
||
key: privateKey,
|
||
passphrase: this.options.privateKeyEncoding.passphrase
|
||
});
|
||
|
||
console.log('\n🔑 密钥信息:');
|
||
console.log('──────────────────────────────');
|
||
console.log(`算法: ${publicKeyInfo.asymmetricKeyType}`);
|
||
console.log(`模数长度: ${publicKeyInfo.asymmetricKeySize} 位`);
|
||
console.log(`格式: PEM`);
|
||
|
||
if (this.options.privateKeyEncoding.passphrase) {
|
||
console.log(`私钥加密: ${this.options.privateKeyEncoding.cipher}`);
|
||
} else {
|
||
console.log('⚠️ 私钥未加密,建议在生产环境中使用加密私钥');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 命令行界面
|
||
function main() {
|
||
const args = process.argv.slice(2);
|
||
const outputDir = args[0] || './keys';
|
||
const keyName = args[1] || 'app';
|
||
const useEncryption = args.includes('--encrypt');
|
||
const passphrase = process.env.KEY_PASSPHRASE || '';
|
||
|
||
const generator = new KeyPairGenerator({
|
||
privateKeyEncoding: {
|
||
type: 'pkcs8',
|
||
format: 'pem',
|
||
...(useEncryption && {
|
||
cipher: 'aes-256-cbc',
|
||
passphrase: passphrase
|
||
})
|
||
}
|
||
});
|
||
|
||
console.log(`
|
||
🔐 RSA 密钥对生成器
|
||
──────────────────────────────
|
||
输出目录: ${outputDir}
|
||
密钥名称: ${keyName}
|
||
私钥加密: ${useEncryption ? '是' : '否'}
|
||
密钥长度: 2048 位 (平衡安全性与性能)
|
||
`);
|
||
|
||
if (useEncryption && !passphrase) {
|
||
console.log('❌ 请设置 KEY_PASSPHRASE 环境变量来加密私钥');
|
||
console.log(' 例如: KEY_PASSPHRASE=your-secret-password node generate-keys.js');
|
||
process.exit(1);
|
||
}
|
||
|
||
generator.generateAndSaveKeyPair(outputDir, keyName)
|
||
.then((result) => {
|
||
generator.displayKeyInfo(result.publicKey, result.privateKey);
|
||
|
||
console.log('\n🎉 密钥对生成完成!');
|
||
console.log('──────────────────────────────');
|
||
console.log('📁 生成的文件:');
|
||
console.log(` 公钥: ${result.publicKeyFile}`);
|
||
console.log(` 私钥: ${result.privateKeyFile}`);
|
||
console.log(` 配置文件示例: ${outputDir}/`);
|
||
|
||
console.log('\n💡 使用建议:');
|
||
console.log(' • 将公钥用于前端加密');
|
||
console.log(' • 将私钥安全地存储在后端');
|
||
console.log(' • 定期轮换密钥(建议每1-2年)');
|
||
console.log(' • 不要将私钥提交到版本控制系统');
|
||
|
||
if (!useEncryption) {
|
||
console.log('\n⚠️ 安全警告:');
|
||
console.log(' 当前私钥未加密,建议在生产环境中使用加密私钥');
|
||
console.log(' 重新运行并添加 --encrypt 参数和 KEY_PASSPHRASE 环境变量');
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.error('❌ 生成失败:', error.message);
|
||
process.exit(1);
|
||
});
|
||
}
|
||
|
||
// 如果直接运行此文件
|
||
if (require.main === module) {
|
||
main();
|
||
}
|
||
|
||
module.exports = KeyPairGenerator;
|