feat(crypto): pkc algos for key generation, encrypt, and decrypt (#1663)
This commit is contained in:
@@ -26,7 +26,8 @@
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"@typescript-eslint/parser": "^5.12.1",
|
||||
"eslint-plugin-prettier": "^4.2.1"
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.1.13"
|
||||
|
||||
7
packages/sncrypto-common/src/Types/PkcKeyPair.ts
Normal file
7
packages/sncrypto-common/src/Types/PkcKeyPair.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { HexString } from './HexString'
|
||||
|
||||
export type PkcKeyPair = {
|
||||
keyType: 'curve25519' | 'ed25519' | 'x25519'
|
||||
privateKey: HexString
|
||||
publicKey: HexString
|
||||
}
|
||||
@@ -8,3 +8,4 @@ export * from './StreamDecryptorResult'
|
||||
export * from './StreamEncryptor'
|
||||
export * from './Unencrypted'
|
||||
export * from './Utf8String'
|
||||
export * from './PkcKeyPair'
|
||||
|
||||
@@ -39,11 +39,13 @@
|
||||
"babel-loader": "^8.2.3",
|
||||
"chai": "^4.3.6",
|
||||
"connect": "^3.7.0",
|
||||
"eslint": "*",
|
||||
"eslint-plugin-prettier": "*",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"serve-static": "^1.14.2",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typedarray-to-buffer": "^4.0.0",
|
||||
"typescript": "*",
|
||||
"uuid": "^8.3.2",
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*"
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PureCryptoInterface,
|
||||
Utf8String,
|
||||
timingSafeEqual,
|
||||
PkcKeyPair,
|
||||
} from '@standardnotes/sncrypto-common'
|
||||
import * as Utils from './utils'
|
||||
import * as sodium from './libsodium'
|
||||
@@ -83,7 +84,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
iterations: number,
|
||||
length: number,
|
||||
): Promise<HexString | null> {
|
||||
const keyData = await Utils.stringToArrayBuffer(password)
|
||||
const keyData = Utils.stringToArrayBuffer(password)
|
||||
const key = await this.webCryptoImportKey(keyData, WebCryptoAlgs.Pbkdf2, [WebCryptoActions.DeriveBits])
|
||||
if (!key) {
|
||||
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> {
|
||||
const keyData = await Utils.hexStringToArrayBuffer(key)
|
||||
const ivData = await Utils.hexStringToArrayBuffer(iv)
|
||||
const keyData = Utils.hexStringToArrayBuffer(key)
|
||||
const ivData = Utils.hexStringToArrayBuffer(iv)
|
||||
const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData }
|
||||
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)
|
||||
return Utils.arrayBufferToBase64(result)
|
||||
}
|
||||
|
||||
public async aes256CbcDecrypt(ciphertext: Base64String, iv: HexString, key: HexString): Promise<Utf8String | null> {
|
||||
const keyData = await Utils.hexStringToArrayBuffer(key)
|
||||
const ivData = await Utils.hexStringToArrayBuffer(iv)
|
||||
const keyData = Utils.hexStringToArrayBuffer(key)
|
||||
const ivData = Utils.hexStringToArrayBuffer(iv)
|
||||
const alg = { name: WebCryptoAlgs.AesCbc, iv: ivData }
|
||||
const importedKeyData = await this.webCryptoImportKey(keyData, alg.name, [WebCryptoActions.Decrypt])
|
||||
const textData = await Utils.base64ToArrayBuffer(ciphertext)
|
||||
const textData = Utils.base64ToArrayBuffer(ciphertext)
|
||||
|
||||
try {
|
||||
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> {
|
||||
const keyHexData = await Utils.hexStringToArrayBuffer(key)
|
||||
const keyHexData = Utils.hexStringToArrayBuffer(key)
|
||||
const keyData = await this.webCryptoImportKey(keyHexData, WebCryptoAlgs.Hmac, [WebCryptoActions.Sign], {
|
||||
name: WebCryptoAlgs.Sha1,
|
||||
})
|
||||
const messageData = await Utils.stringToArrayBuffer(message)
|
||||
const messageData = Utils.stringToArrayBuffer(message)
|
||||
const funcParams = { name: WebCryptoAlgs.Hmac }
|
||||
|
||||
try {
|
||||
@@ -169,15 +170,14 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
}
|
||||
|
||||
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)
|
||||
return Utils.arrayBufferToHexString(digest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw string key to a WebCrypto CryptoKey object.
|
||||
* @param rawKey
|
||||
* A plain utf8 string or an array buffer
|
||||
* @param keyData
|
||||
* @param alg
|
||||
* The name of the algorithm this key will be used for (i.e 'AES-CBC' or 'HMAC')
|
||||
* @param actions
|
||||
@@ -220,7 +220,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
): Promise<HexString> {
|
||||
const params = {
|
||||
name: WebCryptoAlgs.Pbkdf2,
|
||||
salt: await Utils.stringToArrayBuffer(salt),
|
||||
salt: Utils.stringToArrayBuffer(salt),
|
||||
iterations: iterations,
|
||||
hash: { name: WebCryptoAlgs.Sha512 },
|
||||
}
|
||||
@@ -298,13 +298,13 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
public xchacha20StreamEncryptorPush(
|
||||
encryptor: StreamEncryptor,
|
||||
plainBuffer: Uint8Array,
|
||||
assocData: Utf8String,
|
||||
assocData?: Utf8String,
|
||||
tag: SodiumConstant = SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH,
|
||||
): Uint8Array {
|
||||
const encryptedBuffer = sodium.crypto_secretstream_xchacha20poly1305_push(
|
||||
encryptor.state as sodium.StateAddress,
|
||||
plainBuffer,
|
||||
assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
|
||||
assocData && assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
|
||||
tag,
|
||||
)
|
||||
return encryptedBuffer
|
||||
@@ -325,7 +325,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
public xchacha20StreamDecryptorPush(
|
||||
decryptor: StreamDecryptor,
|
||||
encryptedBuffer: Uint8Array,
|
||||
assocData: Utf8String,
|
||||
assocData?: Utf8String,
|
||||
): StreamDecryptorResult | false {
|
||||
if (encryptedBuffer.length < SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES) {
|
||||
throw new Error('Invalid ciphertext size')
|
||||
@@ -334,7 +334,7 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
const result = sodium.crypto_secretstream_xchacha20poly1305_pull(
|
||||
decryptor.state as sodium.StateAddress,
|
||||
encryptedBuffer,
|
||||
assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
|
||||
assocData && assocData.length > 0 ? Utils.stringToArrayBuffer(assocData) : null,
|
||||
)
|
||||
|
||||
if ((result as unknown) === false) {
|
||||
@@ -344,6 +344,51 @@ export class SNWebCrypto implements PureCryptoInterface {
|
||||
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
|
||||
*
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
export { SNWebCrypto } from './crypto'
|
||||
export {
|
||||
arrayBufferToBase64,
|
||||
arrayBufferToHexString,
|
||||
arrayBufferToString,
|
||||
base64Decode,
|
||||
base64Encode,
|
||||
base64ToArrayBuffer,
|
||||
base64ToHex,
|
||||
hexStringToArrayBuffer,
|
||||
hexToBase64,
|
||||
isWebCryptoAvailable,
|
||||
stringToArrayBuffer,
|
||||
base32Decode,
|
||||
base32Encode,
|
||||
} from './utils'
|
||||
export * from './crypto'
|
||||
export * from './utils'
|
||||
export { SodiumConstant } from '@standardnotes/sncrypto-common'
|
||||
|
||||
@@ -3,12 +3,15 @@ export {
|
||||
base64_variants,
|
||||
crypto_aead_xchacha20poly1305_ietf_decrypt,
|
||||
crypto_aead_xchacha20poly1305_ietf_encrypt,
|
||||
crypto_secretstream_xchacha20poly1305_push,
|
||||
crypto_secretstream_xchacha20poly1305_pull,
|
||||
crypto_secretstream_xchacha20poly1305_init_push,
|
||||
crypto_secretstream_xchacha20poly1305_init_pull,
|
||||
crypto_box_easy,
|
||||
crypto_box_keypair,
|
||||
crypto_box_open_easy,
|
||||
crypto_pwhash_ALG_DEFAULT,
|
||||
crypto_pwhash,
|
||||
crypto_secretstream_xchacha20poly1305_init_pull,
|
||||
crypto_secretstream_xchacha20poly1305_init_push,
|
||||
crypto_secretstream_xchacha20poly1305_pull,
|
||||
crypto_secretstream_xchacha20poly1305_push,
|
||||
from_base64,
|
||||
from_hex,
|
||||
from_string,
|
||||
|
||||
@@ -50,31 +50,18 @@ describe('crypto operations', async function () {
|
||||
|
||||
it('compares strings with timing safe comparison', async function () {
|
||||
const crypto = new SNWebCrypto()
|
||||
expect(crypto.timingSafeEqual('hello world 🌍', 'hello world 🌍')).to.equal(
|
||||
true,
|
||||
)
|
||||
expect(crypto.timingSafeEqual('helo world 🌍', 'hello world 🌍')).to.equal(
|
||||
false,
|
||||
)
|
||||
expect(crypto.timingSafeEqual('hello world 🌍', 'hello world 🌍')).to.equal(true)
|
||||
expect(crypto.timingSafeEqual('helo world 🌍', 'hello world 🌍')).to.equal(false)
|
||||
expect(crypto.timingSafeEqual('', 'a')).to.equal(false)
|
||||
expect(crypto.timingSafeEqual('', '')).to.equal(true)
|
||||
expect(
|
||||
crypto.timingSafeEqual(
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
),
|
||||
crypto.timingSafeEqual('2e1ee7920bb188a88f94bb912153befd83cc55cd', '2e1ee7920bb188a88f94bb912153befd83cc55cd'),
|
||||
).to.equal(true)
|
||||
expect(
|
||||
crypto.timingSafeEqual(
|
||||
'1e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
),
|
||||
crypto.timingSafeEqual('1e1ee7920bb188a88f94bb912153befd83cc55cd', '2e1ee7920bb188a88f94bb912153befd83cc55cd'),
|
||||
).to.equal(false)
|
||||
expect(
|
||||
crypto.timingSafeEqual(
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cc',
|
||||
'2e1ee7920bb188a88f94bb912153befd83cc55cd',
|
||||
),
|
||||
crypto.timingSafeEqual('2e1ee7920bb188a88f94bb912153befd83cc55cc', '2e1ee7920bb188a88f94bb912153befd83cc55cd'),
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -85,22 +72,16 @@ describe('crypto operations', async function () {
|
||||
|
||||
it('pbkdf2 1', async function () {
|
||||
const password = 'very_secure🔒'
|
||||
const salt =
|
||||
'c3feb78823adce65c4ab024dab9c5cdcda5a04cdbd98f65eac0311dfa432d67b'
|
||||
const expected =
|
||||
'bbb3d3af19dd1cbb901c958003faa55f193aad6a57fff30e51a62591bdc054d8'
|
||||
const salt = 'c3feb78823adce65c4ab024dab9c5cdcda5a04cdbd98f65eac0311dfa432d67b'
|
||||
const expected = 'bbb3d3af19dd1cbb901c958003faa55f193aad6a57fff30e51a62591bdc054d8'
|
||||
const result = await webCrypto.pbkdf2(password, salt, 100000, 256)
|
||||
expect(result).to.equal(expected)
|
||||
})
|
||||
|
||||
it('pbkdf2 2', async function () {
|
||||
const password = 'correct horse battery staple ✅'
|
||||
const salt = Buffer.from(
|
||||
'808182838485868788898a8b8c8d8e8f',
|
||||
'hex',
|
||||
).toString('utf8')
|
||||
const expected =
|
||||
'795d83b18e55d860d3799f85a20f66ee17eb9dcf041df1d7a13fac30af7103d9'
|
||||
const salt = Buffer.from('808182838485868788898a8b8c8d8e8f', 'hex').toString('utf8')
|
||||
const expected = '795d83b18e55d860d3799f85a20f66ee17eb9dcf041df1d7a13fac30af7103d9'
|
||||
const result = await webCrypto.pbkdf2(password, salt, 100000, 256)
|
||||
expect(result).to.equal(expected)
|
||||
})
|
||||
@@ -116,19 +97,16 @@ describe('crypto operations', async function () {
|
||||
|
||||
it('hmac 256', async function () {
|
||||
const text = 'hello world 🌍'
|
||||
const key =
|
||||
'e802dc953f3f1f7b5db62409b74ac848559d4711c4e0047ecc5e312ad8ab8397'
|
||||
const key = 'e802dc953f3f1f7b5db62409b74ac848559d4711c4e0047ecc5e312ad8ab8397'
|
||||
const hash = await webCrypto.hmac256(text, key)
|
||||
const expected =
|
||||
'b63f94ee33a067ffac3ee97c7987dd3171dcdc747a322bb3f3ab890201c8e6f9'
|
||||
const expected = 'b63f94ee33a067ffac3ee97c7987dd3171dcdc747a322bb3f3ab890201c8e6f9'
|
||||
expect(hash).to.equal(expected)
|
||||
})
|
||||
|
||||
it('sha256', async function () {
|
||||
const text = 'hello world 🌍'
|
||||
const hash = await webCrypto.sha256(text)
|
||||
const expected =
|
||||
'1e71fe32476da1ff115b44dfd74aed5c90d68a1d80a2033065e30cff4335211a'
|
||||
const expected = '1e71fe32476da1ff115b44dfd74aed5c90d68a1d80a2033065e30cff4335211a'
|
||||
expect(hash).to.equal(expected)
|
||||
})
|
||||
|
||||
@@ -154,13 +132,7 @@ describe('crypto operations', async function () {
|
||||
const bytes = 67108864
|
||||
const length = 16
|
||||
const iterations = 2
|
||||
const result = await webCrypto.argon2(
|
||||
password,
|
||||
salt,
|
||||
iterations,
|
||||
bytes,
|
||||
length,
|
||||
)
|
||||
const result = await webCrypto.argon2(password, salt, iterations, bytes, length)
|
||||
const expectedResult = '18dfbc268f251701652c8e38b5273f73'
|
||||
expect(result).to.equal(expectedResult)
|
||||
})
|
||||
@@ -172,15 +144,8 @@ describe('crypto operations', async function () {
|
||||
const bytes = 67108864
|
||||
const length = 32
|
||||
const iterations = 5
|
||||
const result = await webCrypto.argon2(
|
||||
password,
|
||||
truncatedSalt,
|
||||
iterations,
|
||||
bytes,
|
||||
length,
|
||||
)
|
||||
const expected =
|
||||
'bb6ec440708c271ce34decd7f997e2444d309b1105992779ccdb47f78a5fda6f'
|
||||
const result = await webCrypto.argon2(password, truncatedSalt, iterations, bytes, length)
|
||||
const expected = 'bb6ec440708c271ce34decd7f997e2444d309b1105992779ccdb47f78a5fda6f'
|
||||
expect(result).to.equal(expected)
|
||||
})
|
||||
|
||||
@@ -189,69 +154,38 @@ describe('crypto operations', async function () {
|
||||
const nonce = await webCrypto.generateRandomKey(192)
|
||||
const plaintext = 'hello world 🌍'
|
||||
const aad = JSON.stringify({ uuid: '123🎤' })
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
aad,
|
||||
)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key,
|
||||
aad,
|
||||
)
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, aad)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(ciphertext, nonce, key, aad)
|
||||
expect(decrypted).to.equal(plaintext)
|
||||
})
|
||||
|
||||
it('xchacha20 streaming encrypt/decrypt', async function () {
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
const bigFile = await fetch(
|
||||
'http://localhost:9003/test/resources/big_file.md',
|
||||
)
|
||||
const bigFile = await fetch('http://localhost:9003/test/resources/big_file.md')
|
||||
const bigText = await bigFile.text()
|
||||
const plaintext = bigText
|
||||
const plainBuffer = stringToArrayBuffer(plaintext)
|
||||
const encryptor = webCrypto.xchacha20StreamEncryptInitEncryptor(key)
|
||||
const header = base64StringToArrayBuffer(encryptor.header)
|
||||
const encryptor = webCrypto.xchacha20StreamInitEncryptor(key)
|
||||
|
||||
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 pullChunkSize =
|
||||
pushChunkSize +
|
||||
SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
|
||||
const pullChunkSize = pushChunkSize + SodiumConstant.CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES
|
||||
|
||||
for (let i = 0; i < plainBuffer.length; i += pushChunkSize) {
|
||||
const readUntil =
|
||||
i + pushChunkSize > plainBuffer.length
|
||||
? plainBuffer.length
|
||||
: i + pushChunkSize
|
||||
const chunk = webCrypto.xchacha20StreamEncryptorPush(
|
||||
encryptor,
|
||||
plainBuffer.slice(i, readUntil),
|
||||
)
|
||||
const readUntil = i + pushChunkSize > plainBuffer.length ? plainBuffer.length : i + pushChunkSize
|
||||
const chunk = webCrypto.xchacha20StreamEncryptorPush(encryptor, plainBuffer.slice(i, readUntil))
|
||||
encryptedBuffer = Buffer.concat([encryptedBuffer, chunk])
|
||||
}
|
||||
|
||||
const decryptor = webCrypto.xchacha20StreamEncryptInitDecryptor(
|
||||
header,
|
||||
key,
|
||||
)
|
||||
const decryptor = webCrypto.xchacha20StreamInitDecryptor(headerBase64, key)
|
||||
|
||||
let decryptedBuffer = Buffer.alloc(0)
|
||||
for (
|
||||
let i = header.length;
|
||||
i < encryptedBuffer.length;
|
||||
i += pullChunkSize
|
||||
) {
|
||||
const readUntil =
|
||||
i + pullChunkSize > encryptedBuffer.length
|
||||
? encryptedBuffer.length
|
||||
: i + pullChunkSize
|
||||
const chunk = webCrypto.xchacha20StreamDecryptorPush(
|
||||
decryptor,
|
||||
encryptedBuffer.slice(i, readUntil),
|
||||
)
|
||||
for (let i = headerBuffer.length; i < encryptedBuffer.length; 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])
|
||||
}
|
||||
|
||||
@@ -263,18 +197,8 @@ describe('crypto operations', async function () {
|
||||
const key = await webCrypto.generateRandomKey(256)
|
||||
const nonce = await webCrypto.generateRandomKey(192)
|
||||
const plaintext = 'hello world 🌍'
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
JSON.stringify({ uuid: 'foo🎲' }),
|
||||
)
|
||||
const result = await webCrypto.xchacha20Decrypt(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key,
|
||||
JSON.stringify({ uuid: 'bar🎲' }),
|
||||
)
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, JSON.stringify({ uuid: 'foo🎲' }))
|
||||
const result = await webCrypto.xchacha20Decrypt(ciphertext, nonce, key, JSON.stringify({ uuid: 'bar🎲' }))
|
||||
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.🌞"
|
||||
const assocData = await hexStringToArrayBuffer('50515253c0c1c2c3c4c5c6c7')
|
||||
const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657'
|
||||
const key =
|
||||
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
const key = '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
|
||||
const ciphertext = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, assocData)
|
||||
const expected =
|
||||
'vW0XnT6D1DuVdleUk8DpOVcqFwAlK/rMvtKQLCE5bLtzHH8bC0qmRAvzqC9O2n45rmTGcIxUwhbLlrcuEhO0Ui+Mm6QNtdlFsRtpuYLBu54/P6wrw2lIj3ayODVl0//5IflmTJdjfal2iBL2FcaLE7UuOqw6kdl7HV6PKzn0pIOeHH3rkwQ='
|
||||
expect(ciphertext).to.equal(expected)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(
|
||||
ciphertext,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(ciphertext, nonce, key, assocData)
|
||||
expect(decrypted).to.equal(plaintext)
|
||||
})
|
||||
|
||||
@@ -315,16 +228,10 @@ describe('crypto operations', async function () {
|
||||
).toString('utf8')
|
||||
const assocData = Buffer.from('50515253c0c1c2c3c4c5c6c7', 'hex')
|
||||
const nonce = '404142434445464748494a4b4c4d4e4f5051525354555657'
|
||||
const key =
|
||||
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
|
||||
const key = '808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'
|
||||
|
||||
/** Encrypt */
|
||||
const ciphertextBase64 = await webCrypto.xchacha20Encrypt(
|
||||
plaintext,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
const ciphertextBase64 = await webCrypto.xchacha20Encrypt(plaintext, nonce, key, assocData)
|
||||
const ciphertextHex = await base64ToHex(ciphertextBase64)
|
||||
const expected =
|
||||
'bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb' +
|
||||
@@ -335,12 +242,7 @@ describe('crypto operations', async function () {
|
||||
expect(ciphertextHex).to.equal(expected)
|
||||
|
||||
/** Decrypt */
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(
|
||||
ciphertextBase64,
|
||||
nonce,
|
||||
key,
|
||||
assocData,
|
||||
)
|
||||
const decrypted = await webCrypto.xchacha20Decrypt(ciphertextBase64, nonce, key, assocData)
|
||||
expect(decrypted).to.equal(plaintext)
|
||||
})
|
||||
|
||||
@@ -351,17 +253,44 @@ describe('crypto operations', async function () {
|
||||
const bytes = 67108864
|
||||
const length = 16
|
||||
const iterations = 2
|
||||
const result = await webCrypto.argon2(
|
||||
password,
|
||||
salt,
|
||||
iterations,
|
||||
bytes,
|
||||
length,
|
||||
)
|
||||
const result = await webCrypto.argon2(password, salt, iterations, bytes, length)
|
||||
const expectedResult = '720f95400220748a811bca9b8cff5d6e'
|
||||
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 () {
|
||||
const secret = await webCrypto.generateOtpSecret()
|
||||
expect(secret).to.have.length(32)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<script src="https://unpkg.com/chai/chai.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>
|
||||
Object.assign(window, SNCrypto);
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('utils', async function () {
|
||||
|
||||
it('base64URLEncode', async function () {
|
||||
const str = 'hello world'
|
||||
const b64 = await base64Encode(str)
|
||||
const b64 = await base64URLEncode(str)
|
||||
expect(b64).to.equal('aGVsbG8gd29ybGQ')
|
||||
})
|
||||
|
||||
|
||||
@@ -7429,6 +7429,7 @@ __metadata:
|
||||
"@typescript-eslint/parser": ^5.12.1
|
||||
eslint-plugin-prettier: ^4.2.1
|
||||
reflect-metadata: ^0.1.13
|
||||
typescript: "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -7448,6 +7449,7 @@ __metadata:
|
||||
buffer: ^6.0.3
|
||||
chai: ^4.3.6
|
||||
connect: ^3.7.0
|
||||
eslint: "*"
|
||||
eslint-plugin-prettier: "*"
|
||||
libsodium-wrappers: ^0.7.10
|
||||
reflect-metadata: ^0.1.13
|
||||
@@ -7455,6 +7457,7 @@ __metadata:
|
||||
serve-static: ^1.14.2
|
||||
ts-loader: ^9.2.6
|
||||
typedarray-to-buffer: ^4.0.0
|
||||
typescript: "*"
|
||||
uuid: ^8.3.2
|
||||
webpack: "*"
|
||||
webpack-cli: "*"
|
||||
|
||||
Reference in New Issue
Block a user