🐛 fix: Refactor key generation logic and update dependencies for ML-KEM-768 integration

This commit is contained in:
web 2025-09-16 04:32:16 -07:00
parent 5ce5d62b22
commit b8f630f8ab
11 changed files with 80 additions and 83 deletions

View File

@ -1,6 +1,10 @@
import { z } from 'zod';
import { generatePassword, generateRealityKeyPair, generateRealityShortId } from './generate';
import { generateVlessX25519Pair } from './generate/mlkem768x25519plus';
import {
generateMLKEM768KeyPair,
generatePassword,
generateRealityKeyPair,
generateRealityShortId,
} from './generate';
export const protocols = [
'shadowsocks',
@ -700,10 +704,10 @@ export const PROTOCOL_FIELDS: Record<string, FieldConfig[]> = {
placeholder: (t) => t('encryption_private_key_placeholder'),
group: 'encryption',
generate: {
function: () => generateVlessX25519Pair(),
function: generateMLKEM768KeyPair,
updateFields: {
encryption_private_key: 'privateKeyB64',
encryption_password: 'passwordB64',
encryption_private_key: 'privateKey',
encryption_password: 'publicKey',
},
},
condition: (p) => p.encryption === 'mlkem768x25519plus',

View File

@ -1,6 +1,4 @@
export { generatePassword } from './random';
export {
generateRealityKeyPair,
generateRealityShortId,
publicKeyFromPrivate,
} from './reality-key';
export { generateMLKEM768KeyPair } from './mlkem768';
export { generateRealityShortId } from './short-id';
export { generatePassword } from './uid';
export { generateRealityKeyPair } from './x25519';

View File

@ -0,0 +1,34 @@
import mlkem from 'mlkem-wasm';
import { toB64Url } from './util';
export async function generateMLKEM768KeyPair() {
const mlkemKeyPair = await mlkem.generateKey({ name: 'ML-KEM-768' }, true, [
'encapsulateBits',
'decapsulateBits',
]);
const mlkemPublicKeyRaw = await mlkem.exportKey('raw-public', mlkemKeyPair.publicKey);
const mlkemPrivateKeyRaw = await mlkem.exportKey('raw-seed', mlkemKeyPair.privateKey);
return {
publicKey: toB64Url(new Uint8Array(mlkemPublicKeyRaw)),
privateKey: toB64Url(new Uint8Array(mlkemPrivateKeyRaw)),
};
}
const test = await generateMLKEM768KeyPair();
console.log('生成的密钥信息:');
console.log('私钥长度:', test.privateKey.length);
console.log('公钥长度:', test.publicKey.length);
console.log(test);
// 从 VLESS 配置字符串中提取的密钥
const extractedKeys = {
decryptionKey:
'B2qLcDHhiztvBaB4BhMCnU-fM-axE4DZowMK9TIvL5qma2M5fVAmFswPdfej2N1SmlJa5ppidC1ksHLVfoEvjw',
encryptionKey:
'FgEI5sTIzBgZS4psemcdGDCRb5JvdDFqzOeVvLJYEmmZFSgxG1SkYxx8DUO-txGqvYdhFruja-ZzmIQoGOCQEMd0RbK4BhYRm4jE96GXTytu7Hi7pYo9-2SEuKC10righ2ceqis0LoggubajmAFvkop6igZfcmEA3MJ9aHpjGiUszDWy0pA99WY7c1ebTom8xQetW2J2OnkrA_UE8Cuy8AhMPvSBQrsFNmBqnttfVlmKzPS3RWBkIpAZIie1XMGk76nDEXgEsBulEhmYO2Mc-oRfirQfGmYfD1ybYBs9gAKgzQixTrkJtOuRU4GjkwQpxuyeVuww8Pin19IzAlVpUdeEJPVlRHwQDNJ__YsZDJB6rSIaY4tM4ysSs3N-1mmYvkVs1WSa0uy14KRheqMIPfsw_KxLBvp2_ZdZeQIrW3dDxgOuxXWip-g78desyONwwkx0bQK-JDcELHEH0TVmTOe4mSqW1fPI6jJI_ZChmQwBxZKpp2RN2xKmw6W3z4ETdUQDTZgePEkXDveneltNHrGT73WJQ7uEs6hwfXwD2vGcuFx6DKybfYAzgWh9t4IM3mBI7OiGqDItigqIDgaBF6LPTXwby3VxgfEVXXqXzMVdc0BL4da1BPGF-lAtvVJtbpBp7_O_JlB1wWajv8eLCpKjRiKGQey7oLZyRROQ3loNFyRDBjoBVSa2etCmFRCV1wegIBqmJRImJQxPsIV_kkIxeWmPfUIik1GpSTtrWkAFCXZAl4oSFiNhLwlmW_g_s_glGeqcZBNAkiw-OlssPESmqLti-bSJPbmoJMNE0poJXCYX27YyPKVyCtJKM5pzOkUBqMIVsxliU4N2IaWHb8uMYKW8U0tQ8aaeR3Bf5ll0fza78aY3lSQw7At21XkN9LhugzWv-_CPGZQpStGCWtJl5dChLPlmZbRddmekp7UEfXAPW6ONyNrFZ9WUvBCwsdmXcChTXwkg-FIMFHpU25c6IwWqyveuZrQmpvZYEQSgyWhsUBMPywI1vqiLfuhnaqBNU9wPbGA0IrG-w9UGpQErl9ssqPdZRjaIbiM-PKKooehp34QzU3ENj5h944gC4yHMkMzOPUaFl8YUWwmkGCsTNnJyq7NLucTOdQSUsLM2QWEkxWk9c6YyQkwx2mUsR1eGmrsbo8BGK6ppbgotpzMjGZfPOQRdHYh0lzGQ28HGetJ97Vei8Vxxl2u4j6CfHTPET4EHZ_uTuPtRIaaMegKOtUgyKqKj4BqsB5tatIECz8N1H_LP5qlRvUxYqrU-JikuRCoAdfl7VFYLLhe_80cny2UoWFpJ1iovprnALDbIIKZ03LG0OXI8dfEolQijO1xhwoUb7Ci3Xma-wKyEWHhSw0e600SRT0RhDuOh5hVg4KVLILUDDuMIJQyiPRBW3qwAMCk1SzCeXvFqbWFU-TQnVoLGNboaygQ6aumgP_B4DBcmEdyVqAMHIOdAPENvLauALKSiBHVjEHgeB7KpVCGsIGOjdCgb4UmKdsxnXBxC7peXspcmGgmHL-VU6KdMhwHwq1mYeNVEzeshb5mWYj5fgysp_e5U--geYKefs5Y',
};
console.log('提取的密钥信息:');
console.log('私钥长度:', extractedKeys.decryptionKey.length);
console.log('公钥长度:', extractedKeys.encryptionKey.length);

View File

@ -1,23 +0,0 @@
import { x25519 } from '@noble/curves/ed25519';
const toB64Url = (u8: Uint8Array) =>
(typeof Buffer !== 'undefined'
? Buffer.from(u8).toString('base64')
: btoa(String.fromCharCode(...u8))
)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '');
export type VlessX25519Pair = {
passwordB64: string;
privateKeyB64: string;
};
export function generateVlessX25519Pair(): VlessX25519Pair {
const { secretKey, publicKey } = x25519.keygen();
return {
passwordB64: toB64Url(publicKey),
privateKeyB64: toB64Url(secretKey),
};
}

View File

@ -1,49 +0,0 @@
import { x25519 } from '@noble/curves/ed25519.js';
function toB64Url(bytes: Uint8Array) {
return btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '');
}
function fromB64Url(s: string) {
const b64 = s
.replace(/-/g, '+')
.replace(/_/g, '/')
.padEnd(Math.ceil(s.length / 4) * 4, '=');
const bin = atob(b64);
return new Uint8Array([...bin].map((c) => c.charCodeAt(0)));
}
/**
* Generate a Reality key pair
* @returns An object containing the private and public keys in base64url format
*/
export function generateRealityKeyPair() {
const { secretKey, publicKey } = x25519.keygen();
return { privateKey: toB64Url(secretKey), publicKey: toB64Url(publicKey) };
}
/**
* Derive public key from private key
* @param privateKeyB64Url Private key in base64url format
* @returns Public key in base64url format
*/
export function publicKeyFromPrivate(privateKeyB64Url: string) {
return toB64Url(x25519.getPublicKey(fromB64Url(privateKeyB64Url)));
}
/**
* Generate a short ID for Reality
* @returns A random hexadecimal string of length 2, 4, 6, 8, 10, 12, 14, or 16
*/
export function generateRealityShortId() {
const hex = '0123456789abcdef';
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
const idx = Math.floor(Math.random() * lengths.length);
const len = lengths[idx] ?? 16;
let out = '';
for (let i = 0; i < len; i++) {
out += hex.charAt(Math.floor(Math.random() * hex.length));
}
return out;
}

