WebCrypto compatible encryption with Bash and OpenSSL.
This package implements the Webcryptobox encryption API.
Compatible packages:
There is also a CLI tool: wcb.sh
Demo: https://jo.github.io/webcryptobox-js/
This library provides easy to use and convenient wrappers around the WebCrypto primitives, as well as some helpers for encoding/decoding and a few sugar functions, with zero dependencies.
It works directly in the browser and in latest Node.js versions (via the experimental WebCrypto API).
In Node, you can use the lib as usual:
npm install webcryptobox
and then
import * as wcb from 'webcryptobox'
In modern browser which support es6 modules, just include the file directly:
<script type=module>
import * as wcb from './node_modules/webcryptobox/index.js';
</script>
Now you can dance with Webcryptobox like this:
const alice = await wcb.generateKeyPair()
const bob = await wcb.generateKeyPair()
const text = 'Test message'
const message = wcb.decodeText(text)
const box = await wcb.encryptTo({ message, privateKey: alice.privateKey, publicKey: bob.publicKey })
const decryptedBox = await wcb.decryptFrom({ box, privateKey: bob.privateKey, publicKey: alice.publicKey })
const decryptedText = wcb.encodeText(decryptedBox)
This lib is written with some ECMAScript 6 features, mainly modules, dynamic import, async, destructuring and object spreading.
Most of the functions return promises, except for the encoding/decoding utilities.
Returns a cipher identifier:
wcb.cipher
// 'ECDH-P-521-AES-256-CBC'
Webcryptobox provides utility functions to convert between several text representations and the internally used Uint8Array
s.
Takes an unicode string and encodes it to an Uint8Array:
const data = wcb.decodeText('my message')
// Uint8Array(10) [
// 109, 121, 32, 109,
// 101, 115, 115, 97,
// 103, 101
// ]
Given a Uint8Array, encodes the data as unicode string:
const text = wcb.encodeText(new Uint8Array([
109, 121, 32, 109,
101, 115, 115, 97,
103, 101
]))
// my message
Takes a hex string and encodes it to an Uint8Array:
const data = wcb.decodeHex('6d79206d657373616765')
// Uint8Array(10) [
// 109, 121, 32, 109,
// 101, 115, 115, 97,
// 103, 101
// ]
Given a Uint8Array, encodes the data as hex string:
const hex = wcb.encodeHex(new Uint8Array([
109, 121, 32, 109,
101, 115, 115, 97,
103, 101
]))
// 6d79206d657373616765
Takes a base64 string and encodes it to an Uint8Array:
const data = wcb.decodeBase64('bXkgbWVzc2FnZQ==')
// Uint8Array(10) [
// 109, 121, 32, 109,
// 101, 115, 115, 97,
// 103, 101
// ]
Given a Uint8Array, encodes the data as base64 string:
const base64 = wcb.encodeBase64(new Uint8Array([
109, 121, 32, 109,
101, 115, 115, 97,
103, 101
]))
// bXkgbWVzc2FnZQ==
Functions for generating a ecdh and aes-cbc keys, for deriving an aes-cbc key or the public key from a private one and for generating a sha-256 fingerprint of a key.
Generate aes-cbc key with a length of 256. The key will be extractable and can be used for encryption and decryption.
const key = await wcb.generateKey()
// CryptoKey {
// type: 'secret',
// extractable: true,
// algorithm: { name: 'AES-GCM', length: 256 },
// usages: [ 'encrypt', 'decrypt' ]
// }
Generates ecdh key pair with curve P-521
. The private key will be extractable, and can be used to derive a key.
const keyPair = await wcb.generateKeyPair()
// {
// publicKey: CryptoKey {
// type: 'public',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: []
// },
// privateKey: CryptoKey {
// type: 'private',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: [ 'deriveKey' ]
// }
// }
Given a private key, returns its corresponding public key. As there is no direct API for this in WebCrypto, this utilizes import and export of the key (in jwk
format), while removing the private key parts.
const { privateKey } = await wcb.generateKeyPair()
const publicKey = await wcb.getPublicKey(privateKey)
// CryptoKey {
// type: 'public',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: []
// }
Given a private and a public key, this function derives an aes-cbc key. For two key pairs A
and B
, the derived key will be the same for
deriveKey({ privateKey: A.privateKey, publicKey: B.publicKey })
and deriveKey({ privateKey: B.privateKey, publicKey: A.publicKey })
.
const { privateKey } = await wcb.generateKeyPair()
const { publicKey } = await wcb.generateKeyPair()
const key = await wcb.deriveKey({ privateKey, publicKey })
// CryptoKey {
// type: 'secret',
// extractable: true,
// algorithm: { name: 'AES-GCM', length: 256 },
// usages: [ 'encrypt', 'decrypt' ]
// }
Given a key pair, this function derives 16 bytes in an ArrayBuffer.
const { privateKey, publicKey } = await wcb.generateKeyPair()
const key = await wcb.deriveBits({ privateKey, publicKey })
// ArrayBuffer {
// [Uint8Contents]: <ac 54 d5 01 74 ca d6 87 f5 65 18 d0 4f e4 0f 18 77 ... more bytes>,
// byteLength: 16
// }
Given a key pair, this function derives a password of given length as an ArrayBuffer.
const { privateKey, publicKey } = await wcb.generateKeyPair()
const key = await wcb.derivePassword({ privateKey, publicKey, length: 16 })
// ArrayBuffer {
// [Uint8Contents]: <ac 54 d5 01 74 ca d6 87 f5 65 18 d0 4f e4 0f 18 77 ... more bytes>,
// byteLength: 16
// }
Methods for calculating fingerprints of public keys. Note that the fingerprints differ from the fingerprints from ssh-keygen
.
Calculate a SHA-256 fingerprint of a key. It has a length of 64 hex chars.
const { publicKey } = await wcb.generateKeyPair()
const fingerprintBits = await wcb.sha256Fingerprint(publicKey)
const fingerprint = wcb.encodeHex(fingerprintBits)
// aca8f766cdef8346177987a86b0f04b14fd4060b0e2478f941adc91982d6668c
Calculate a SHA-1 fingerprint of a key. It has a length of 40 hex chars.
const { publicKey } = await wcb.generateKeyPair()
const fingerprintBits = await wcb.sha1Fingerprint(publicKey)
const fingerprint = wcb.encodeHex(fingerprintBits)
// d04f73b7eb0b865a8d4711b5a379273a27c65581
Tools for exchanging keys. Also comes with convenient helpers to deal with PEM formatted keys.
Exports aes key data as ArrayBuffer:
const key = await wcb.generateKey()
const data = await wcb.exportKey(key)
// ArrayBuffer {
// [Uint8Contents]: <ac 54 d5 01 74 ca d6 87 f5 65 18 d0 4f e4 0f 18 77 ... more bytes>,
// byteLength: 32
// }
Import aes key data, returns CryptoKey:
const data = new Uint8Array([
210, 29, 179, 47, 204, 90, 109, 111, 95, 64, 50, 48, 192, 105, 44, 236,
74, 120, 2, 193, 83, 122, 22, 99, 202, 73, 20, 23, 187, 160, 140, 112
])
const key = await wcb.importKey(data)
// CryptoKey {
// type: 'secret',
// extractable: true,
// algorithm: { name: 'AES-GCM', length: 256 },
// usages: [ 'encrypt', 'decrypt' ]
// }
Utility function to export a public key as pem:
const { publicKey } = await wcb.generateKeyPair()
const pem = await wcb.exportPublicKeyPem(publicKey)
// -----BEGIN PUBLIC KEY-----
// MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBdjbvVyj7NglRaHLqgn2l+Rcw1Lev
// 50/9xL6qvIwYSv84jU6xLOMJGY7nouU0tuWmm1+ojHsd3raDfxjsNSmKSOEAbSFJ
// 1rnRvBU/DflEh0i/RofX4vmKH3quCKPQ8T1NQoQijyKEOjkQDFqDgpPW03SMusqs
// d9/kSDNOMLm+EAIA6C0=
// -----END PUBLIC KEY-----
Given a pem of a public key, returns the CryptoKey:
const pem = `-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBdjbvVyj7NglRaHLqgn2l+Rcw1Lev
50/9xL6qvIwYSv84jU6xLOMJGY7nouU0tuWmm1+ojHsd3raDfxjsNSmKSOEAbSFJ
1rnRvBU/DflEh0i/RofX4vmKH3quCKPQ8T1NQoQijyKEOjkQDFqDgpPW03SMusqs
d9/kSDNOMLm+EAIA6C0=
-----END PUBLIC KEY-----`
const publicKey = await wcb.importPublicKeyPem(pem)
// CryptoKey {
// type: 'public',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: []
// }
Utility function to export a private key as pem:
const { privateKey } = await wcb.generateKeyPair()
const pem = await wcb.exportPrivateKeyPem(privateKey)
// -----BEGIN PRIVATE KEY-----
// MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBcf8zEjlssqn4aTEB
// RR43ofwH/4BAXDAAd83Kz1Dyd+Ko0pit4ESgqSu/bJMdnDrpiGYuz0Klarwip8LD
// rYd9mEahgYkDgYYABAF2Nu9XKPs2CVFocuqCfaX5FzDUt6/nT/3Evqq8jBhK/ziN
// TrEs4wkZjuei5TS25aabX6iMex3etoN/GOw1KYpI4QBtIUnWudG8FT8N+USHSL9G
// h9fi+Yofeq4Io9DxPU1ChCKPIoQ6ORAMWoOCk9bTdIy6yqx33+RIM04wub4QAgDo
// LQ==
// -----END PRIVATE KEY-----
Given a pem of a private key, returns the CryptoKey:
const pem = `-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBcf8zEjlssqn4aTEB
RR43ofwH/4BAXDAAd83Kz1Dyd+Ko0pit4ESgqSu/bJMdnDrpiGYuz0Klarwip8LD
rYd9mEahgYkDgYYABAF2Nu9XKPs2CVFocuqCfaX5FzDUt6/nT/3Evqq8jBhK/ziN
TrEs4wkZjuei5TS25aabX6iMex3etoN/GOw1KYpI4QBtIUnWudG8FT8N+USHSL9G
h9fi+Yofeq4Io9DxPU1ChCKPIoQ6ORAMWoOCk9bTdIy6yqx33+RIM04wub4QAgDo
LQ==
-----END PRIVATE KEY-----`
const privateKey = await wcb.importPrivateKeyPem(pem)
// CryptoKey {
// type: 'private',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: [ 'deriveKey' ]
// }
Encrypt a private key with passphrase and export as PEM:
const { privateKey } = await wcb.generateKeyPair()
const passphrase = 'secure'
const pem = await wcb.exportEncryptedPrivateKeyPem({ key: privateKey, passphrase })
// -----BEGIN ENCRYPTED PRIVATE KEY-----
// MIIBZjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQi9FqU3dish14EV99
// Bz3tugIDAPoAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBK8mQ193fArwsz
// PKt2SvCkBIIBAOKBG4NQBWNDUUxSTJAMy5XnOU6nnX+Sisb9uu8/bAhxRtn3ItTo
// vGCs2MxtTKQhBRC5WdjU7oEe5rZAsWfoYdb567hPnl19QRaf2cneTNHT5qDdzRF+
// PrLRyw2+XUDEeeU5vhC4E29LZgigeYdSd2r8fOOcJxrKmMzkyFJCrYFhoqpdw2IS
// 4FQgJ9axQ2AncSaTqbuhBFQcoIFMrJ21ncVeEtTHS3428RHQJF1czNb/qnj/uIg7
// ta5OqDeXseEgnF+StrDcnSjkSuqMqXVeKsduZd/5JZz25sbHgLBzRgiqf/1jyrrl
// j8CC5qXX2PQH6RKBTys/1fRY++y3OVALhb8=
// -----END ENCRYPTED PRIVATE KEY-----
Decrypt passphrase encrypted private key pem and import the key:
const pem = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBZjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQi9FqU3dish14EV99
Bz3tugIDAPoAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBK8mQ193fArwsz
PKt2SvCkBIIBAOKBG4NQBWNDUUxSTJAMy5XnOU6nnX+Sisb9uu8/bAhxRtn3ItTo
vGCs2MxtTKQhBRC5WdjU7oEe5rZAsWfoYdb567hPnl19QRaf2cneTNHT5qDdzRF+
PrLRyw2+XUDEeeU5vhC4E29LZgigeYdSd2r8fOOcJxrKmMzkyFJCrYFhoqpdw2IS
4FQgJ9axQ2AncSaTqbuhBFQcoIFMrJ21ncVeEtTHS3428RHQJF1czNb/qnj/uIg7
ta5OqDeXseEgnF+StrDcnSjkSuqMqXVeKsduZd/5JZz25sbHgLBzRgiqf/1jyrrl
j8CC5qXX2PQH6RKBTys/1fRY++y3OVALhb8=
-----END ENCRYPTED PRIVATE KEY-----`
const passphrase = 'secure'
const privateKey = await wcb.importEncryptedPrivateKeyPem({ pem, passphrase })
// CryptoKey {
// type: 'private',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: [ 'deriveKey' ]
// }
Encrypt a private key with key pair and export as PEM:
const share = await wcb.generateKeyPair()
const alice = await wcb.generateKeyPair()
const bob = await wcb.generateKeyPair()
const pem = await wcb.exportEncryptedPrivateKeyPem({
key: share.privateKey,
privateKey: alice.privateKey,
publicKey: bob.publicKey
})
// -----BEGIN ENCRYPTED PRIVATE KEY-----
// MIIBZjBgBgkqhkiG9w0BBQ0wUzAyBgkqhkiG9w0BBQwwJQQQPjQ7/lIPfbHQQHGi
// QqXaJgIDAPoAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCI371u15dp+o/N
// Iqq+O3DQBIIBAJ+hnDVdydYcKYTmmhhUmwybqNkWEWi9pG4un5Xf7bEtm2A2qzoi
// 73XnmHPfXW+435RgMbLRtJOxqDa519kRvedO1nNIw1Iycs9GynTar+D+fBE/tFmJ
// 66XwlhKcKe0zMtSoi4FnkeyueEMYpJ7UDx+zVABqwwZdSFUrraeg6g/ljL1SGslg
// xDhTEyULoLyYV4G1+2t+rRXdzr408v6AAi+fJh/iBiwqd6clc+oNW0iXxHsi5/nH
// kETXf3RmXRonS7Ema+zhe3hMGlGGV1OMixaUZHGiIB6zc4fHHyzb5ippXEDkZ6e3
// U+l5Po65rsFkaAcDupfN18Ez9FRAyYbNkz8=
// -----END ENCRYPTED PRIVATE KEY-----
Decrypts encrypted private key PEM with key pair:
// alice and bob from above
const privateKey = await wcb.importEncryptedPrivateKeyPemFrom({
pem,
privateKey: bob.privateKey,
publicKey: alice.publicKey
})
// CryptoKey {
// type: 'private',
// extractable: true,
// algorithm: { name: 'ECDH', namedCurve: 'P-521' },
// usages: [ 'deriveKey' ]
// }
Encrypt and decrypt a message with aes-cbc, and with ECDH key pairs.
Encrypts a message with aes-cbc:
const key = await wcb.generateKey()
const text = 'my message'
const message = decodeText(text)
const data = await wcb.encrypt({ message, key })
// ArrayBuffer {
// [Uint8Contents]: <95 e1 e9 d4 72 74 27 6b b3 e3 e3 79 9e c3 dd f0 8a ... more bytes>,
// byteLength: 26
// }
Decrypts a message:
const key = await wcb.generateKey()
const text = 'my message'
const message = decodeText(text)
const box = await wcb.encrypt({ message, key })
const data = await wcb.decrypt({ box, key })
// ArrayBuffer {
// [Uint8Contents]: <6d 79 20 6d 65 73 73 61 67 65>,
// byteLength: 10
// }
Encrypts a message with aes-cbc for given private and public ecdh key:
const { privateKey, publicKey } = await wcb.generateKeyPair()
const text = 'my message'
const message = decodeText(text)
const data = await wcb.encryptoTo({ message, privateKey, publicKey })
// ArrayBuffer {
// [Uint8Contents]: <e3 93 88 af 9a 48 eb 44 cc a7 d1 11 ca 66 33 a2 31 ... more bytes>,
// byteLength: 26
// }
Decrypts a message for given private and public ecdh key:
const { privateKey, publicKey } = await wcb.generateKeyPair()
const text = 'my message'
const message = decodeText(text)
const box = await wcb.encryptoTo({ message, privateKey, publicKey })
const data = await wcb.decryptFrom({ box, privateKey, publicKey })
// ArrayBuffer {
// [Uint8Contents]: <6d 79 20 6d 65 73 73 61 67 65>,
// byteLength: 10
// }
There's a little test suite which ensures the lib works as expected. You can run it either directly:
node test/utils.js
node test/webcryptobox.js
or via npm:
npm test
This package is licensed under the Apache 2.0 License.
© 2022 Johannes J. Schmidt