diff --git a/apps/admin/app/dashboard/servers/form-schema.ts b/apps/admin/app/dashboard/servers/form-schema.ts index a211701..84b8f5f 100644 --- a/apps/admin/app/dashboard/servers/form-schema.ts +++ b/apps/admin/app/dashboard/servers/form-schema.ts @@ -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 = { 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', diff --git a/apps/admin/app/dashboard/servers/generate/index.ts b/apps/admin/app/dashboard/servers/generate/index.ts index 7a5ddda..3cb29f3 100644 --- a/apps/admin/app/dashboard/servers/generate/index.ts +++ b/apps/admin/app/dashboard/servers/generate/index.ts @@ -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'; diff --git a/apps/admin/app/dashboard/servers/generate/mlkem768.ts b/apps/admin/app/dashboard/servers/generate/mlkem768.ts new file mode 100644 index 0000000..7e4261c --- /dev/null +++ b/apps/admin/app/dashboard/servers/generate/mlkem768.ts @@ -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); diff --git a/apps/admin/app/dashboard/servers/generate/mlkem768x25519plus.ts b/apps/admin/app/dashboard/servers/generate/mlkem768x25519plus.ts deleted file mode 100644 index 73914f9..0000000 --- a/apps/admin/app/dashboard/servers/generate/mlkem768x25519plus.ts +++ /dev/null @@ -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), - }; -} diff --git a/apps/admin/app/dashboard/servers/generate/reality-key.ts b/apps/admin/app/dashboard/servers/generate/reality-key.ts deleted file mode 100644 index 6869e99..0000000 --- a/apps/admin/app/dashboard/servers/generate/reality-key.ts +++ /dev/null @@ -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; -} diff --git a/apps/admin/app/dashboard/servers/generate/short-id.ts b/apps/admin/app/dashboard/servers/generate/short-id.ts new file mode 100644 index 0000000..98b5070 --- /dev/null +++ b/apps/admin/app/dashboard/servers/generate/short-id.ts @@ -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; +} diff --git a/apps/admin/app/dashboard/servers/generate/random.ts b/apps/admin/app/dashboard/servers/generate/uid.ts similarity index 100% rename from apps/admin/app/dashboard/servers/generate/random.ts rename to apps/admin/app/dashboard/servers/generate/uid.ts diff --git a/apps/admin/app/dashboard/servers/generate/util.ts b/apps/admin/app/dashboard/servers/generate/util.ts new file mode 100644 index 0000000..96a9057 --- /dev/null +++ b/apps/admin/app/dashboard/servers/generate/util.ts @@ -0,0 +1,6 @@ +export function toB64Url(bytes: Uint8Array) { + return btoa(String.fromCharCode(...bytes)) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/g, ''); +} diff --git a/apps/admin/app/dashboard/servers/generate/x25519.ts b/apps/admin/app/dashboard/servers/generate/x25519.ts new file mode 100644 index 0000000..1ec8d72 --- /dev/null +++ b/apps/admin/app/dashboard/servers/generate/x25519.ts @@ -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) }; +} diff --git a/apps/admin/package.json b/apps/admin/package.json index b3180f9..4da437d 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -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", diff --git a/bun.lockb b/bun.lockb index 39843e5..a6c0067 100755 Binary files a/bun.lockb and b/bun.lockb differ