feat(crypto): pkc algos for key generation, encrypt, and decrypt (#1663)
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
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 './StreamEncryptor'
|
||||||
export * from './Unencrypted'
|
export * from './Unencrypted'
|
||||||
export * from './Utf8String'
|
export * from './Utf8String'
|
||||||
|
export * from './PkcKeyPair'
|
||||||
|
|||||||
@@ -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": "*"
|
||||||
|
|||||||
@@ -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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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'
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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: "*"
|
||||||
|
|||||||
Reference in New Issue
Block a user