View File

@ -0,0 +1,15 @@
/**
* Generate a short ID for Reality
* @returns A random hexadecimal string of length 2, 4, 6, 8, 10, 12, 14, or 16
*/
export function generateRealityShortId() {
const hex = '0123456789abcdef';
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
const idx = Math.floor(Math.random() * lengths.length);
const len = lengths[idx] ?? 16;
let out = '';
for (let i = 0; i < len; i++) {
out += hex.charAt(Math.floor(Math.random() * hex.length));
}
return out;
}

View File

@ -0,0 +1,6 @@
export function toB64Url(bytes: Uint8Array) {
return btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '');
}

View File

@ -0,0 +1,11 @@
import { x25519 } from '@noble/curves/ed25519';
import { toB64Url } from './util';
/**
* Generate a Reality key pair
* @returns An object containing the private and public keys in base64url format
*/
export function generateRealityKeyPair() {
const { secretKey, publicKey } = x25519.keygen();
return { privateKey: toB64Url(secretKey), publicKey: toB64Url(publicKey) };
}

View File

@ -18,6 +18,7 @@
"ahooks": "^3.9.4",
"axios": "^1.11.0",
"js-yaml": "^4.1.0",
"mlkem-wasm": "^0.0.6",
"nanoid": "^5.1.5",
"next": "^15.5.2",
"next-intl": "^3.26.3",

BIN
bun.lockb

Binary file not shown.