feat(crypto): pkc algos for key generation, encrypt, and decrypt (#1663)
This commit is contained in:
@@ -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')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user