Home

Awesome

micro-key-producer

Produces secure keys and passwords.

Used in: terminal7 WebRTC terminal multiplexer.

Usage

npm install micro-key-producer

import ssh from 'micro-key-producer/ssh.js';
import pgp from 'micro-key-producer/pgp.js';
import * as pwd from 'micro-key-producer/password.js';
import * as otp from 'micro-key-producer/otp.js';
import tor from 'micro-key-producer/tor.js';
import ipns from 'micro-key-producer/ipns.js';
import slip10 from 'micro-key-producer/slip10.js';
import { randomBytes } from 'micro-key-producer/utils.js';

Key generation: known and random seeds

Every method takes a seed (key), from which the formatted result is produced.

A seed can be known (a.k.a. deterministic - it will always produce the same result), or random.

// known: (deterministic) Uses known mnemonic (handled in separate package)
import { mnemonicToSeedSync } from '@scure/bip39';
const mnemonic = 'letter advice cage absurd amount doctor acoustic avoid letter advice cage above';
const knownSeed = mnemonicToSeedSync(mnemonic, '');

// random: Uses system's CSPRNG to produce new random seed
import { randomBytes } from 'micro-key-producer/utils.js';
const randSeed = randomBytes(32);

Generate SSH keys

import ssh from 'micro-key-producer/ssh.js';
import { randomBytes } from 'micro-key-producer/utils.js';

const seed = randomBytes(32);
const key = ssh(seed, 'user@example.com');
console.log(key.fingerprint, key.privateKey, key.publicKey);
// SHA256:3M832z6j5R6mQh4TTzVG5KVs2Ibvy...
// -----BEGIN OPENSSH PRIVATE KEY----- ...
// ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...

The PGP (GPG) keys conform to RFC 4880 & RFC 6637. Only ed25519 algorithm is currently supported.

Generate PGP keys

import pgp, { getKeyId } from 'micro-key-producer/pgp.js';
import { randomBytes } from 'micro-key-producer/utils.js';

const seed = randomBytes(32);
const email = 'user@example.com';
const pass = 'password';
const createdAt = Date.now(); // optional; timestamp >= 0

const keyId = getKeyId(seed);
const key = pgp(seed, email, pass, createdAt);
console.log(key.fingerprint, key.privateKey, key.publicKey);
// ca88e2a8afd9cdb8
// -----BEGIN PGP PRIVATE KEY BLOCK-----...
// -----BEGIN PGP PUBLIC KEY BLOCK-----...

Generate BLS keys for ETH validators

import { mnemonicToSeedSync } from '@scure/bip39';
import { createDerivedEIP2334Keystores } from 'micro-key-producer/bls.js';

const password = 'my_password';
const mnemonic = 'letter advice cage absurd amount doctor acoustic avoid letter advice cage above';
const keyType = 'signing'; // or 'withdrawal'
const indexes = [0, 1, 2, 3]; // create 4 keys

const keystores = createDerivedEIP2334Keystores(
  password
  'scrypt',
  mnemonicToSeedSync(mnemonic, ''),
  keyType,
  indexes
);

Conforms to EIP-2333 / EIP-2334 / EIP-2335. Online demo: eip2333-tool

Generate secure passwords

import * as pwd from 'micro-key-producer/password.js';
import { randomBytes } from '@noble/hashes/utils';

const seed = randomBytes(32);
const pass = pwd.secureMask.apply(seed).password;
// wivfi1-Zykrap-fohcij, will change on each run
// secureMask is format from iOS keychain, see "Detailed API" section

Supports iOS / macOS Safari Secure Password from Keychain. Optional zxcvbn score for password bruteforce estimation

Generate 2FA OTP codes

import * as otp from 'micro-key-producer/otp.js';
otp.hotp(otp.parse('ZYTYYE5FOAGW5ML7LRWUL4WTZLNJAMZS'), 0n); // 549419
otp.totp(otp.parse('ZYTYYE5FOAGW5ML7LRWUL4WTZLNJAMZS'), 0); // 549419

Conforms to RFC 6238.

Generate TOR keys and addresses

import tor from 'micro-key-producer/tor.js';
import { randomBytes } from 'micro-key-producer/utils.js';
const seed = randomBytes(32);
const key = tor(seed);
console.log(key.privateKey, key.publicKey);
// ED25519-V3:EOl78M2gA...
// rx724x3oambzxr46pkbd... .onion

Generate IPNS addresses

import ipns from 'micro-key-producer/ipns.js';
import { randomBytes } from 'micro-key-producer/utils.js';
const seed = randomBytes(32);
const k = ipns(seed);
console.log(k.privateKey, k.publicKey, k.base16, k.base32, k.base36, k.contenthash);
// 0x080112400681d6420abb1b...
// 0x017200240801122012c829...
// ipns://f0172002408011220...
// ipns://bafzaajaiaejcaewi...
// ipns://k51qzi5uqu5dgnfwb...
// 0xe501017200240801122012...

