ซอร์สที่เผยแพร่
ดาวน์โหลด ZIP ของซอร์ส
CH-J Tools
เรียกดูไดเรกทอรีและไฟล์ของรุ่นแอปพลิเคชันที่ระบุ
CH-J / Server Manager
406 ไฟล์
core-20261024T135205Z-7c487d719de2
วันที่เพิ่ม: · วันที่บิลด์:
1
"use strict";3
const crypto = require("node:crypto");4
const fs = require("node:fs");5
const path = require("node:path");7
const TYPES = new Set(["ed25519", "rsa", "ecdsa", "dsa", "rsa1"]);8
const PRIVATE_KEY_FORMATS = new Set(["pem", "openssh", "ppk"]);9
const BITS = Object.freeze({ rsa: [2048, 3072, 4096], ecdsa: [256, 384, 521], dsa: [1024], rsa1: [1024] });10
const CURVES = Object.freeze({11
256: ["prime256v1", "ecdsa-sha2-nistp256", "nistp256", 32],12
384: ["secp384r1", "ecdsa-sha2-nistp384", "nistp384", 48],13
521: ["secp521r1", "ecdsa-sha2-nistp521", "nistp521", 66]14
});16
function decodeBase64Url(value) {17
const source = String(value || "").replace(/-/g, "+").replace(/_/g, "/");18
return Buffer.from(source.padEnd(Math.ceil(source.length / 4) * 4, "="), "base64");19
}21
function sshUint32(value) {22
const result = Buffer.alloc(4); result.writeUInt32BE(Number(value) >>> 0); return result;23
}25
function sshString(value) {26
const data = Buffer.isBuffer(value) ? value : Buffer.from(String(value), "utf8");27
return Buffer.concat([sshUint32(data.length), data]);28
}30
function stripLeadingZeroBytes(value) {31
let data = Buffer.from(value || []);32
while (data.length > 1 && data[0] === 0) data = data.subarray(1);33
return data;34
}36
function sshMpint(value) {37
let data = stripLeadingZeroBytes(value);38
if (!data.length || data.every((byte) => byte === 0)) data = Buffer.alloc(0);39
else if (data[0] & 0x80) data = Buffer.concat([Buffer.from([0]), data]);40
return sshString(data);41
}43
function comment(value) {44
return String(value || "").replace(/[\r\n\t]+/g, " ").trim().slice(0, 200);45
}47
function normalize(payload = {}) {48
const type = String(payload.type || "ed25519").toLowerCase();49
if (!TYPES.has(type)) throw new Error("Unsupported key type.");50
if ((type === "dsa" || type === "rsa1") && payload.legacyConfirmed !== true) throw new Error("Legacy key generation requires explicit confirmation.");51
const allowed = BITS[type] || [];52
const defaults = { rsa: 4096, ecdsa: 256, dsa: 1024, rsa1: 1024 };53
const bits = allowed.includes(Number(payload.bits)) ? Number(payload.bits) : (defaults[type] || 0);54
const passphrase = String(payload.passphrase || "");55
if (passphrase.length > 1024 || passphrase.includes("\0")) throw new Error("Invalid key passphrase.");56
const requestedFormat = String(payload.privateKeyFormat || "pem").toLowerCase();57
if (!PRIVATE_KEY_FORMATS.has(requestedFormat)) throw new Error("Unsupported private key format.");58
return { type, bits, passphrase, privateKeyFormat: requestedFormat, comment: comment(payload.comment) };59
}61
function generatePair(type, bits) {62
if (type === "ed25519") return crypto.generateKeyPairSync("ed25519");63
if (type === "rsa" || type === "rsa1") return crypto.generateKeyPairSync("rsa", { modulusLength: bits, publicExponent: 0x10001 });64
if (type === "ecdsa") return crypto.generateKeyPairSync("ec", { namedCurve: CURVES[bits][0] });65
return generateDsaPair();66
}68
// ---------- DSA (legacy) ----------69
// Electron's BoringSSL cannot generate DSA keys (generateKeyPairSync("dsa") fails with "Key generation job failed"),70
// but it can import them. The domain parameters and the key are therefore generated here (FIPS 186-4 A.1.1.2 style71
// probable primes from the platform CSPRNG and Miller-Rabin of checkPrimeSync) and imported as a normal KeyObject,72
// so the PEM/OpenSSH/PPK export path is the same as for every other key type.73
function modPow(base, exponent, modulus) {74
let result = 1n; let value = base % modulus; let power = exponent;75
while (power > 0n) {76
if (power & 1n) result = (result * value) % modulus;77
value = (value * value) % modulus; power >>= 1n;78
}79
return result;80
}81
function randomBits(bits) {82
const bytes = crypto.randomBytes(Math.ceil(bits / 8));83
bytes[0] &= 0xff >> (bytes.length * 8 - bits);84
return BigInt(`0x${bytes.toString("hex")}`);85
}86
function bitLength(value) { return value.toString(2).length; }87
function generateDsaNumbers(L = 1024, N = 160) {88
for (let round = 0; round < 64; round += 1) {89
const q = crypto.generatePrimeSync(N, { bigint: true });90
if (bitLength(q) !== N) continue;91
const twoQ = 2n * q; const top = 1n << BigInt(L - 1);92
for (let counter = 0; counter < 4 * L; counter += 1) {93
const X = randomBits(L) | top;94
const p = X - ((X % twoQ) - 1n);95
if (p < top || !crypto.checkPrimeSync(p, { checks: 64 })) continue;96
const e = (p - 1n) / q;97
let g = 1n;98
for (let h = 2n; g === 1n && h < p - 1n; h += 1n) g = modPow(h, e, p);99
let x = 0n;100
while (x < 1n || x >= q) x = randomBits(N);101
return { p, q, g, x, y: modPow(g, x, p) };102
}103
}104
throw new Error("DSA parameters could not be generated.");105
}106
function derLength(length) {107
if (length < 0x80) return Buffer.from([length]);108
const bytes = []; for (let value = length; value > 0; value = Math.floor(value / 256)) bytes.unshift(value & 0xff);109
return Buffer.from([0x80 | bytes.length, ...bytes]);110
}111
function der(tag, content) { return Buffer.concat([Buffer.from([tag]), derLength(content.length), content]); }112
function derBigInt(value) {113
let hex = value.toString(16); if (hex.length % 2) hex = `0${hex}`;114
let bytes = Buffer.from(hex, "hex"); if (bytes[0] & 0x80) bytes = Buffer.concat([Buffer.from([0]), bytes]);115
return der(0x02, bytes);116
}117
const DSA_OID = Buffer.from([0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x01]);118
function generateDsaPair() {119
const { p, q, g, x, y } = generateDsaNumbers();120
const algorithm = der(0x30, Buffer.concat([DSA_OID, der(0x30, Buffer.concat([derBigInt(p), derBigInt(q), derBigInt(g)]))]));121
const pkcs8 = der(0x30, Buffer.concat([derBigInt(0n), algorithm, der(0x04, derBigInt(x))]));122
const privateKey = crypto.createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" });123
const publicKey = crypto.createPublicKey(privateKey);124
// The public value derived by the crypto library must match y = g^x mod p.125
if (BigInt(`0x${dsaPublicNumbers(publicKey).y.toString("hex")}`) !== y) throw new Error("DSA self-check failed.");126
return { privateKey, publicKey };127
}129
// ---------- passwords ----------130
// Character sets as requested for the Key Generator; digits are 1-9 (no 0, avoids confusion with O).131
const PASSWORD_SETS = Object.freeze({132
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",133
lower: "abcdefghijklmnopqrstuvwxyz",134
digits: "123456789",135
special: "-@)\u00a8\u00a7'=+"136
});137
const PASSWORD_LENGTH = Object.freeze({ min: 4, max: 70 });138
const PASSWORD_GROUP = 5;139
// Uniform choice with crypto.randomInt; with grouping every 5th character is a fixed "-" (xxxx-yyyy-cccc-) and "-" is140
// not used as a random character. Each selected set appears at least once (rejection sampling keeps it uniform).141
function generatePassword(options = {}) {142
const length = Number(options.length);143
if (!Number.isInteger(length) || length < PASSWORD_LENGTH.min || length > PASSWORD_LENGTH.max) throw new Error(`Password length must be ${PASSWORD_LENGTH.min}-${PASSWORD_LENGTH.max} characters.`);144
const grouped = options.grouped === true;145
const sets = Object.keys(PASSWORD_SETS).filter((key) => options[key] === true)146
.map((key) => [...(grouped ? PASSWORD_SETS[key].replace("-", "") : PASSWORD_SETS[key])]).filter((set) => set.length);147
if (!sets.length) throw new Error("Select at least one character set.");148
const pool = [...new Set(sets.flat())];149
const separator = (index) => grouped && (index + 1) % PASSWORD_GROUP === 0;150
const slots = Array.from({ length }, (_value, index) => !separator(index)).filter(Boolean).length;151
if (slots < sets.length) throw new Error(`The length leaves ${slots} random characters, but ${sets.length} character sets are selected.`);152
for (let attempt = 0; attempt < 10000; attempt += 1) {153
const chars = Array.from({ length: slots }, () => pool[crypto.randomInt(pool.length)]);154
if (!sets.every((set) => chars.some((char) => set.includes(char)))) continue;155
let next = 0;156
const password = Array.from({ length }, (_value, index) => (separator(index) ? "-" : chars[next++])).join("");157
return { password, length, randomCharacters: slots, poolSize: pool.length, entropyBits: Math.floor(slots * Math.log2(pool.length)) };158
}159
throw new Error("The password could not be generated with these settings.");160
}162
function readDerLength(buffer, offset) {163
if (offset >= buffer.length) throw new Error("Invalid DER length.");164
const first = buffer[offset];165
if ((first & 0x80) === 0) return { length: first, nextOffset: offset + 1 };166
const bytes = first & 0x7f;167
if (!bytes || offset + 1 + bytes > buffer.length) throw new Error("Invalid DER length bytes.");168
let length = 0;169
for (let index = 0; index < bytes; index += 1) length = (length * 256) + buffer[offset + 1 + index];170
if (!Number.isSafeInteger(length)) throw new Error("Unsupported DER length size.");171
return { length, nextOffset: offset + 1 + bytes };172
}174
function readDerElement(buffer, offset = 0) {175
if (!Buffer.isBuffer(buffer) || offset < 0 || offset >= buffer.length) throw new Error("Invalid DER input.");176
const lengthInfo = readDerLength(buffer, offset + 1);177
const valueStart = lengthInfo.nextOffset; const valueEnd = valueStart + lengthInfo.length;178
if (valueEnd > buffer.length) throw new Error("Invalid DER element bounds.");179
return { tag: buffer[offset], valueStart, valueEnd, nextOffset: valueEnd };180
}182
function derInteger(buffer, element) {183
if (element.tag !== 0x02) throw new Error("Expected DER INTEGER.");184
return stripLeadingZeroBytes(buffer.subarray(element.valueStart, element.valueEnd));185
}187
function dsaParameters(buffer, algorithm) {188
let cursor = algorithm.valueStart;189
const oid = readDerElement(buffer, cursor); cursor = oid.nextOffset;190
const params = readDerElement(buffer, cursor);191
if (oid.tag !== 0x06 || params.tag !== 0x30 || params.nextOffset !== algorithm.valueEnd) throw new Error("Invalid DSA parameters.");192
cursor = params.valueStart;193
const p = readDerElement(buffer, cursor); cursor = p.nextOffset;194
const q = readDerElement(buffer, cursor); cursor = q.nextOffset;195
const g = readDerElement(buffer, cursor); cursor = g.nextOffset;196
if (cursor !== params.valueEnd) throw new Error("Invalid DSA parameter payload.");197
return { p: derInteger(buffer, p), q: derInteger(buffer, q), g: derInteger(buffer, g) };198
}200
function dsaPublicNumbers(publicKey) {201
const buffer = Buffer.from(publicKey.export({ type: "spki", format: "der" }));202
const root = readDerElement(buffer); let cursor = root.valueStart;203
const algorithm = readDerElement(buffer, cursor); cursor = algorithm.nextOffset;204
const subject = readDerElement(buffer, cursor);205
if (root.tag !== 0x30 || root.nextOffset !== buffer.length || algorithm.tag !== 0x30 || subject.tag !== 0x03 || subject.nextOffset !== root.valueEnd) throw new Error("Invalid DSA SPKI structure.");206
const encoded = buffer.subarray(subject.valueStart + 1, subject.valueEnd);207
const y = readDerElement(encoded);208
if (buffer[subject.valueStart] !== 0 || y.nextOffset !== encoded.length) throw new Error("Invalid DSA public value.");209
return { ...dsaParameters(buffer, algorithm), y: derInteger(encoded, y) };210
}212
function dsaPrivateNumbers(privateKey) {213
const buffer = Buffer.from(privateKey.export({ type: "pkcs8", format: "der" }));214
const root = readDerElement(buffer); let cursor = root.valueStart;215
const version = readDerElement(buffer, cursor); cursor = version.nextOffset;216
const algorithm = readDerElement(buffer, cursor); cursor = algorithm.nextOffset;217
const octet = readDerElement(buffer, cursor);218
if (root.tag !== 0x30 || root.nextOffset !== buffer.length || version.tag !== 0x02 || algorithm.tag !== 0x30 || octet.tag !== 0x04 || octet.nextOffset !== root.valueEnd) throw new Error("Invalid DSA PKCS#8 structure.");219
const encoded = buffer.subarray(octet.valueStart, octet.valueEnd); const x = readDerElement(encoded);220
if (x.nextOffset !== encoded.length) throw new Error("Invalid DSA private value.");221
return { ...dsaParameters(buffer, algorithm), x: derInteger(encoded, x) };222
}224
function keyNumbers(type, pair) {225
if (type === "dsa") return { ...dsaPublicNumbers(pair.publicKey), ...dsaPrivateNumbers(pair.privateKey) };226
const privateJwk = pair.privateKey.export({ format: "jwk" });227
const publicJwk = pair.publicKey.export({ format: "jwk" });228
if (type === "ed25519") return { seed: decodeBase64Url(privateJwk.d), publicRaw: decodeBase64Url(publicJwk.x) };229
if (type === "rsa" || type === "rsa1") return Object.fromEntries(["n", "e", "d", "p", "q", "qi"].map((key) => [key, stripLeadingZeroBytes(decodeBase64Url(privateJwk[key]))]));230
return { d: stripLeadingZeroBytes(decodeBase64Url(privateJwk.d)), x: decodeBase64Url(publicJwk.x), y: decodeBase64Url(publicJwk.y) };231
}233
function publicMaterial(type, bits, numbers, keyComment) {234
const suffix = keyComment ? ` ${keyComment}` : "";235
if (type === "ed25519") {236
const algorithm = "ssh-ed25519";237
const blob = Buffer.concat([sshString(algorithm), sshString(numbers.publicRaw)]);238
return { algorithm, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };239
}240
if (type === "rsa" || type === "rsa1") {241
if (type === "rsa1") {242
const exponent = BigInt(`0x${numbers.e.toString("hex")}`).toString();243
const modulus = BigInt(`0x${numbers.n.toString("hex")}`).toString();244
return { algorithm: "ssh1-rsa", blob: Buffer.concat([sshMpint(numbers.e), sshMpint(numbers.n)]), line: `${bits} ${exponent} ${modulus}${suffix}` };245
}246
const algorithm = "ssh-rsa"; const blob = Buffer.concat([sshString(algorithm), sshMpint(numbers.e), sshMpint(numbers.n)]);247
return { algorithm, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };248
}249
if (type === "dsa") {250
const algorithm = "ssh-dss";251
const blob = Buffer.concat([sshString(algorithm), sshMpint(numbers.p), sshMpint(numbers.q), sshMpint(numbers.g), sshMpint(numbers.y)]);252
return { algorithm, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };253
}254
const [, algorithm, curve, size] = CURVES[bits];255
const x = numbers.x.length === size ? numbers.x : Buffer.concat([Buffer.alloc(size - numbers.x.length), numbers.x]);256
const y = numbers.y.length === size ? numbers.y : Buffer.concat([Buffer.alloc(size - numbers.y.length), numbers.y]);257
const point = Buffer.concat([Buffer.from([4]), x, y]);258
const blob = Buffer.concat([sshString(algorithm), sshString(curve), sshString(point)]);259
return { algorithm, curve, point, blob, line: `${algorithm} ${blob.toString("base64")}${suffix}` };260
}262
function pemBlock(name, raw) {263
const base64 = Buffer.from(raw).toString("base64"); const lines = [];264
for (let index = 0; index < base64.length; index += 70) lines.push(base64.slice(index, index + 70));265
return `-----BEGIN ${name}-----\n${lines.join("\n")}\n-----END ${name}-----\n`;266
}268
function openSshPrivate(type, numbers, material, keyComment) {269
const check = crypto.randomBytes(4); let fields;270
if (type === "ed25519") fields = [sshString(material.algorithm), sshString(numbers.publicRaw), sshString(Buffer.concat([numbers.seed, numbers.publicRaw]))];271
else if (type === "rsa") fields = [sshString(material.algorithm), sshMpint(numbers.n), sshMpint(numbers.e), sshMpint(numbers.d), sshMpint(numbers.qi), sshMpint(numbers.p), sshMpint(numbers.q)];272
else if (type === "dsa") fields = [sshString(material.algorithm), sshMpint(numbers.p), sshMpint(numbers.q), sshMpint(numbers.g), sshMpint(numbers.y), sshMpint(numbers.x)];273
else fields = [sshString(material.algorithm), sshString(material.curve), sshString(material.point), sshMpint(numbers.d)];274
let privateSection = Buffer.concat([check, check, ...fields, sshString(keyComment)]);275
const paddingLength = (8 - (privateSection.length % 8)) % 8;276
if (paddingLength) privateSection = Buffer.concat([privateSection, Buffer.from(Array.from({ length: paddingLength }, (_unused, index) => index + 1))]);277
const raw = Buffer.concat([278
Buffer.from("openssh-key-v1\0", "ascii"), sshString("none"), sshString("none"), sshString(Buffer.alloc(0)),279
sshUint32(1), sshString(material.blob), sshString(privateSection)280
]);281
return pemBlock("OPENSSH PRIVATE KEY", raw);282
}284
function ppkPrivateBlob(type, numbers) {285
if (type === "ed25519") return sshString(Buffer.concat([numbers.seed, numbers.publicRaw]));286
if (type === "rsa" || type === "rsa1") return Buffer.concat([sshMpint(numbers.d), sshMpint(numbers.p), sshMpint(numbers.q), sshMpint(numbers.qi)]);287
if (type === "dsa") return sshMpint(numbers.x);288
return sshMpint(numbers.d);289
}291
function ppkV2(type, material, numbers, keyComment) {292
const publicBlob = material.blob; const privateBlob = ppkPrivateBlob(type, numbers);293
const macData = Buffer.concat([sshString(material.algorithm), sshString("none"), sshString(keyComment), sshString(publicBlob), sshString(privateBlob)]);294
const macKey = crypto.createHash("sha1").update("putty-private-key-file-mac-key", "utf8").digest();295
const privateMac = crypto.createHmac("sha1", macKey).update(macData).digest("hex");296
const lines = (buffer) => buffer.toString("base64").match(/.{1,64}/g) || [""];297
const publicLines = lines(publicBlob); const privateLines = lines(privateBlob);298
return [`PuTTY-User-Key-File-2: ${material.algorithm}`, "Encryption: none", `Comment: ${keyComment}`, `Public-Lines: ${publicLines.length}`, ...publicLines, `Private-Lines: ${privateLines.length}`, ...privateLines, `Private-MAC: ${privateMac}`].join("\n") + "\n";299
}301
function exportPem(privateKey, passphrase) {302
const options = { type: "pkcs8", format: "pem" };303
if (passphrase) Object.assign(options, { cipher: "aes-256-cbc", passphrase });304
return String(privateKey.export(options));305
}307
function chooseFormat(input) {308
if (input.type === "rsa1") {309
if (input.passphrase) throw new Error("SSH1 RSA PPK export cannot be protected by a passphrase. Remove it or use a modern key type.");310
return { format: "ppk", notice: input.privateKeyFormat === "ppk" ? "SSH1 RSA je zastaralý formát." : "SSH1 RSA se ukládá jako PuTTY PPK v2." };311
}312
if (input.passphrase && input.privateKeyFormat !== "pem") {313
return { format: "pem", notice: `${input.privateKeyFormat === "ppk" ? "PPK v2" : "OpenSSH"} export s passphrase není ve vestavěném backendu dostupný; byl bezpečně použit šifrovaný PEM/PKCS#8.` };314
}315
return { format: input.privateKeyFormat, notice: input.privateKeyFormat === "ppk" ? "PPK v2 je určen hlavně pro kompatibilitu se staršími nástroji PuTTY." : "" };316
}318
class KeyGeneratorService {319
constructor(options = {}) {320
this.selectSavePath = options.selectSavePath;321
this.writeClipboard = options.writeClipboard;322
this.generations = new Map();323
this.passwords = new Map();324
}326
generate(payload) {327
const input = normalize(payload); const pair = generatePair(input.type, input.bits);328
const numbers = keyNumbers(input.type, pair); const material = publicMaterial(input.type, input.bits, numbers, input.comment);329
const privateKeyPem = exportPem(pair.privateKey, input.passphrase);330
const selected = chooseFormat(input);331
const privateKeyOpenSSH = selected.format === "openssh" ? openSshPrivate(input.type, numbers, material, input.comment) : "";332
const privateKeyPpk = selected.format === "ppk" ? ppkV2(input.type, material, numbers, input.comment) : "";333
const privateKey = selected.format === "openssh" ? privateKeyOpenSSH : selected.format === "ppk" ? privateKeyPpk : privateKeyPem;334
const generationId = crypto.randomUUID();335
const baseName = { ed25519: "id_ed25519", rsa: "id_rsa", ecdsa: "id_ecdsa", dsa: "id_dsa", rsa1: "id_rsa1" }[input.type];336
const suggestedName = selected.format === "ppk" ? `${baseName}.ppk` : baseName;337
const result = {338
generationId, type: input.type, bits: input.bits, privateKeyFormat: selected.format, privateKey,339
privateKeyPem, privateKeyOpenSSH, privateKeyPpk, publicKey: material.line, publicKeyOpenSsh: material.line,340
suggestedName, legacy: input.type === "dsa" || input.type === "rsa1", notice: selected.notice341
};342
this.generations.set(generationId, { ...result, createdAt: Date.now() });343
while (this.generations.size > 20) this.generations.delete(this.generations.keys().next().value);344
return { ...result };345
}347
// Password generator for the Key Generator plugin. Never logged or persisted; kept in memory for at most 10 minutes348
// only so the plugin can ask Core to copy it (plugin windows have no clipboard permission and get no general349
// clipboard capability: only passwords generated here can be copied).350
generatePassword(options) {351
const result = generatePassword(options);352
const passwordId = crypto.randomUUID();353
this.passwords.set(passwordId, { password: result.password, createdAt: Date.now() });354
while (this.passwords.size > 20) this.passwords.delete(this.passwords.keys().next().value);355
return { ...result, passwordId };356
}358
copyPassword(passwordId) {359
const entry = this.passwords.get(String(passwordId || ""));360
if (!entry || Date.now() - entry.createdAt > 10 * 60 * 1000) { this.passwords.delete(String(passwordId || "")); throw new Error("Generated password expired. Generate it again."); }361
if (typeof this.writeClipboard !== "function") throw new Error("Clipboard is unavailable.");362
this.writeClipboard(entry.password);363
return { copied: true };364
}366
async save(generationId) {367
const generated = this.generations.get(String(generationId || ""));368
if (!generated || Date.now() - generated.createdAt > 10 * 60 * 1000) throw new Error("Generated key expired. Generate it again.");369
if (typeof this.selectSavePath !== "function") throw new Error("Key save dialog is unavailable.");370
const selected = await this.selectSavePath(generated.suggestedName);371
if (!selected || selected.canceled || !selected.path) return { canceled: true };372
const privatePath = path.resolve(selected.path); const publicPath = `${privatePath}.pub`;373
await fs.promises.writeFile(privatePath, generated.privateKey, { encoding: "utf8", mode: 0o600 });374
await fs.promises.writeFile(publicPath, generated.publicKey, { encoding: "utf8", mode: 0o644 });375
await Promise.allSettled([fs.promises.chmod(privatePath, 0o600), fs.promises.chmod(publicPath, 0o644)]);376
return { canceled: false, privatePath, publicPath, privateKeyFormat: generated.privateKeyFormat };377
}378
}380
module.exports = { BITS, KeyGeneratorService, PASSWORD_LENGTH, PASSWORD_SETS, PRIVATE_KEY_FORMATS, generateDsaNumbers, generatePassword, normalize };SHA-256
19ce990542667c49ff61ebc2d41ea24da84c30ba37568c7d91575a0235e42346CH-J Proprietary Software License 1.14 · ซอร์สโค้ดนี้ให้ไว้ภายใต้ CH-J Proprietary Software License 1.14 การเปิดให้เข้าถึงไม่ได้เปลี่ยนแปลงเงื่อนไขของสัญญาอนุญาตหรือให้สิทธิ์เพิ่มเติม
SHA-256 ของไฟล์บีบอัด
476ed8aef0f1e96a5acdf223127b6692acdf4492eeaf5d0816b6e2a634fbbda6