@bicycle-codes/keys
v0.0.11
Published
Create keys with the webcrypto API
Downloads
862
Readme
keys
Create and store keypairs in the browser with the web crypto API.
Use indexedDB to store non-extractable keypairs in the browser. "Non-extractable" means that the browser prevents you from ever reading the private key, but the keys can be persisted and re-used indefinitely.
[!TIP] Use the persist method to tell the browser not to delete from
indexedDB
.
See also, the API docs generated from typescript.
install
npm i -S @bicycle-codes/keys
get started
indexedDB
Create a new keypair, then save it in indexedDB
.
import { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create()
// save the keys to indexedDB
await keys.persist()
// ... sometime in the future ...
// get our keys from indexedDB
const keysAgain = await Keys.load()
console.assrt(keys.DID === keysAgain.DID) // true
sign and verify something
.verify
takes the content, the signature, and the DID for the public key used to sign. The DID is exposed as the property .DID
on a Keys
instance.
[!NOTE]
verify
is exposed as a separate function, so you don't have to include all ofKeys
just to verify a signature.
import { verify } from '@bicycle-codes/keys'
// sign something
const sig = await keys.signAsString('hello string')
// verify the signature
const isOk = await verify('hello string', sig, keys.DID)
encrypt something
Takes the public key we are encrypting to, return an object of { content, key }
, where content
is the encrypted content as a string, and key
is the AES key that was used to encrypt the content, encrypted to the given public key. (AES key is encrypted to the public key.)
import { encryptTo } from '@bicycle-codes/keys'
// need to know the public key we are encrypting for
const publicKey = await keys.getPublicEncryptKey()
const encrypted = await encryptTo.asString({
content: 'hello public key',
publicKey
})
// => { content, key }
decrypt something
A Keys
instance has a method decrypt
. The encryptedMessage
argument is an object of { content, key }
as returned from encryptTo
, above.
import { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create()
// ...
const decrypted = await keys.decrypt(encryptedMsg)
API
exports
This exposes ESM and common JS via package.json exports
field.
ESM
import '@bicycle-codes/keys'
Common JS
require('@bicycle-codes/keys')
pre-built JS
This package exposes minified JS files too. Copy them to a location that is accessible to your web server, then link to them in HTML.
copy
cp ./node_modules/@bicycle-codes/keys/dist/index.min.js ./public/keys.min.js
HTML
<script type="module" src="./keys.min.js"></script>
examples
Create a new Keys
instance
Use the factory function Keys.create
because async
. The optional parameters, encryptionKeyName
and signingKeyName
, are added as properties to the keys
instance -- ENCRYPTION_KEY_NAME
and SIGNING_KEY_NAME
. These are used as indexes for saving the keys in indexedDB
.
class Keys {
ENCRYPTION_KEY_NAME:string = 'encryption-key'
SIGNING_KEY_NAME:string = 'signing-key'
static async create (opts?:{
encryptionKeyName:string,
signingKeyName:string
}):Promise<Keys>
}
.create()
example
import { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create()
Get a hash of the DID
Get a 32-character, DNS-friendly string of the hash of the given DID. Available as static or instance method.
static method
class Keys {
static async deviceName (did:DID):Promise<string>
}
instance method
class Keys {
async getDeviceName ():Promise<string>
}
Persist the keys
Save the keys to indexedDB
. This depends on the values of class properties ENCRYPTION_KEY_NAME
and SIGNING_KEY_NAME
. Set them if you want to change the indexes under which the keys are saved to indexedDB
.
.persist
class Keys {
async persist ():Promise<void>
}
Restore from indexedDB
Create a Keys
instance from data saved to indexedDB
. Pass in different indexedDB
key names for the keys if you need to.
static .load
class Keys {
static async load (opts:{
encryptionKeyName,
signingKeyName
} = {
encryptionKeyName: DEFAULT_ENC_NAME,
signingKeyName: DEFAULT_SIG_NAME
}):Promise<Keys>
}
example
import { Keys } from '@bicycle-codes/keys'
const newKeys = await Keys.load()
Sign something
Create a new signature for the given input.
class Keys {
async sign (
msg:ArrayBuffer|string|Uint8Array,
charsize?:CharSize,
):Promise<Uint8Array>
}
example
const sig = await keys.sign('hello signatures')
Get a signature as a string
class Keys {
async signAsString (
msg:ArrayBuffer|string|Uint8Array,
charsize?:CharSize
):Promise<string>
}
const sig = await keys.signAsString('hello string')
// => ubW9PIjb360v...
Verify a signature
Check if a given signature is valid. This is exposed as a stateless function so that it can be used independently from any keypairs. You need to pass in the data that was signed, the signature, and the DID
string of the public key used to create the signature.
async function verify (
msg:string|Uint8Array,
sig:string|Uint8Array,
signingDid:DID
):Promise<boolean>
import { verify } from '@bicycle-codes/keys'
const isOk = await verify('hello string', sig, keys.DID)
Encrypt a key
This method uses async (RSA) encryption, so it should be used to encrypt AES keys only, not arbitrary data. You must pass in either a DID or a public key as the encryption target.
async function encryptKeyTo ({ key, publicKey, did }:{
key:string|Uint8Array|CryptoKey;
publicKey?:CryptoKey|Uint8Array|string;
did?:DID
}):Promise<Uint8Array>
example
const encrypted = await encryptKeyTo({
content: myAesKey,
publicKey: keys.publicEncryptKey
})
const encryptedTwo = await encryptKeyTo({
content: aesKey,
did: keys.DID
})
Encrypt some arbitrary data
Take some arbitrary content and encrypt it. Will use either the given AES key, or will generate a new one if it is not passed in. The return value is the encrypted key and the given data. You must pass in either a DID or a public key to encrypt to.
async function encryptTo (opts:{
content:string|Uint8Array;
publicKey?:CryptoKey|string;
did?:DID;
}, aesKey?:SymmKey|Uint8Array|string):Promise<{
content:Uint8Array;
key:Uint8Array;
}>
example
import { encryptTo } from '@bicycle-codes/keys'
const encrypted = await encryptTo({
key: 'hello encryption',
publicKey: keys.publicEncryptKey
// or pass in a DID
// did: keys.DID
})
// => {
// content:Uint8Array
// key: Uint8Array <-- the encrypted AES key
// }
Decrypt a message
class Keys {
async decrypt (msg:{
content:string|Uint8Array;
key:string|Uint8Array;
}):Promise<Uint8Array>
}
const decrypted = await keys.decrypt(encrypted)
// => Uint8Array
decryptToString
Decrypt a message, and stringify the result.
class Keys {
async decryptToString (msg:EncryptedMessage):Promise<string>
}
const decrypted = await keys.decryptToString(encryptedMsg)
// => 'hello encryption'
AES
Expose several AES functions with nice defaults.
- algorithm:
AES-GCM
- key size:
256
iv
size:12
bytes (96 bits)
import { AES } from '@bicycle-codes/keys'
const key = await AES.create(/* ... */)
create
Create a new AES key. By default uses 256 bits & GCM algorithm.
function create (opts:{ alg:string, length:number } = {
alg: DEFAULT_SYMM_ALGORITHM, // AES-GCM
length: DEFAULT_SYMM_LENGTH // 256
}):Promise<CryptoKey>
import { AES } from '@bicycle-codes/keys'
const aesKey = await AES.create()
export
Get the AES key as a Uint8Array
.
{
async export (key:CryptoKey):Promise<Uint8Array>
}
const exported = await AES.export(aesKey)
exportAsString
Get the key as a string, base64
encoded.
async function exportAsString (key:CryptoKey):Promise<string>
const exported = await AES.exportAsString(aesKey)
encrypt
async function encrypt (
data:Uint8Array,
cryptoKey:CryptoKey|Uint8Array,
iv?:Uint8Array
):Promise<Uint8Array>
const encryptedText = await AES.encrypt(fromString('hello AES'), aesKey)
decrypt
async function decrypt (
encryptedData:Uint8Array|string,
cryptoKey:CryptoKey|Uint8Array|ArrayBuffer,
iv?:Uint8Array
):Promise<Uint8Array>
const decryptedText = await AES.decrypt(encryptedText, aesKey)