Generate SLIP10 ed25519 hdkeys

import slip10 from 'micro-key-producer/slip10.js';
import { randomBytes } from 'micro-key-producer/utils.js';

const seed = randomBytes(32);
const hdkey1 = slip10.fromMasterSeed(seed);

// props
[hdkey1.depth, hdkey1.index, hdkey1.chainCode];
console.log(hdkey2.privateKey, hdkey2.publicKey);
console.log(hdkey3.derive("m/0/2147483647'/1'"));
const sig = hdkey3.sign(hash);
hdkey3.verify(hash, sig);

SLIP10 (ed25519 BIP32) HDKey implementation has been funded by the Kin Foundation for Kinetic.

Low-level details

PGP key generation

  1. Generated private and public keys would have different representation, however, their fingerprints would be the same. This is because AES encryption is used to hide the keys, and AES requires different IV / salt.
  2. The function is slow (~725ms on Apple M1), because it uses S2K to derive keys.
  3. "warning: lower 3 bits of the secret key are not cleared" happens even for keys generated with GnuPG 2.3.6, because check looks at item as Opaque MPI, when it is just MPI: see bugtracker URL.
import * as pgp from 'micro-key-producer/pgp';
import { randomBytes } from 'micro-key-producer/utils';
const pseed = randomBytes(32);
pgp.getKeyId(pseed); // fast
const pkeys = pgp.getKeys(pseed, 'user@example.com', 'password');
console.log(pkeys.keyId);
console.log(pkeys.privateKey);
console.log(pkeys.publicKey);

// Also, you can explore existing keys internal structure
console.log(pgp.pubArmor.decode(keys.publicKey));
const privDecoded = pgp.privArmor.decode(keys.privateKey);
console.log(privDecoded);
// And receive raw private keys as bigint
console.log({
  ed25519: pgp.decodeSecretKey('password', privDecoded[0].data),
  cv25519: pgp.decodeSecretKey('password', privDecoded[3].data),
});

Password generation

Bruteforce estimation and ZXCVBN score

import * as pwd from 'micro-key-producer/password.js';
console.log(pwd.secureMask.estimate);

// Output
{
  score: 'somewhat guessable', // ZXCVBN Score
  // Guess times
  guesses: {
    online_throttling: '1y 115mo', // Throttled online attack
    online: '1mo 10d', // Online attack
    // Offline attack (salte, slow hash function like bcrypt, scrypt, PBKDF2, argon, etc)
    slow: '57min 36sec',
    fast: '0 sec' // Offline attack
  },
  // Estimated attack costs (in $)
  costs: {
    luks: 1.536122841572242, // LUKS (Linux FDE)
    filevault2: 0.2308740987992559, // FileVault 2 (macOS FDE)
    macos: 0.03341598798410283, // MaccOS v10.8+ passwords
    pbkdf2: 0.011138662661367609 // PBKDF2 (PBKDF2-HMAC-SHA256)
  }
}

Mask control characters

MaskDescriptionExample
1digits4, 7, 5, 0
@symbols!, @, %, ^
vvowelsa, e, i
cconsonantb, c, d
aletter (vowel or consonant)a, b, e, c
Vuppercase vowelA, E, I
Cuppercase consonantB, C, D
Auppercase letterA, B, E, C
llower and upper case lettersA, b, C
nsame as 'l', but also digitsA, 1, b, 2, C
*same as 'n', but also symbolsA, 1, !, b, @
ssyllable (same as 'cv')ca, re, do
SCapitalized syllable (same as 'Cv)Ca, Ti, Je
All other characters used as is

Examples:

Design rationale

Most strict password rules (so password will be accepted everywhere):

What do we want from passwords?

SLIP10 API

SLIP-0010 hierarchical deterministic (HD) wallets for implementation. Based on code from scure-bip32. Check out scure-bip39 if you also need mnemonic phrases.

Note: chainCode property is essentially a private part of a secret "master" key, it should be guarded from unauthorized access.

The full API is:

class HDKey {
  public static HARDENED_OFFSET: number;
  public static fromMasterSeed(seed: Uint8Array | string): HDKey;

  readonly depth: number = 0;
  readonly index: number = 0;
  readonly chainCode: Uint8Array | null = null;
  readonly parentFingerprint: number = 0;
  public readonly privateKey: Uint8Array;

  get fingerprint(): number;
  get fingerprintHex(): string;
  get parentFingerprintHex(): string;
  get pubKeyHash(): Uint8Array;
  get publicKey(): Uint8Array;
  get publicKeyRaw(): Uint8Array;

  derive(path: string, forceHardened = false): HDKey;
  deriveChild(index: number): HDKey;
  sign(hash: Uint8Array): Uint8Array;
  verify(hash: Uint8Array, signature: Uint8Array): boolean;
}

License

MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.