@bicycle-codes/crypto-util
v0.2.7
Published
Cryptography utility functions for the browser or node
Downloads
1,095
Readme
crypto util
Utility functions for working with crypto keys in the browser or node.
This is some helpful functions that make it easier to work with cryptography. Note this does not deal with storing keys. Look at using @bicycle-codes/webauthn-keys (biometric authentication) or indexedDB for help with that.
This includes both sodium based keys and also webcrypto functions.
The Webcrypto keys are preferable because we create them as non-extractable keys, and are able to persist them in indexedDB, despite not being able to read the private key.
[!TIP] Request "persistent" storage with the
.persist()
method in the browser.
[!NOTE] The install size is kind of large (9.77 MB) because this includes a minified bundle of the sodium library.
Plus, See the docs generated from typescript
Contents
- install
- example
- API
- webcrypto API
- webcrypto AES API
- webcrypto RSA API
- webcrypto ECC API
- sodium API
- Sodium AES API
- Sodium ECC API
- see also
install
npm i -S @bicycle-codes/crypto-util
example
Create a new keypair
Use ECC keys with the web crypto API.
Also, you can use RSA keys.
import { create, KeyUse } from '@bicycle-codes/crypto-util'
// create a new keypair
const encryptKeypair = await create(KeyUse.Encrypt)
const signKeys = await create(KeyUse.Sign)
Use 2 ECC keypairs to create a new AES key
This requires a keypair + another keypair to derive a shared AES key.
import { getSharedKey } from '@bicycle-codes/crypto-util/webcrypto/ecc'
import { KeyUse } from '@bicycle-codes/crypto-util/types'
const alicesKeys = await createEcc(KeyUse.Encrypt)
const bobsKeys = await createEcc(KeyUse.Encrypt)
// pass in our private key, their public key
const sharedKey = await getSharedKey(alicesKeys.privateKey, bobsKeys.publicKey)
Bob can derive the same key by using their private key + Alice's public key.
const bobsSharedKey = await getSharedKey(bobsKeys.privateKey, alicesKeys.publicKey)
Encrypt with AES keys
Encrypt a given message with a given key.
import { create, encrypt } from '@bicycle-codes/crypto-util/webcrypto/aes'
const aesKey = await create()
const aesEncryptedText = await encrypt('hello AES', aesKey)
Decrypt with AES keys
import { decrypt } from '@bicycle-codes/crypto-util/webcrypto/aes'
const decrypted = await decrypt(aesEncryptedText, aesKey)
encrypt with ECC keys
This is a message from Alice to Bob. We use Alice's private key & Bob's public key.
import { KeyUse } from '@bicycle-codes/crypto-util'
import {
create,
encrypt,
decrypt
} from '@bicycle-codes/crypto-util/webcrypto'
const alicesKeys = await create(KeyUse.Encrypt)
const bobsKeys = await create(KeyUse.Encrypt)
const eccEncryptedText = await encrypt(
'hello ecc',
alicesKeys.privateKey,
bobsKeys.publicKey
)
Decrypt with ECC keys
Bob can decrypt the message encrypted by Alice, because we used bob's public key when encrypting it.
// note keys are reversed here --
// alice's public key and bob's private key
const decrypted = await decrypt(
eccEncryptedText,
bobsKeys.privateKey,
alicesKeys.publicKey
)
// => 'hello ecc'
Create a signing keypair
Create another keypair that is used for signatures.
import { KeyUse } from '@bicycle-codes/crypto-util'
import { create } from '@bicycle-codes/crypto-util/webcrypto'
const eccSignKeys = await create(KeyUse.Sign)
Create signatures
import { sign } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const sig = await sign('hello dids', eccSignKeys.privateKey)
Create a DID
A DID is a decentralized identifier, a string the encodes a user's public key.
If you are transmiting your public key along with a message, for example, this is the preferred format.
import { publicKeyToDid } from '@bicycle-codes/crypto-util'
const did = await publicKeyToDid(eccSignKeys.publicKey)
Verify a signature
Use a DID to verify a signature string.
import { verifyWithDid, sign } from '@bicycle-codes/crypto-util/ecc'
const sig = await sign('hello dids', eccSignKeys.privateKey)
const isOk = await verifyWithDid('hello dids', sig, did)
API
This exposes ESM and common JS via package.json exports
field.
ESM
import * as util from '@bicycle-codes/crypto-utils'
Common JS
const util = require('@bicycle-codes/crypto-utils')
pre-built JS
This package exposes minified, pre-bundled JS files too. Copy them to a location that is accessible to your web server, then link in HTML.
copy
cp ./node_modules/@bicycle-codes/crypto-util/dist/index.min.js ./public/crypto-util.js
HTML
<script type="module" src="./crypto-util.js"></script>
webcrypto vs sodium
To use the webcrypto API, import from the webcrypto
sub-path.
webcrypto API
This depends on an environment with a webcrypto API.
import { aes, ecc, rsa } from '@bicycle-codes/crypto-util/webcrypto'
example
import { rsa, ecc, aes } from '@bicycle-codes/crypto-util/webcrypto'
// create some ECC keypairs
const eccKeypair = await ecc.create(KeyUse.Sign)
const eccSignKeys = await ecc.create(KeyUse.Encrypt)
// get the public key as a string
const publicKey = await ecc.exportPublicKey(eccSignKeys.publicKey)
// get the public key as a DID format string
const did = await ecc.publicKeyToDid(eccSignKeys.publicKey)
// transform a DID string to a public key instance
const publicKey = ecc.didToPublicKey(eccDid)
webcrypto AES API
aes.create
Create a new AES-GCM key.
function create (opts:{ alg, length } = {
alg: DEFAULT_SYMM_ALGORITHM,
length: DEFAULT_SYMM_LEN
}):Promise<CryptoKey>
aes.create
example
import { create } from '@bicycle-codes/crypto-util/webcrypto/aes'
const aesKey = await createAes()
aes.encrypt
Encrypt a string.
async function encrypt (
msg:Msg,
key:SymmKey|string,
opts?:Partial<SymmKeyOpts>
):Promise<string>
import { encrypt } from '@bicycle-codes/crypto-util/webcrypto/aes'
let aesEncryptedText:string
test('encrypt some text with AES', async t => {
aesEncryptedText = await encrypt('hello AES', aesKey)
// returns a string by default
})
aes.decrypt
async function decrypt (
msg:Msg,
key:SymmKey|string,
opts?:Partial<SymmKeyOpts>,
charSize:CharSize = DEFAULT_CHAR_SIZE
):Promise<string>
import { decrypt } from '@bicycle-codes/crypto-util/webcrypto/aes'
const decrypted = await decrypt(aesEncryptedText, aesKey)
// => 'hello AES'
webcrypto RSA API
We expose RSA because not all browser yet support ECC keys. See ./src/rsa/webcrypto.ts and ./test/index.ts.
webcrypto ECC API
ecc.create
async function create (
use:KeyUse,
curve:EccCurve = EccCurve.P_256,
):Promise<CryptoKeyPair>
create
example
import { create } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const alicesEncryptionKeys = await createEcc(KeyUse.Encrypt)
const alicesSigningKeys = await createEcc(KeyUse.Sign)
sign
async function sign (
msg:Msg, // <-- string or Uint8Array
privateKey:PrivateKey,
{ format }:{ format: 'base64'|'raw' } = { format: 'base64' },
charSize:CharSize = DEFAULT_CHAR_SIZE,
hashAlg:HashAlg = DEFAULT_HASH_ALGORITHM,
):Promise<ArrayBuffer|string>
example
import { sign } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const sig = await sign('hello webcrypto', eccSignKeys.privateKey)
verifyWithDid
Verify a signature with a DID format string.
async function verifyWithDid (
msg:string,
sig:string,
did:DID
):Promise<boolean>
import { verifyWithDid } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const isOk = await verifyWithDid('hello dids', sig, did)
getSharedKey
Get a shared key given two existing keypairs.
async function getSharedKey (
privateKey:PrivateKey,
publicKey:PublicKey,
opts?:Partial<{
alg:'AES-GCM'|'AES-CBC'|'AES-CTR'
length:SymmKeyLength
iv:ArrayBuffer
}>
):Promise<SymmKey>
let BobsKeys:CryptoKeyPair
let sharedKey:CryptoKey
const BobsKeys = await createEcc(KeyUse.Encrypt)
const sharedKey = await getSharedKey(eccKeypair.privateKey, BobsKeys.publicKey)
t.ok(sharedKey instanceof CryptoKey, 'should return a `CryptoKey`')
encrypt
Encrypt something with your private key and the recipient's public key.
async function encrypt (
msg:Msg, // <-- string or Uint8Array
privateKey:PrivateKey,
publicKey:string|PublicKey, // <-- base64 or key
{ format }:{ format: 'base64'|'raw' } = { format: 'base64' },
charSize:CharSize = DEFAULT_CHAR_SIZE,
curve:EccCurve = DEFAULT_ECC_CURVE,
opts?:Partial<{
alg:SymmAlg
length:SymmKeyLength
iv:ArrayBuffer
}>
):Promise<Uint8Array|string>
example
const eccEncryptedText = await ecc.encrypt(
'hello ecc',
alicesKeys.privateKey,
BobsKeys.publicKey
)
decrypt
Decrypt some text that was encrypted with ecc.ecncrypt
.
async function decrypt (
msg:Msg, // <-- string or Uint8Array
privateKey:PrivateKey,
publicKey:string|PublicKey,
curve:EccCurve = DEFAULT_ECC_CURVE,
opts?:Partial<{
alg:'AES-GCM'|'AES-CBC'|'AES-CTR'
length:SymmKeyLength
iv:ArrayBuffer
}>
):Promise<string>
example
Note the keys a swapped here -- the public and private keys can come from either keypair and it still works.
const decrypted = await ecc.decrypt(
eccEncryptedText,
BobsKeys.privateKey,
alicesKeys.publicKey
)
sodium API
These should work anywhere that JS can run.
import { aes, ecc, rsa } from '@bicycle-codes/crypto-util/sodium'
Or import individual modules
import * as aes from '@bicycle-codes/crypto-util/sodium/aes'
import * as ecc from '@bicycle-codes/crypto-util/sodium/ecc'
import * as webcryptoAes from '@bicycle-codes/crypto-util/webcrypto/aes'
Sodium AES API
Encrypt with AEGIS-256 (symmetric crypto).
Sodium + AES example
import {
create,
encrypt,
decrypt
} from '@bicycle-codes/crypto-util/sodium/aes'
// create a new key
const key = await create()
// or create a key, return a Uint8Array
const keyAsBuffer = await createAes({ format: 'raw' })
// encrypt something
const encryptedString = await encrypt('hello sodium + AES', key)
aes.create
Create a new AES key. Pass { format: 'raw' }
to return a Uint8Array
.
async function create (opts:{
format: 'string'|'raw'
} = { format: 'string' }):Promise<Uint8Array|string>
example
import { create } from '@bicycle-codes/crypto-util/sodium/aes'
const aesKey = await create()
aes.encrypt
Encrypt the given string or buffer. Pass { format: 'raw' }
to return a Uint8Array
.
async function encrypt (
msg:Uint8Array|string,
key:Uint8Array|string,
opts:Partial<{
iv?:Uint8Array
format?:'string'|'raw'
}> = { format: 'string' },
):Promise<Uint8Array|string>
example
import { encrypt } from '@bicycle-codes/crypto-util/sodium/aes'
const encryptedString = await encryptAes('hello sodium + AES', aesKey)
aes.decrypt
Decrypt the given string or buffer. Pass { format: 'raw' }
to return a Uint8Array
.
async function decrypt (
cipherText:string|Uint8Array,
key:string|Uint8Array,
opts:{ format:'string'|'raw' } = { format: 'string' }
):Promise<Uint8Array|string>
example
import { decrypt } from '@bicycle-codes/sodium/aes'
const decrypted = await decrypt(encryptedAes, aesKey)
// => "hello sodium + AES"
Sodium ECC API
Sodium + ECC example
import * as ecc from '@bicycle-codes/crypto-util/sodium/ecc'
const keys = await ecc.create()
ecc.create
Create a new Edward keypair.
async function create (
use:KeyUse,
curve:EccCurve = EccCurve.P_256,
):Promise<CryptoKeyPair>
.create
example
import { create } from '@bicycle-codes/crypto-util/sodium/ecc'
const keys = await create()
ecc.sign
Create a signature for the diven data. Pass { format: 'raw' }
to get a Uint8Array
instead of a string.
async function sign (
data:string|Uint8Array,
key:LockKey,
opts:{
format:'string'|'raw'
} = { format: 'string' }
):Promise<string|Uint8Array>
ecc.sign
example
import { sign } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const sig = await sign('hello webcrypto', alicesKeys, { format: 'raw' })
ecc.publicKeyToDid
Take a public key instance and return a DID format string.
async function publicKeyToDid (
publicKey:Uint8Array|PublicKey
):Promise<DID>
ecc.publicKeyToDid
example
import {
exportPublicKey,
publicKeyToDid
} from '@bicycle-codes/crypto-util/webcrypto/ecc'
const arr = await exportPublicKey(eccSignKeys.publicKey)
const did = await publicKeyToDid(arr)
ecc.verify
Verify the given signature + public key + message data.
async function verify (
msg:Msg, // <-- string or Uint8Array
sig:string|Uint8Array|ArrayBuffer,
publicKey:string|PublicKey,
charSize:CharSize = DEFAULT_CHAR_SIZE,
curve:EccCurve = DEFAULT_ECC_CURVE,
hashAlg: HashAlg = DEFAULT_HASH_ALGORITHM
):Promise<boolean>
ecc.verify
example
const key = await importDid(eccDid)
const isOk = await verify('hello webcrypto', sig, key)
t.ok(isOk, 'should verify a valid signature')
ecc.verifyWithDid
Verify the given signature + message + public key are ok together, using the given DID string as public key material.
async function verifyWithDid (
msg:string,
sig:string,
did:DID
):Promise<boolean>
ecc.verifyWithDid
example
import { verifyWithDid } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const isOk = await verifyWithDid('hello dids', sig, did)
ecc.encrypt
Use the given private and public keys to create a shared key, then encrypt the message with the key.
async function encrypt (
msg:Msg,
privateKey:PrivateKey,
publicKey:string|PublicKey, // <-- base64 or key
{ format }:{ format: 'base64'|'raw' } = { format: 'base64' },
charSize:CharSize = DEFAULT_CHAR_SIZE,
curve:EccCurve = DEFAULT_ECC_CURVE,
opts?:Partial<{
alg:SymmAlg
length:SymmKeyLength
iv:ArrayBuffer
}>
):Promise<Uint8Array|string>
ecc.encrypt example
import { encrypt } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const eccEncryptedText = await encrypt(
'hello ecc',
AlicesKeys.privateKey,
BobsKeys.publicKey
)
ecc.decrypt
Decrypt a message given a public and private key. Note in the example, the keypairs are reversed from the encrypt
example. This creates a new shared key via Diffie Hellman.
import { decrypt } from '@bicycle-codes/crypto-util/webcrypto/ecc'
const decrypted = await decrypt(
eccEncryptedText,
BobsKeys.privateKey,
AlicesKeys.publicKey
)
see also
- [question] AES GCM — iv length #74 -- partial motivation for publishing this
libsodium
docsUnless you absolutely need AES-GCM, use AEGIS-256 (crypto_aead_aegis256_*()) instead. It doesn’t have any of these limitations.
- Web Crypto API -- MDN docs
- StorageManager: persist() method
- idb-keyval -- super simple key value storage API built on
indexedDB
.