
Security News
Another Round of TEA Protocol Spam Floods npm, But It’s Not a Worm
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.
@bicycle-codes/keys
Advanced tools
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.
Each instance of Keys has two keypairs -- one for signing, and another for
encrypting. We are using RSA keys only right now, because we are
waiting for all browsers to support ECC.
See also, the API docs generated from typescript.
Keys instance.decrypt.asStringnpm i -S @bicycle-codes/keys
Asymmetric encryption means using an AES key to encrypt the content, then encrypting the AES key to a public RSA key. If you encrypt something to Bob's public key, that means we create a new buffer of encrypted AES key + encrypted content, where the encrypted AES key can only be decrypted by Bob's private key.
exportsThis exposes ESM and common JS via package.json exports field.
import { Keys } from '@bicycle-codes/keys'
const { Keys } = require('@bicycle-codes/keys')
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.
cp ./node_modules/@bicycle-codes/keys/dist/index.min.js ./public/keys.min.js
<script type="module" src="./keys.min.js"></script>
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.assert(keys.DID === keysAgain.DID) // true
keys instancekeys.DIDThis is the DID string for the signing key for this instance. The DID looks like this:
'did:key:z13V3Sog2YaUKhdGCmgx9UZuW...'
keys.getDeviceName / keys.deviceNameReturn the 32 character, DNS friendly hash of the signing public key.
const name = await keys.getDeviceName()
// a promise is exposed as property `deviceName`
const name = await keys.deviceName
keys.persistedA flag indicating whether .persist has been called, meaning that these keys
are saved in indexedDB.
keys.publicEncryptKeyThe public encryption CryptoKey.
keys.getPublicEncryptKey()Get the public encryption key, as a base64 string. For other formats,
see below.
{
async getPublicEncryptKey ():Promise<string>
}
keys.getPublicEncryptKey(format)Get the public encryption key. The given format should be a supported encoding in uint8arrays.
{
async getPublicEncryptKey (
format?:SupportedEncodings
):Promise<string>
}
Get the public encryption key as a Uint8Array.
{
function uint8Array:()=>Promise<Uint8Array<ArrayBufferLike>>
}
Delete the keys from indexedDB.
await keys.delete()
.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]
verifyis exposed as a separate function, so you don't have to include all ofKeysjust to verify a signature.
import { Keys, verify } from '@bicycle-codes/keys'
const keys = await Keys.create()
// sign something
const sig = await keys.sign.asString('hello string')
// verify the signature
const isOk = await verify('hello string', sig, keys.DID)
Take the public key we are encrypting to, return an ArrayBuffer, containing
the encrypted AES key concattenated with the iv and encrypted content.
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({
content: 'hello public key',
publicKey
}) // => ArrayBuffer
const encrypted = await encryptTo.asString({
content: 'hello public key',
publicKey
})
// => <encrypted text>
A Keys instance has a method decrypt. The encryptedMessage argument is
an ArrayBuffer as returned from encryptTo, above.
import { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create()
// ...
const decrypted = await keys.decrypt(encryptedMsg)
Keys instanceUse the factory function Keys.create. 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() exampleUse the factory function b/c async.
import { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create()
Get a 32-character, DNS-friendly string of the hash of the given DID.
Available as static or instance method. If called as an instance method,
this will use the DID assigned to the given Keys instance.
The static method requires a DID string to be passed in.
class Keys {
static async deviceName (did:DID):Promise<string>
}
If used as an instance method, this will use the DID assigned to the instance.
class Keys {
async getDeviceName ():Promise<string>
}
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.
By default we use these:
const DEFAULT_ENC_NAME = 'encryption-key'
const DEFAULT_SIG_NAME = 'signing-key'
.persistclass Keys {
async persist ():Promise<void>
}
.persist exampleimport { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create()
keys.ENCRYPTION_KEY_NAME = 'encryption-key-custom-name'
keys.SIGNING_KEY_NAME = 'signing-key-custom-name'
await keys.persist()
Create a Keys instance from data saved to indexedDB. Pass in different
indexedDB key names for the keys if you need to.
static .loadclass Keys {
static async load (opts:{
encryptionKeyName,
signingKeyName
} = {
encryptionKeyName: DEFAULT_ENC_NAME,
signingKeyName: DEFAULT_SIG_NAME
}):Promise<Keys>
}
import { Keys } from '@bicycle-codes/keys'
const newKeys = await Keys.load()
Create a new signature for the given input.
class Keys {
async sign (
msg:ArrayBuffer|string|Uint8Array,
charsize?:CharSize,
):Promise<Uint8Array>
}
const sig = await keys.sign('hello signatures')
keys.sign.asString(msg){
/**
* Sign a message, return the signature as a base64 encoded string.
*
* @param {Msg} msg The message to sign
* @param {CharSize} [charsize] Character size
* @returns {Promise<string>}
*/
asString: async (msg:Msg, charsize?:CharSize):Promise<string>
}
const sig = await keys.sign.asString('hello string')
// => ubW9PIjb360v...
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)
Use asynchronous (RSA) encryption to encrypt an AES key to the given public key.
async function encryptKeyTo ({ key, publicKey }:{
key:string|Uint8Array|CryptoKey;
publicKey:CryptoKey|Uint8Array|string;
}, format?:'uint8array'|'arraybuffer'):Promise<Uint8Array|ArrayBuffer>
import { encryptKeyTo } from '@bicycle-codes/keys'
// pass in a CryptoKey
const encrypted = await encryptKeyTo({
key: myAesKey,
publicKey: keys.publicEncryptKey
})
// pass in a base64 string
const encryptedTwo = await encryptKeyTo({
key: aesKey,
publicKey: await keys.getPublicEncryptKey()
}) // => Uint8Array
Encrypt the given key to the public key, and return the result as a base64 string.
import { encryptKeyTo } from '@bicycle-codes/keys'
encryptKeyTo.asString = async function ({ key, publicKey }:{
key:string|Uint8Array|CryptoKey;
publicKey:CryptoKey|string|Uint8Array;
}, format?:SupportedEncodings):Promise<string> {
encryptKeyTo.asString takes an optional second argument for the format
of the returned string. Format is anything supported by uint8arrays. By default, if omitted, it is base64.
Encrypt the given message to the given public key. If an AES key is not provided, one will be created. Use the AES key to encrypt the given content, then encrypt the AES key to the given public key.
The return value is an ArrayBuffer containing the encrypted AES key +
the iv + the encrypted content.
To decrypt, pass the returned value to keys.decrypt, where keys is an
instance with the corresponding private key.
async function encryptTo (
opts:{
content:string|Uint8Array;
publicKey:CryptoKey|string;
},
aesKey?:SymmKey|Uint8Array|string,
):Promise<ArrayBuffer>
import { encryptTo } from '@bicycle-codes/keys'
const encrypted = await encryptTo({
content: 'hello encryption',
publicKey: keys.publicEncryptKey
})
// => ArrayBuffer
Encrypt the given string, and return a new string that is the (encrypted) AES
key concattenated with the iv and cipher text. The
corresponding method keys.decrypt.asString will know how to parse and
decrypt the resulting text.
Use the functions encryptTo.asString and keys.decrypt.asString.
keys.decrypt.asStringasync function asString (msg:string, keysize?:SymmKeyLength):Promise<string>
import { Keys, encryptTo } from '@bicycle-codes/keys'
const keys = await Keys.create()
const pubKey = await keys.getPublicEncryptKey()
const msg = { type: 'test', content: 'hello' }
const cipherText = await encryptTo.asString({
content: JSON.stringify(msg),
// pass in a string public key or crypto key or Uint8Array
publicKey: pubKey
}) // => string
const text = await keys.decrypt.asString(cipherText)
const data = JSON.parse(text)
// => { type: 'test', content: 'hello' }
class Keys {
async decrypt (
msg:string|Uint8Array|ArrayBuffer,
keysize?:SymmKeyLength
):Promise<Uint8Array>
}
const decrypted = await keys.decrypt(encrypted)
// => Uint8Array
.decrypt.asStringDecrypt a message, and stringify the result.
{
async function asString (msg:EncryptedMessage):Promise<string>
}
await keys.decrypt.asString(encryptedString)
// => 'hello encryption'
Create a keypair, but do not save it in indexedDB, even if you call persist.
Pass an option .session to .create or .load.
import { Keys } from '@bicycle-codes/keys'
const keys = await Keys.create({ session: true })
// or pass it to `.load`
const keysTwo = await Keys.load({ session: true })
Expose several AES functions with nice defaults.
AES-GCM256iv size: 12 bytes (96 bits)import { AES } from '@bicycle-codes/keys'
const key = await AES.create(/* ... optional arguments ... */)
createCreate 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()
exportGet the AES key as a Uint8Array.
async function export (key:CryptoKey):Promise<Uint8Array>
import { AES } from '@bicycle-codes/keys'
const exported = await AES.export(aesKey)
exportAsStringGet the key as a string, base64 encoded.
async function asString (
key:CryptoKey,
format?:SupportedEncoding
):Promise<string>
import { AES } from '@bicycle-codes/keys'
const exported = await AES.export.asString(aesKey)
AES.encryptTake a Uint8Array, return an encrypted Uint8Array.
async function encrypt (
data:Uint8Array,
cryptoKey:CryptoKey|Uint8Array,
iv?:Uint8Array
):Promise<Uint8Array>
import { AES } from '@bicycle-codes/keys'
import { fromString } from 'uint8arrays'
const encryptedText = await AES.encrypt(fromString('hello AES'), aesKey)
AES.decryptasync function decrypt (
encryptedData:Uint8Array|string,
cryptoKey:CryptoKey|Uint8Array|ArrayBuffer,
iv?:Uint8Array
):Promise<Uint8Array>
import { AES } from '@bicycle-codes/keys'
const decryptedText = await AES.decrypt(encryptedText, aesKey)
FAQs
Create keys with the webcrypto API
The npm package @bicycle-codes/keys receives a total of 15 weekly downloads. As such, @bicycle-codes/keys popularity was classified as not popular.
We found that @bicycle-codes/keys demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.

Security News
PyPI adds Trusted Publishing support for GitLab Self-Managed as adoption reaches 25% of uploads

Research
/Security News
A malicious Chrome extension posing as an Ethereum wallet steals seed phrases by encoding them into Sui transactions, enabling full wallet takeover.