Closed grant-arqit closed 8 months ago
Here's a working version using standard node webcrypto
import { Buffer as IsomorphicBuffer } from 'buffer/'
const { subtle } = globalThis.crypto
class Buffer extends IsomorphicBuffer {}
const base64Decode = (base64String: string): Uint8Array => {
return Uint8Array.from(Buffer.from(base64String, 'base64'))
}
const byteArrayToString = (byteArray: Uint8Array): string => {
return Buffer.from(byteArray).toString('utf8')
}
const decryptWithPreshared = async (encodedKey: string, encryptedEncodedData: string ): Promise<string> => {
const encryptedData = base64Decode(encryptedEncodedData)
const key = base64Decode(encodedKey)
const iv = encryptedData.subarray(0, 12)
const dataToDecrypt = encryptedData.subarray(12)
const cryptoKey = await subtle.importKey('raw', key, "AES-GCM", false, ['decrypt'])
const cipher: ArrayBuffer = await subtle.decrypt({ name: "AES-GCM", iv }, cryptoKey, dataToDecrypt)
return byteArrayToString(new Uint8Array(cipher))
}
decryptWithPreshared('U5yzshQRpa1aplMWK/QvbUXlHQyyVk+Zwxz8fyLKRLQ=', '0L5oGCBu1Jbe/TTcMu+4wVbK32vRK7UptQjC1zedbpL6Zq3j')
.then((txt) => {
console.log(`Decrypted string: ${txt}`)
})
Hey @grant-arqit !
I checked the case, and there may be a mismatch in types or behavior in k6 vs. nodejs. I need to check deeply what exactly, but currently, it's that in the k6, you have to:
'k6/encoding'
module for base64 decoding wrap subarray
into Uint8Array
:thinking:
For instance, below, I did some minimal working version using the data that you've provided:
import { crypto } from "k6/experimental/webcrypto";
import encoding from 'k6/encoding';
const displayArrayBufferContent = (buffer) => {
return [...new Uint8Array(buffer)]
.map((x) => x.toString(10))
.join(" ");
}
function arrayBufferToString(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
const base64Decode = (base64String) => {
return new Uint8Array(encoding.b64decode(base64String));
}
export default async function() {
const keyData = base64Decode('U5yzshQRpa1aplMWK/QvbUXlHQyyVk+Zwxz8fyLKRLQ=');
const key = await crypto.subtle.importKey('raw', keyData, "AES-GCM", false, ['decrypt']);
const encryptedData = base64Decode('0L5oGCBu1Jbe/TTcMu+4wVbK32vRK7UptQjC1zedbpL6Zq3j');
const iv = new Uint8Array(encryptedData.subarray(0, 12));
const ciphertext = new Uint8Array(encryptedData.subarray(12));
console.log('iv: ' + displayArrayBufferContent(iv));
console.log('ciphertext: ' + displayArrayBufferContent(ciphertext));
const plaintext = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
ciphertext,
);
console.log("Decrypted string: '" + arrayBufferToString(plaintext) + "'")
}
that will result me with:
INFO[0000] iv: 208 190 104 24 32 110 212 150 222 253 52 220 source=console
INFO[0000] ciphertext: 50 239 184 193 86 202 223 107 209 43 181 41 181 8 194 215 55 157 110 146 250 102 173 227 source=console
INFO[0000] Decrypted string: 'MySecret' source=console
Let me know if that helps Cheers!
I think we also should update the k6's webcrypto docs to showcase such import :thinking:
Thanks @olegbespalov , that's really useful feedback. I'll see if I can adapt our encoding libraries to use k6 encoding and see what further progress can be made.
HI @olegbespalov. I have made some progress, however I am hitting on some k6 specific encoding issues by the looks of things. Do you know how url encoding is treated?
One of my strings which includes *^
characters is encodced to %2A%5E
respectively as per RFC3986, but I get the following error.
ERRO[0000] Error: GoError: illegal base64 data at input byte 40
Hey @grant-arqit !
Glad to hear that :relaxed:
Our encoding module is a wrapper around Golang's encoding. You have some control over which encoding is used. See the documentation at https://grafana.com/docs/k6/latest/javascript-api/k6-encoding/b64decode/ or the Golang documentation at https://pkg.go.dev/encoding/base64#pkg-variables.
So I might think that you should use something like encoding.b64decode(enc, 'url')
.
I'm not sure about RFC3986. Since it's about Uniform Resource Identifier (URI). Golang, base64 implementation is based on https://www.rfc-editor.org/rfc/rfc4648.html.
I've managed to resolve my crypto / encoding issues now. Thanks very much your help.
I have a service which encrypts data in a kotlin appliction with the following function.
The console output of the above standalone code is an encrypted, base64 encoded string. One example of the encrypted string is
0L5oGCBu1Jbe/TTcMu+4wVbK32vRK7UptQjC1zedbpL6Zq3j
(each execution will always be different because of the iv prefix element based on a random value)I've fed the encoded/encrypted string into the K6 test below and I get an OperationError when calling crypto.subtle.decrypt (the import function is fine).
Are you able advise what may be going wrong here and if there are any workarounds for a K6 test? Are there any known interoperability issues either between
javax.crypto
libraries and k6 webcrypto? The buffer npm libs used in the decode and byteArray to string routines successfully execute in the test, but could the conversion / decoded output be incompatible when supplying the output of these functions as inputs to k6 webcrypto? Incidently when decrypting this in a nodejs runtime environment with theisomorphic-webcrypto
implementation, decryption works fine without error.