feat(crypto): pkc algos for key generation, encrypt, and decrypt (#1663)

This commit is contained in:
Mo
2022-09-28 09:15:34 -05:00
committed by GitHub
parent 554f59a3fe
commit a83003ee69
11 changed files with 161 additions and 183 deletions

View File

@@ -26,7 +26,8 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.30.0", "@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.12.1", "@typescript-eslint/parser": "^5.12.1",
"eslint-plugin-prettier": "^4.2.1" "eslint-plugin-prettier": "^4.2.1",
"typescript": "*"
}, },
"dependencies": { "dependencies": {
"reflect-metadata": "^0.1.13" "reflect-metadata": "^0.1.13"

View File

@@ -0,0 +1,7 @@
import { HexString } from './HexString'
export type PkcKeyPair = {
keyType: 'curve25519' | 'ed25519' | 'x25519'
privateKey: HexString
publicKey: HexString
}

View File

@@ -8,3 +8,4 @@ export * from './StreamDecryptorResult'
export * from './StreamEncryptor' export * from './StreamEncryptor'
export * from './Unencrypted' export * from './Unencrypted'
export * from './Utf8String' export * from './Utf8String'
export * from './PkcKeyPair'

View File

@@ -39,11 +39,13 @@
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"chai": "^4.3.6", "chai": "^4.3.6",
"connect": "^3.7.0", "connect": "^3.7.0",
"eslint": "*",
"eslint-plugin-prettier": "*", "eslint-plugin-prettier": "*",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"serve-static": "^1.14.2", "serve-static": "^1.14.2",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typedarray-to-buffer": "^4.0.0", "typedarray-to-buffer": "^4.0.0",
"typescript": "*",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"webpack": "*", "webpack": "*",
"webpack-cli": "*" "webpack-cli": "*"

View File

@@ -9,6 +9,7 @@ import {
PureCryptoInterface, PureCryptoInterface,
Utf8String, Utf8String,
timingSafeEqual, timingSafeEqual,
PkcKeyPair,
} from '@standardnotes/sncrypto-common' } from '@standardnotes/sncrypto-common'
import * as Utils from './utils' import * as Utils from './utils'
import * as sodium from './libsodium' import * as sodium from './libsodium'
@@ -83,7 +84,7 @@ export class SNWebCrypto implements PureCryptoInterface {
iterations: number, iterations: number,
length: number, length: number,
): Promise<HexString | null> { ): Promise<HexString | null> {
const keyData = await Utils.stringToArrayBuffer(password) const keyData = Utils.stringToArrayBuffer(password)
const key = await this.webCryptoImportKey(keyData, WebCryptoAlgs.Pbkdf2, [WebCryptoActions.DeriveBits]) const key = await this.webCryptoImportKey(keyData, WebCryptoAlgs.Pbkdf2, [WebCryptoActions.DeriveBits])
if (!key) { if (!key) {
console.error('Key is null, unable to continue') console.error('Key is null, unable to continue')
@@ -99,21 +100,21 @@ export class SNWebCrypto implements PureCryptoInterface {
} }
public async aes256CbcEncrypt(plaintext: Utf8String, iv: HexString, key: HexString): Promise<Base64String> { public async aes256CbcEncrypt(plaintext: Utf8String, iv: HexString, key: HexString): Promise<Base64String> {
const keyData = await Utils.hexStringToArrayBuffer(key) const keyData = Utils.hexStringToArrayBuffer(key)
const ivData = await Utils.hexStringToArrayBuffer(iv) const ivData = Utils.hexStringToArrayBuffer(iv)
const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData } const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData }
const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Encrypt]) const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Encrypt])
const textData = await Utils.stringToArrayBuffer(plaintext) const textData = Utils.stringToArrayBuffer(plaintext)
const result = await crypto.subtle.encrypt(alg, importedKeyData, textData) const result = await crypto.subtle.encrypt(alg, importedKeyData, textData)
return Utils.arrayBufferToBase64(result) return Utils.arrayBufferToBase64(result)
} }
public async aes256CbcDecrypt(ciphertext: Base64String, iv: HexString, key: HexString): Promise<Utf8String | null> { public async aes256CbcDecrypt(ciphertext: Base64String, iv: HexString, key: HexString): Promise<Utf8String | null> {
const keyData = await Utils.hexStringToArrayBuffer(key) const keyData = Utils.hexStringToArrayBuffer(key)
const ivData = await Utils.hexStringToArrayBuffer(iv) const ivData = Utils.hexStringToArrayBuffer(iv)
const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData } const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData }
const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Decrypt]) const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Decrypt])
const textData = await Utils.base64ToArrayBuffer(ciphertext) const textData = Utils.base64ToArrayBuffer(ciphertext)
try { try {
const result = await crypto.subtle.decrypt(alg, importedKeyData, textData) const result = await crypto.subtle.decrypt(alg, importedKeyData, textData)
@@ -150,11 +151,11 @@ export class SNWebCrypto implements PureCryptoInterface {
} }
public async hmac1(message: Utf8String, key: HexString): Promise<HexString | null> { public async hmac1(message: Utf8String, key: HexString): Promise<HexString | null> {
const keyHexData = await Utils.hexStringToArrayBuffer(key) const keyHexData = Utils.hexStringToArrayBuffer(key)
const keyData = await this.webCryptoImportKey(keyHexData, WebCryptoAlgs.Hmac, [WebCryptoActions.Sign], { const keyData = await this.webCryptoImportKey(keyHexData, WebCryptoAlgs.Hmac, [WebCryptoActions.Sign], {
name: WebCryptoAlgs.Sha1, name: WebCryptoAlgs.Sha1,
}) })
const messageData = await Utils.stringToArrayBuffer(message) const messageData = Utils.stringToArrayBuffer(message)
const funcParams = { name: WebCryptoAlgs.Hmac } const funcParams = { name: WebCryptoAlgs.Hmac }
try { try {
@@ -169,15 +170,14 @@ export class SNWebCrypto implements PureCryptoInterface {
} }
public async unsafeSha1(text: string): Promise<string> { public async unsafeSha1(text: string): Promise<string> {
const textData = await Utils.stringToArrayBuffer(text) const textData = Utils.stringToArrayBuffer(text)
const digest = await crypto.subtle.digest(WebCryptoAlgs.Sha1, textData) const digest = await crypto.subtle.digest(WebCryptoAlgs.Sha1, textData)
return Utils.arrayBufferToHexString(digest) return Utils.arrayBufferToHexString(digest)
} }
/** /**
* Converts a raw string key to a WebCrypto CryptoKey object. * Converts a raw string key to a WebCrypto CryptoKey object.
* @param rawKey * @param keyData
* A plain utf8 string or an array buffer
* @param alg * @param alg
* The name of the algorithm this key will be used for (i.e 'AES-CBC' or 'HMAC') * The name of the algorithm this key will be used for (i.e 'AES-CBC' or 'HMAC')
* @param actions * @param actions
@@ -220,7 +220,7 @@ export class SNWebCrypto implements PureCryptoInterface {
): Promise<HexString> { ): Promise<HexString> {
const params = { const params = {
name: WebCryptoAlgs.Pbkdf2, name: WebCryptoAlgs.Pbkdf2,
salt: await Utils.stringToArrayBuffer(salt), salt: Utils.stringToArrayBuffer(salt),
iterations: iterations, iterations: iterations,
hash: { name: WebCryptoAlgs.Sha512 }, hash: { name: WebCryptoAlgs.Sha512 },
} }
@@ -298,13 +298,13 @@ export class SNWebCrypto implements PureCryptoInterface {
public xchacha20StreamEncryptorPush( public xchacha20StreamEncryptorPush(
encryptor: StreamEncryptor, encryptor: StreamEncryptor,
plainBuffer: Uint8Array, plainBuffer: Uint8Array,
assocData: Utf8String, assocData?: Utf8String,
tag: SodiumConstant = SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH, tag: SodiumConstant = SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
): Uint8Array { ): Uint8Array {
const encryptedBuffer = sodium.crypto_secretstream_xchacha20poly1305_push( const encryptedBuffer = sodium.crypto_secretstream_xchacha20poly1305_push(
encryptor.state as sodium.StateAddress, encryptor.state as sodium.StateAddress,
plainBuffer, plainBuffer,
assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null, assocData && assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
tag, tag,
) )
return encryptedBuffer return encryptedBuffer
@@ -325,7 +325,7 @@ export class SNWebCrypto implements PureCryptoInterface {
public xchacha20StreamDecryptorPush( public xchacha20StreamDecryptorPush(
decryptor: StreamDecryptor, decryptor: StreamDecryptor,
encryptedBuffer: Uint8Array, encryptedBuffer: Uint8Array,
assocData: Utf8String, assocData?: Utf8String,
): StreamDecryptorResult | false { ): StreamDecryptorResult | false {
if (encryptedBuffer.length < SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) { if (encryptedBuffer.length < SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) {
throw new Error('Invalid ciphertext size') throw new Error('Invalid ciphertext size')
@@ -334,7 +334,7 @@ export class SNWebCrypto implements PureCryptoInterface {
const result = sodium.crypto_secretstream_xchacha20poly1305_pull( const result = sodium.crypto_secretstream_xchacha20poly1305_pull(
decryptor.state as sodium.StateAddress, decryptor.state as sodium.StateAddress,
encryptedBuffer, encryptedBuffer,
assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null, assocData && assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
) )
if ((result as unknown) === false) { if ((result as unknown) === false) {
@@ -344,6 +344,51 @@ export class SNWebCrypto implements PureCryptoInterface {
return result return result
} }
/**
* https://doc.libsodium.org/public-key_cryptography/authenticated_encryption
*/
public sodiumCryptoBoxEasyEncrypt(
message: Utf8String,
nonce: HexString,
senderSecretKey: HexString,
recipientPublicKey: HexString,
): Base64String {
const result = sodium.crypto_box_easy(
message,
Utils.hexStringToArrayBuffer(nonce),
Utils.hexStringToArrayBuffer(recipientPublicKey),
Utils.hexStringToArrayBuffer(senderSecretKey),
)
return Utils.arrayBufferToBase64(result)
}
public sodiumCryptoBoxEasyDecrypt(
ciphertext: Base64String,
nonce: HexString,
senderPublicKey: HexString,
recipientSecretKey: HexString,
): Base64String {
const result = sodium.crypto_box_open_easy(
Utils.base64ToArrayBuffer(ciphertext),
Utils.hexStringToArrayBuffer(nonce),
Utils.hexStringToArrayBuffer(senderPublicKey),
Utils.hexStringToArrayBuffer(recipientSecretKey),
'text',
)
return result
}
public sodiumCryptoBoxGenerateKeypair(): PkcKeyPair {
const result = sodium.crypto_box_keypair()
const publicKey = Utils.arrayBufferToHexString(result.publicKey)
const privateKey = Utils.arrayBufferToHexString(result.privateKey)
return { publicKey, privateKey, keyType: result.keyType }
}
/** /**
* Generates a random secret for TOTP authentication * Generates a random secret for TOTP authentication
* *

View File

@@ -1,16 +1,3 @@
export { SNWebCrypto } from './crypto' export * from './crypto'
export { export * from './utils'
arrayBufferToBase64, export { SodiumConstant } from '@standardnotes/sncrypto-common'
arrayBufferToHexString,
arrayBufferToString,
base64Decode,
base64Encode,
base64ToArrayBuffer,
base64ToHex,
hexStringToArrayBuffer,
hexToBase64,
isWebCryptoAvailable,
stringToArrayBuffer,
base32Decode,
base32Encode,
} from './utils'

View File

@@ -3,12 +3,15 @@ export {
base64_variants, base64_variants,
crypto_aead_xchacha20poly1305_ietf_decrypt, crypto_aead_xchacha20poly1305_ietf_decrypt,
crypto_aead_xchacha20poly1305_ietf_encrypt, crypto_aead_xchacha20poly1305_ietf_encrypt,
crypto_secretstream_xchacha20poly1305_push, crypto_box_easy,
crypto_secretstream_xchacha20poly1305_pull, crypto_box_keypair,
crypto_secretstream_xchacha20poly1305_init_push, crypto_box_open_easy,
crypto_secretstream_xchacha20poly1305_init_pull,
crypto_pwhash_ALG_DEFAULT, crypto_pwhash_ALG_DEFAULT,
crypto_pwhash, crypto_pwhash,
crypto_secretstream_xchacha20poly1305_init_pull,
crypto_secretstream_xchacha20poly1305_init_push,
crypto_secretstream_xchacha20poly1305_pull,
crypto_secretstream_xchacha20poly1305_push,
from_base64, from_base64,
from_hex, from_hex,
from_string, from_string,

View File

@@ -50,31 +50,18 @@ describe('crypto operations', async function () {
it('compares strings with timing safe comparison', async function () { it('compares strings with timing safe comparison', async function () {
const crypto = new SNWebCrypto() const crypto = new SNWebCrypto()
expect(crypto.timingSafeEqual('hello world 🌍', 'hello world 🌍')).to.equal( expect(crypto.timingSafeEqual('hello world 🌍', 'hello world 🌍')).to.equal(true)
true, expect(crypto.timingSafeEqual('helo world 🌍', 'hello world 🌍')).to.equal(false)
)
expect(crypto.timingSafeEqual('helo world 🌍', 'hello world 🌍')).to.equal(
false,
)
expect(crypto.timingSafeEqual('', 'a')).to.equal(false) expect(crypto.timingSafeEqual('', 'a')).to.equal(false)
expect(crypto.timingSafeEqual('', '')).to.equal(true) expect(crypto.timingSafeEqual('', '')).to.equal(true)
expect( expect(
crypto.timingSafeEqual( crypto.timingSafeEqual('2e1ee7920bb188a88f94bb912153befd83cc55cd', '2e1ee7920bb188a88f94bb912153befd83cc55cd'),
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
),
).to.equal(true) ).to.equal(true)
expect( expect(
crypto.timingSafeEqual( crypto.timingSafeEqual('1e1ee7920bb188a88f94bb912153befd83cc55cd', '2e1ee7920bb188a88f94bb912153befd83cc55cd'),
'1e1ee7920bb188a88f94bb912153befd83cc55cd',
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
),
).to.equal(false) ).to.equal(false)
expect( expect(
crypto.timingSafeEqual( crypto.timingSafeEqual('2e1ee7920bb188a88f94bb912153befd83cc55cc', '2e1ee7920bb188a88f94bb912153befd83cc55cd'),
'2e1ee7920bb188a88f94bb912153befd83cc55cc',
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
),
).to.equal(false) ).to.equal(false)
}) })
@@ -85,22 +72,16 @@ describe('crypto operations', async function () {
it('pbkdf2 1', async function () { it('pbkdf2 1', async function () {
const password = 'very_secure🔒' const password = 'very_secure🔒'
const salt = const salt = 'c3feb78823adce65c4ab024dab9c5cdcda5a04cdbd98f65eac0311dfa432d67b'
'c3feb78823adce65c4ab024dab9c5cdcda5a04cdbd98f65eac0311dfa432d67b' const expected = 'bbb3d3af19dd1cbb901c958003faa55f193aad6a57fff30e51a62591bdc054d8'
const expected =
'bbb3d3af19dd1cbb901c958003faa55f193aad6a57fff30e51a62591bdc054d8'
const result = await webCrypto.pbkdf2(password, salt, 100000, 256) const result = await webCrypto.pbkdf2(password, salt, 100000, 256)
expect(result).to.equal(expected) expect(result).to.equal(expected)
}) })
it('pbkdf2 2', async function () { it('pbkdf2 2', async function () {
const password = 'correct horse battery staple ✅' const password = 'correct horse battery staple ✅'
const salt = Buffer.from( const salt = Buffer.from('808182838485868788898a8b8c8d8e8f', 'hex').toString('utf8')
'808182838485868788898a8b8c8d8e8f', const expected = '795d83b18e55d860d3799f85a20f66ee17eb9dcf041df1d7a13fac30af7103d9'
'hex',
).toString('utf8')
const expected =
'795d83b18e55d860d3799f85a20f66ee17eb9dcf041df1d7a13fac30af7103d9'
const result = await webCrypto.pbkdf2(password, salt, 100000, 256) const result = await webCrypto.pbkdf2(password, salt, 100000, 256)
expect(result).to.equal(expected) expect(result).to.equal(expected)
}) })
@@ -116,19 +97,16 @@ describe('crypto operations', async function () {
it('hmac 256', async function () { it('hmac 256', async function () {
const text = 'hello world 🌍' const text = 'hello world 🌍'
const key = const key = 'e802dc953f3f1f7b5db62409b74ac848559d4711c4e0047ecc5e312ad8ab8397'
'e802dc953f3f1f7b5db62409b74ac848559d4711c4e0047ecc5e312ad8ab8397'
const hash = await webCrypto.hmac256(text, key) const hash = await webCrypto.hmac256(text, key)
const expected = const expected = 'b63f94ee33a067ffac3ee97c7987dd3171dcdc747a322bb3f3ab890201c8e6f9'
'b63f94ee33a067ffac3ee97c7987dd3171dcdc747a322bb3f3ab890201c8e6f9'
expect(hash).to.equal(expected) expect(hash).to.equal(expected)
}) })
it('sha256', async function () { it('sha256', async function () {
const text = 'hello world 🌍' const text = 'hello world 🌍'
const hash = await webCrypto.sha256(text) const hash = await webCrypto.sha256(text)
const expected = const expected = '1e71fe32476da1ff115b44dfd74aed5c90d68a1d80a2033065e30cff4335211a'
'1e71fe32476da1ff115b44dfd74aed5c90d68a1d80a2033065e30cff4335211a'
expect(hash).to.equal(expected) expect(hash).to.equal(expected)
}) })
@@ -154,13 +132,7 @@ describe('crypto operations', async function () {
const bytes = 67108864 const bytes = 67108864
const length = 16 const length = 16
const iterations = 2 const iterations = 2
const result = await webCrypto.argon2( const result = await webCrypto.argon2(password, salt, iterations, bytes, length)
password,
salt,
iterations,
bytes,
length,
)
const expectedResult = '18dfbc268f251701652c8e38b5273f73' const expectedResult = '18dfbc268f251701652c8e38b5273f73'
expect(result).to.equal(expectedResult) expect(result).to.equal(expectedResult)
}) })
@@ -172,15 +144,8 @@ describe('crypto operations', async function () {
const bytes = 67108864 const bytes = 67108864
const length = 32 const length = 32
const iterations = 5 const iterations = 5
const result = await webCrypto.argon2( const result = await webCrypto.argon2(password, truncatedSalt, iterations, bytes, length)
password, const expected = 'bb6ec440708c271ce34decd7f997e2444d309b1105992779ccdb47f78a5fda6f'
truncatedSalt,
iterations,
bytes,
length,
)
const expected =
'bb6ec440708c271ce34decd7f997e2444d309b1105992779ccdb47f78a5fda6f'
expect(result).to.equal(expected) expect(result).to.equal(expected)
}) })
@@ -189,69 +154,38 @@ describe('crypto operations', async function () {
const nonce = await webCrypto.generateRandomKey(192) const nonce = await webCrypto.generateRandomKey(192)
const plaintext = 'hello world 🌍' const plaintext = 'hello world 🌍'
const aad = JSON.stringify({ uuid: '123🎤' }) const aad = JSON.stringify({ uuid: '123🎤' })
const ciphertext = await webCrypto.xchacha20Encrypt( const ciphertext = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, aad)
plaintext, const decrypted = await webCrypto.xchacha20Decrypt(ciphertext, nonce, key, aad)
nonce,
key,
aad,
)
const decrypted = await webCrypto.xchacha20Decrypt(
ciphertext,
nonce,
key,
aad,
)
expect(decrypted).to.equal(plaintext) expect(decrypted).to.equal(plaintext)
}) })
it('xchacha20 streaming encrypt/decrypt', async function () { it('xchacha20 streaming encrypt/decrypt', async function () {
const key = await webCrypto.generateRandomKey(256) const key = await webCrypto.generateRandomKey(256)
const bigFile = await fetch( const bigFile = await fetch('http://localhost:9003/test/resources/big_file.md')
'http://localhost:9003/test/resources/big_file.md',
)
const bigText = await bigFile.text() const bigText = await bigFile.text()
const plaintext = bigText const plaintext = bigText
const plainBuffer = stringToArrayBuffer(plaintext) const plainBuffer = stringToArrayBuffer(plaintext)
const encryptor = webCrypto.xchacha20StreamEncryptInitEncryptor(key) const encryptor = webCrypto.xchacha20StreamInitEncryptor(key)
const header = base64StringToArrayBuffer(encryptor.header)
let encryptedBuffer = Buffer.concat([header]) const headerBase64 = encryptor.header
const headerBuffer = base64ToArrayBuffer(encryptor.header)
let encryptedBuffer = Buffer.concat([headerBuffer])
const pushChunkSize = plainBuffer.length / 200 const pushChunkSize = plainBuffer.length / 200
const pullChunkSize = const pullChunkSize = pushChunkSize + SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
pushChunkSize +
SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
for (let i = 0; i < plainBuffer.length; i += pushChunkSize) { for (let i = 0; i < plainBuffer.length; i += pushChunkSize) {
const readUntil = const readUntil = i + pushChunkSize > plainBuffer.length ? plainBuffer.length : i + pushChunkSize
i + pushChunkSize > plainBuffer.length const chunk = webCrypto.xchacha20StreamEncryptorPush(encryptor, plainBuffer.slice(i, readUntil))
? plainBuffer.length
: i + pushChunkSize
const chunk = webCrypto.xchacha20StreamEncryptorPush(
encryptor,
plainBuffer.slice(i, readUntil),
)
encryptedBuffer = Buffer.concat([encryptedBuffer, chunk]) encryptedBuffer = Buffer.concat([encryptedBuffer, chunk])
} }
const decryptor = webCrypto.xchacha20StreamEncryptInitDecryptor( const decryptor = webCrypto.xchacha20StreamInitDecryptor(headerBase64, key)
header,
key,
)
let decryptedBuffer = Buffer.alloc(0) let decryptedBuffer = Buffer.alloc(0)
for ( for (let i = headerBuffer.length; i < encryptedBuffer.length; i += pullChunkSize) {
let i = header.length; const readUntil = i + pullChunkSize > encryptedBuffer.length ? encryptedBuffer.length : i + pullChunkSize
i < encryptedBuffer.length; const chunk = webCrypto.xchacha20StreamDecryptorPush(decryptor, encryptedBuffer.slice(i, readUntil))
i += pullChunkSize
) {
const readUntil =
i + pullChunkSize > encryptedBuffer.length
? encryptedBuffer.length
: i + pullChunkSize
const chunk = webCrypto.xchacha20StreamDecryptorPush(
decryptor,
encryptedBuffer.slice(i, readUntil),
)
decryptedBuffer = Buffer.concat([decryptedBuffer, chunk.message]) decryptedBuffer = Buffer.concat([decryptedBuffer, chunk.message])
} }
@@ -263,18 +197,8 @@ describe('crypto operations', async function () {
const key = await webCrypto.generateRandomKey(256) const key = await webCrypto.generateRandomKey(256)
const nonce = await webCrypto.generateRandomKey(192) const nonce = await webCrypto.generateRandomKey(192)
const plaintext = 'hello world 🌍' const plaintext = 'hello world 🌍'
const ciphertext = await webCrypto.xchacha20Encrypt( const ciphertext = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, JSON.stringify({ uuid: 'foo🎲' }))
plaintext, const result = await webCrypto.xchacha20Decrypt(ciphertext, nonce, key, JSON.stringify({ uuid: 'bar🎲' }))
nonce,
key,
JSON.stringify({ uuid: 'foo🎲' }),
)
const result = await webCrypto.xchacha20Decrypt(
ciphertext,
nonce,
key,
JSON.stringify({ uuid: 'bar🎲' }),
)
expect(result).to.not.be.ok expect(result).to.not.be.ok
}) })
@@ -284,23 +208,12 @@ describe('crypto operations', async function () {
"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.🌞" "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.🌞"
const assocData = await hexStringToArrayBuffer('50515253c0c1c2c3c4c5c6c7') const assocData = await hexStringToArrayBuffer('50515253c0c1c2c3c4c5c6c7')
const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657' const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657'
const key = const key = '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f' const ciphertext = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, assocData)
const ciphertext = await webCrypto.xchacha20Encrypt(
plaintext,
nonce,
key,
assocData,
)
const expected = const expected =
'vW0XnT6D1DuVdleUk8DpOVcqFwAlK/rMvtKQLCE5bLtzHH8bC0qmRAvzqC9O2n45rmTGcIxUwhbLlrcuEhO0Ui+Mm6QNtdlFsRtpuYLBu54/P6wrw2lIj3ayODVl0//5IflmTJdjfal2iBL2FcaLE7UuOqw6kdl7HV6PKzn0pIOeHH3rkwQ=' 'vW0XnT6D1DuVdleUk8DpOVcqFwAlK/rMvtKQLCE5bLtzHH8bC0qmRAvzqC9O2n45rmTGcIxUwhbLlrcuEhO0Ui+Mm6QNtdlFsRtpuYLBu54/P6wrw2lIj3ayODVl0//5IflmTJdjfal2iBL2FcaLE7UuOqw6kdl7HV6PKzn0pIOeHH3rkwQ='
expect(ciphertext).to.equal(expected) expect(ciphertext).to.equal(expected)
const decrypted = await webCrypto.xchacha20Decrypt( const decrypted = await webCrypto.xchacha20Decrypt(ciphertext, nonce, key, assocData)
ciphertext,
nonce,
key,
assocData,
)
expect(decrypted).to.equal(plaintext) expect(decrypted).to.equal(plaintext)
}) })
@@ -315,16 +228,10 @@ describe('crypto operations', async function () {
).toString('utf8') ).toString('utf8')
const assocData = Buffer.from('50515253c0c1c2c3c4c5c6c7', 'hex') const assocData = Buffer.from('50515253c0c1c2c3c4c5c6c7', 'hex')
const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657' const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657'
const key = const key = '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
/** Encrypt */ /** Encrypt */
const ciphertextBase64 = await webCrypto.xchacha20Encrypt( const ciphertextBase64 = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, assocData)
plaintext,
nonce,
key,
assocData,
)
const ciphertextHex = await base64ToHex(ciphertextBase64) const ciphertextHex = await base64ToHex(ciphertextBase64)
const expected = const expected =
'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb' + 'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb' +
@@ -335,12 +242,7 @@ describe('crypto operations', async function () {
expect(ciphertextHex).to.equal(expected) expect(ciphertextHex).to.equal(expected)
/** Decrypt */ /** Decrypt */
const decrypted = await webCrypto.xchacha20Decrypt( const decrypted = await webCrypto.xchacha20Decrypt(ciphertextBase64, nonce, key, assocData)
ciphertextBase64,
nonce,
key,
assocData,
)
expect(decrypted).to.equal(plaintext) expect(decrypted).to.equal(plaintext)
}) })
@@ -351,17 +253,44 @@ describe('crypto operations', async function () {
const bytes = 67108864 const bytes = 67108864
const length = 16 const length = 16
const iterations = 2 const iterations = 2
const result = await webCrypto.argon2( const result = await webCrypto.argon2(password, salt, iterations, bytes, length)
password,
salt,
iterations,
bytes,
length,
)
const expectedResult = '720f95400220748a811bca9b8cff5d6e' const expectedResult = '720f95400220748a811bca9b8cff5d6e'
expect(result).to.equal(expectedResult) expect(result).to.equal(expectedResult)
}) })
it('pkc crypto_box_easy keypair generation', async function () {
const keypair = await webCrypto.sodiumCryptoBoxGenerateKeypair()
expect(keypair.keyType).to.equal('x25519')
expect(keypair.publicKey.length).to.equal(64)
expect(keypair.privateKey.length).to.equal(64)
})
it('pkc crypto_box_easy encrypt/decrypt', async function () {
const senderKeypair = await webCrypto.sodiumCryptoBoxGenerateKeypair()
const recipientKeypair = await webCrypto.sodiumCryptoBoxGenerateKeypair()
const nonce = await webCrypto.generateRandomKey(192)
const plaintext = 'hello world 🌍'
const ciphertext = await webCrypto.sodiumCryptoBoxEasyEncrypt(
plaintext,
nonce,
senderKeypair.privateKey,
recipientKeypair.publicKey,
)
expect(ciphertext.length).to.equal(44)
const decrypted = await webCrypto.sodiumCryptoBoxEasyDecrypt(
ciphertext,
nonce,
senderKeypair.publicKey,
recipientKeypair.privateKey,
)
expect(decrypted).to.equal(plaintext)
})
it('generates random OTP secret 160 bits long', async function () { it('generates random OTP secret 160 bits long', async function () {
const secret = await webCrypto.generateOtpSecret() const secret = await webCrypto.generateOtpSecret()
expect(secret).to.have.length(32) expect(secret).to.have.length(32)

View File

@@ -6,7 +6,7 @@
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" /> <link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<script src="https://unpkg.com/chai/chai.js"></script> <script src="https://unpkg.com/chai/chai.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script> <script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="../../../node_modules/regenerator-runtime/runtime.js"></script> <script src="https://unpkg.com/regenerator-runtime@0.13.9/runtime.js"></script>
<script src="../dist/sncrypto-web.js"></script> <script src="../dist/sncrypto-web.js"></script>
<script> <script>
Object.assign(window, SNCrypto); Object.assign(window, SNCrypto);

View File

@@ -42,7 +42,7 @@ describe('utils', async function () {
it('base64URLEncode', async function () { it('base64URLEncode', async function () {
const str = 'hello world' const str = 'hello world'
const b64 = await base64Encode(str) const b64 = await base64URLEncode(str)
expect(b64).to.equal('aGVsbG8gd29ybGQ') expect(b64).to.equal('aGVsbG8gd29ybGQ')
}) })

View File

@@ -7429,6 +7429,7 @@ __metadata:
"@typescript-eslint/parser": ^5.12.1 "@typescript-eslint/parser": ^5.12.1
eslint-plugin-prettier: ^4.2.1 eslint-plugin-prettier: ^4.2.1
reflect-metadata: ^0.1.13 reflect-metadata: ^0.1.13
typescript: "*"
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@@ -7448,6 +7449,7 @@ __metadata:
buffer: ^6.0.3 buffer: ^6.0.3
chai: ^4.3.6 chai: ^4.3.6
connect: ^3.7.0 connect: ^3.7.0
eslint: "*"
eslint-plugin-prettier: "*" eslint-plugin-prettier: "*"
libsodium-wrappers: ^0.7.10 libsodium-wrappers: ^0.7.10
reflect-metadata: ^0.1.13 reflect-metadata: ^0.1.13
@@ -7455,6 +7457,7 @@ __metadata:
serve-static: ^1.14.2 serve-static: ^1.14.2
ts-loader: ^9.2.6 ts-loader: ^9.2.6
typedarray-to-buffer: ^4.0.0 typedarray-to-buffer: ^4.0.0
typescript: "*"
uuid: ^8.3.2 uuid: ^8.3.2
webpack: "*" webpack: "*"
webpack-cli: "*" webpack-cli: "*"