Code Monkey home page Code Monkey logo

micro-key-producer's Introduction

micro-key-producer

Produces secure keys and passwords.

  • ๐Ÿ”“ Secure: audited noble cryptography
  • ๐Ÿ”ป Tree-shakeable: unused code is excluded from your builds
  • ๐ŸŽฒ Produce known (deterministic) and random keys
  • ๐Ÿ”‘ SSH, PGP, TOR, IPNS, SLIP10 keys
  • ๐Ÿช™ BLS12-381 keys for ETH validators
  • ๐Ÿ“Ÿ Generate secure passwords & OTP 2FA codes

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, '[email protected]');
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 = '[email protected]';
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.HDKey.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, '[email protected]', '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

Mask Description Example
1 digits 4, 7, 5, 0
@ symbols !, @, %, ^
v vowels a, e, i
c consonant b, c, d
a letter (vowel or consonant) a, b, e, c
V uppercase vowel A, E, I
C uppercase consonant B, C, D
A uppercase letter A, B, E, C
l lower and upper case letters A, b, C
n same as 'l', but also digits A, 1, b, 2, C
* same as 'n', but also symbols A, 1, !, b, @
s syllable (same as 'cv') ca, re, do
S Capitalized syllable (same as 'Cv) Ca, Ti, Je
All other characters used as is

Examples:

  • Mask: Cvccvc-cvccvc-cvccv1 will generate Mavmuq-xadgys-poqsa5
  • Mask @Ss-ss-ss will generate: *Tavy-qyjy-vemo

Design rationale

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

  • at least one upper-case character
  • at least one lower-case character
  • at least one symbol
  • at least one digit
  • length greater or equal to 8 These rules don't significantly increase password entropy (most humans will use mask like 'Aaaaaa1@' or any other popular mask), but they means that we cannot simple use mask like ********, since it can generate passwords which won't satisfy these rules.

What do we want from passwords?

  • length: entering 32 character password for FDE via IPMI java applet on remote server is pretty painful. -> 12-16 probably ok, anything with more characters has chance to be truncated by service.
  • readability: entering '!#%!$#Y^&*#%@#!!1' from air-gapped pc is hard.
  • entropy:
    • 32 bit is likely to be brutforced via network
    • 64 bit: 22 days && 1.6k$ at 4x V100: https://blog.trailofbits.com/2019/11/27/64-bits-ought-to-be-enough-for-anybody/ but it is simple loop, if there is something like pbkdf before password, it will significantly slowdown everything
    • 80 bits is probably outside of budget for most attackers (btc hash rate) even if there is major speedup for specific algorithm
    • For websites and services we don't care much about entropy, since passwords are unique and there is no re-usage, however for FDE / server password entropy is pretty important
  • no fancy and unique mask by default: we don't want to fingeprint users
  • any mask will leak eventually (even if user choices personal mask, there will be password leaks from websites), so we cannot calculate entropy by ****** mask, we need to calculate entropy for specific mask (which is smaller).
  • Password generator should be reversible, that way we can easily proof entropy/strength of password.

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.

  • SLIP-0010 publicKey is 33 bytes (see this issue), if you want 32-byte publicKey, use .publicKeyRaw getter
  • SLIP-0010 vectors fingerprint is actually parentFingerprint
  • SLIP-0010 doesn't allow deriving non-hardened keys for Ed25519, however some other libraries treat non-hardened keys (m/0/1) as hardened (m/0'/1'). If you want this behaviour, there is a flag forceHardened in derive method

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.

micro-key-producer's People

Contributors

0xc0de4c0ffee avatar luisosta avatar paulmillr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

micro-key-producer's Issues

Update `@noble/curves` or make dependencies matching less strict

Hello,

I've faced with the fact that clean installation of ed25519-keygen module installs two versions of @noble/hashes.

npm install ed25519-keygen

npm list @noble/hashes

[email protected] test-ed25519-keygen
โ””โ”€โ”ฌ [email protected]
  โ”œโ”€โ”ฌ @noble/[email protected]
  โ”‚ โ””โ”€โ”€ @noble/[email protected]
  โ””โ”€โ”€ @noble/[email protected]

It is not a big problem for a single usage but produces a lot of duplicates when using many @noble/* and @scure/* libraries.

Is it possible to implement extended private/public keys?

The library https://github.com/paulmillr/scure-bip32/blob/main/index.ts implements extended keys where you can do hdkey.fromExtendedKey(master.publicExtendedKey).deriveChild(x)

I'm looking for this feature using ed25519 and found this nice hdkeys implementation: https://github.com/paulmillr/ed25519-keygen/blob/main/src/hdkey.ts it looks very similar to scure-bip32, I've tried porting over the code from scure-bip32 to use ed25519 but not to much success, so I figured I should just ask. Is this possible at all?

Best canonical URL link back to the project?

Upon update my gen-ssh-ed25519 to adopt micro-key-producer 0.7, what's the best way to apply the credit back here in its README (other than current npm naming in ascii tree chart)?

I have the following options in mind:

  1. GitHub URL
  2. npm URL
  3. your own site page (if any)

Which option or options do you preferred? Thanks.

does this lbrary supports non-hardened derivation of a public key ?

I am looking in generated keys from a parent hd key both from its private key and public key, so that the resulting keys matches

The api seems to only work on keys where the private key is known. any pointer to get non-hardened keys ?

I want something like the function deriveFromPublicKey in the following

derive(parent private key).publickey = deriveFromPublicKey(public key)

ssh is not support in macos

import ssh from 'ed25519-keygen/ssh';
...

I used the example, to generate a ssh private & public, for macos there will be an error

-> % ssh-add id_ed25519
Error loading key "id_ed25519": invalid format

Request to use semantic versioning

Hey Paul,

Unless Iโ€™m mistaken, 0.5 โ†’ 0.6, while appearing as a minor release, contains breaking changes (removal of await from the public API).

May I please request that the project adhere to semantic versioning to avoid breakage during maintenance updates?

Thanks + thanks again for making and sharing this :)

doesn't seem as though ed25519-keygen/utils.js exists in npm deployment

error importing utils:

node:internal/errors:484
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/jcolson/src/personal/gpgssh/node_modules/ed25519-keygen/utils.js'

ls output of node_modules:

ll node_modules/ed25519-keygen/
.rw-r--r--   46 jcolson staff 30 Aug 21:28 index.d.ts
.rw-r--r--  101 jcolson staff 30 Aug 21:28 index.d.ts.map
.rw-r--r--   87 jcolson staff 30 Aug 21:28 index.js
.rw-r--r-- 1.8k jcolson staff 30 Aug 21:28 package.json
.rw-r--r-- 2.3k jcolson staff 30 Aug 21:28 pgp.d.ts
.rw-r--r-- 1.0k jcolson staff 30 Aug 21:28 pgp.d.ts.map
.rw-r--r--  20k jcolson staff 30 Aug 21:28 pgp.js
.rw-r--r-- 5.8k jcolson staff 30 Aug 21:28 README.md
drwxr-xr-x    - jcolson staff 30 Aug 21:28 src
.rw-r--r-- 1.5k jcolson staff 30 Aug 21:28 ssh.d.ts
.rw-r--r--  741 jcolson staff 30 Aug 21:28 ssh.d.ts.map
.rw-r--r-- 2.5k jcolson staff 30 Aug 21:28 ssh.js
.rw-r--r--  339 jcolson staff 30 Aug 21:28 tor.d.ts
.rw-r--r--  258 jcolson staff 30 Aug 21:28 tor.d.ts.map
.rw-r--r-- 1.3k jcolson staff 30 Aug 21:28 tor.js

IPNS keygen support

๐Ÿ™ it'd be nice to have ed25519 IPNS (libp2p-key) keygen support.

This is our experimental app-specific deterministic keygen format using deterministic ETH signatures for ENS contenthash.

let caip10 = `eip155:1:${App.user.address}`
let domain = "domain.eth"
let info = `${caip10}:${domain}`
let password = "pass12#$" // optional salt/pin
let extradata = await sha256(`${info}:${password ? password : ''}`) //still testing 
let msg = `Requesting Signature To Generate IPNS Keys For ${domain}\n\nWARNING:Do Not Sign This Request From Untrusted Clients\nExtradata: ${extradata}\nSigned By: ${caip10}`;
let sig = await App.user.signMessage(msg);
let inputKey = sha256(
    hexToBytes(
        sig.toLowerCase().startsWith('0x') ? sig.slice(2) : sig
    )
)
let salt = await sha256(`${info}:${password ? password : ''}:${sig.slice(-64)}`)
let hashKey = await hkdf(sha256, inputKey, salt, info, 42)
let privateKey = hashToPrivateScalar(hashKey, ed25519.CURVE.n, true).toString(16).padStart(64, "0")
let publicKey = bytesToHex(await ed25519.getPublicKey(privateKey))
let key = `08011240${privateKey}${publicKey}`
let w3Name = await Name.from(hexToBytes(key))
// let contenthash = `0xe5010172002408011220{$publicKey}`

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.