crypto.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. /**
  2. * Relay 加密与安全工具
  3. */
  4. import crypto from 'crypto';
  5. const RSA_KEY_SIZE = 2048;
  6. export interface KeyPair {
  7. publicKey: string;
  8. privateKey: string;
  9. }
  10. export function generateKeyPair(): KeyPair {
  11. const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  12. modulusLength: RSA_KEY_SIZE,
  13. publicKeyEncoding: { type: 'spki', format: 'pem' },
  14. privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
  15. });
  16. return { publicKey, privateKey };
  17. }
  18. export function encryptWithPublicKey(publicKeyPem: string, plaintext: string): string {
  19. const publicKey = crypto.createPublicKey(publicKeyPem);
  20. const aesKey = crypto.randomBytes(32);
  21. const iv = crypto.randomBytes(12);
  22. const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
  23. const ciphertext = Buffer.concat([
  24. cipher.update(Buffer.from(plaintext, 'utf8')),
  25. cipher.final(),
  26. ]);
  27. const wrappedKey = crypto.publicEncrypt(
  28. { key: publicKey, oaepHash: 'sha256' },
  29. aesKey,
  30. );
  31. const envelope = {
  32. v: 2,
  33. alg: 'RSA-OAEP-256+A256GCM',
  34. key: wrappedKey.toString('base64'),
  35. iv: iv.toString('base64'),
  36. tag: cipher.getAuthTag().toString('base64'),
  37. ciphertext: ciphertext.toString('base64'),
  38. };
  39. return `v2:${Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64')}`;
  40. }
  41. export function hashApiSecret(secret: string): string {
  42. return crypto.createHmac('sha256', 'qiwei-relay-secret-salt').update(secret).digest('hex');
  43. }
  44. export function generateApiCredentials(): { apiKey: string; apiSecret: string } {
  45. return {
  46. apiKey: 'qk_' + crypto.randomBytes(16).toString('hex'),
  47. apiSecret: crypto.randomBytes(32).toString('hex'),
  48. };
  49. }
  50. export function generateRelaySecret(): string {
  51. return crypto.randomBytes(32).toString('hex');
  52. }
  53. export function verifyHmacSignature(secret: string, rawBody: string, signature: string): boolean {
  54. try {
  55. const expected = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
  56. return timingSafeEqual(expected, signature);
  57. } catch {
  58. return false;
  59. }
  60. }
  61. export function verifyBearerToken(secret: string, authHeader: string | undefined): boolean {
  62. if (!authHeader) return false;
  63. const match = authHeader.match(/^Bearer\s+(.+)$/i);
  64. const provided = (match ? match[1] : authHeader).trim();
  65. if (!provided) return false;
  66. return timingSafeEqual(provided, secret);
  67. }
  68. function timingSafeEqual(a: string, b: string): boolean {
  69. if (a.length !== b.length) return false;
  70. try {
  71. return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
  72. } catch {
  73. return false;
  74. }
  75. }
  76. export function sha256Hash(input: string): string {
  77. return crypto.createHash('sha256').update(input).digest('hex');
  78. }