I've been trying to decrypt an ArrayBuffer object using CryptoJS, but so far it always returns a blank WordArray. The files (images) are encrypted in an iOS and Android app, sent to a server, and downloaded in this web app to be decrypted and displayed. The iOS and Android apps are able to decrypt the files without problems, so there's nothing wrong with the encryption process.
The files are downloaded with an XMLHttpRequest with responseType set to arraybuffer. Here's my code so far:
// Decrypt a Base64 encrypted string (this works perfectly)
String.prototype.aesDecrypt = function(key) {
var nkey = CryptoJS.enc.Hex.parse(key.sha256());
return CryptoJS.AES.decrypt(this.toString(), nkey, {
iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
}
// Decrypt a plain encrypted ArrayBuffer (this is the problem, it always outputs an empty WordArray)
ArrayBuffer.prototype.aesDecrypt = function(key) {
// Get key
if (!key) return null;
var nkey = CryptoJS.enc.Hex.parse(key.sha256());
// Get input (if I pass the ArrayBuffer directly to the create function, it returns
// a WordList with sigBytes set to NaN)
//var input = CryptoJS.lib.WordArray.create(this);
var input = CryptoJS.lib.WordArray.create(new Uint8Array(this));
// Decrypt
var output = CryptoJS.AES.decrypt(input, nkey, {
iv: CryptoJS.enc.Hex.parse('00000000000000000000000000000000'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// Output is an empty WordList
console.log("Output: ", output);
}
Another question I have is how do you convert a WordArray to an ArrayBuffer?
The conversion of ArrayBuffer -> WordArray has been discussed in CryptoJS's issue 46. For that reason a TypedWordArraywhere you can also pass an ArrayBuffer has been added.
To use that additionally include the following script:
<script src="http://crypto-js.googlecode.com/svn/tags/3.1/build/components/lib-typedarrays.js"></script>
Then you can simply do:
var wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
/* perform decryption of `wordArray` */
To reconvert the resulting decryptedWordArray to an ArrayBuffer, the simplest approach would probably be, to first convert it to a Base64-String (as discussed here) and then decode that String to the desired ArrayBuffer (see here). The whole procedure would look something like this:
dcWordArray = ... // your decrypted WordArray
dcBase64String = dcWordArray.toString(CryptoJS.enc.Base64); // to Base64-String
dcArrayBuffer = base64DecToArr(dcBase64String).buffer; // to ArrayBuffer
Edit:
For a more efficient conversion (no intermediate Base64String necessary) check out Aletheios answer to that question (the function wordToByteArray(wordArray) and then do .buffer).
you can use the web crypto API to directly decrypt arrayBuffer.
No need to convert binary data to a string, which is so inefficient, and it could cause memory and CPU rises.
Related
I have an asymmetric RSA key pair stored in two separate files. I want to generate a new symmetric key and encrypt it with public RSA key in my postbuild.js GULP script, so the user cannot access it. Then I want to send it to the C# server, where it would be decrypted and used.
I use the following JavaScript code in Node.js for encryption:
const generateAndEncryptKey = () => {
const symmetricKey = crypto.randomBytes(32);
const publicKey = fs.readFileSync("pubkey.pem", "utf8");
const encryptedSymmetricKey = crypto.publicEncrypt({
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
}, Buffer.from(symmetricKey)).toString("base64");
return encryptedSymmetricKey;
}
The above code somehow works and generates a base64 string that I later send to the server. I'm not sure if this is the correct way to do this.
But I'm unable to find a way to decrypt this string in C#. I tried to use the BouncyCastle library and the following code:
public string DecryptKey(string encryptedKey) {
var privateKey = #"-----BEGIN RSA PRIVATE KEY-----
...shortened...
-----END RSA PRIVATE KEY-----";
var bytesToDecrypt = Convert.FromBase64String(encryptedKey);
var decryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(privateKey)) {
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)new PemReader(txtreader).ReadObject();
decryptEngine.Init(false, keyPair.Private);
}
var decrypted = Encoding.UTF8.GetString(decryptEngine.ProcessBlock(bytesToDecrypt, 0, bytesToDecrypt.Length));
return decrypted;
}
But the ProcessBlock method always throws an InvalidCipherTextException "unknown block type".
Can someone help me to find out what am I doing wrong or point me to another better way of achieving this?
Decryption with the C# code fails because in the NodeJS code OAEP/SHA256 is used as padding and in the C# code PKCS#1 v1.5 padding. For decryption to work, both paddings must be identical. The padding in the C# code can be adapted to that of the NodeJS code as follows:
var decryptEngine = new OaepEncoding(new RsaEngine(), new Sha256Digest());
Also, the decrypted key must not be UTF-8 decoded as this corrupts the data. Either it is returned as byte[], or if conversion to a string is desired, a suitable binary-to-text encoding such as Base64 or hex must be used.
With these changes decryption works in the C# code.
I've got a blob of audio data confirmed to play in the browser but fails to play after storing, retrieving, and conversion of the same data. I've tried a few methods without success, each time returning the error:
Uncaught (in promise) DOMException: Failed to load because no supported source was found
Hasura notes that bytea data must be passed in as a String, so I tried a couple things.
Converting the blob into base64 stores fine but the retrieval and playing of the data doesn't work. I've tried doing conversions within the browser to base64 and then back into blob. I think it's just the data doesn't store properly as bytea if I convert it to base64 first:
// Storing bytea data as base64 string
const arrayBuffer = await blob.arrayBuffer();
const byteArray = new Uint8Array(arrayBuffer);
const charArray = Array.from(byteArray, (x: number) => String.fromCharCode(x));
const encodedString = window.btoa(charArray.join(''));
hasuraRequest....
`
mutation SaveAudioBlob ($input: String) {
insert_testerooey_one(
object: {
blubberz: $input
}
) {
id
blubberz
}
}
`,
{ input: encodedString }
);
// Decoding bytea data
const decodedString = window.atob(encodedString);
const decodedByteArray = new Uint8Array(decodedString.length).map((_, i) =>
decodedString.charCodeAt(i)
);
const decodedBlob = new Blob([decodedByteArray.buffer], { type: 'audio/mpeg' });
const audio4 = new Audio();
audio4.src = URL.createObjectURL(decodedBlob);
audio4.play();
Then I came across a Github issue (https://github.com/hasura/graphql-engine/issues/3336) suggesting the use of a computed field to convert the bytea data to base64, so I tried using that instead of my decoding attempt, only to be met with the same error:
CREATE OR REPLACE FUNCTION public.content_base64(mm testerooey)
RETURNS text
LANGUAGE sql
STABLE
AS $function$
SELECT encode(mm.blobberz, 'base64')
$function$
It seemed like a base64 string was not the way to store bytea data, so I tried converting the data to a hex string prior to storing. It stores ok, I think, but upon retrieval the data doesn't play, and I think it's a similar problem as storing as base64:
// Encoding to hex string
const arrayBuffer = await blob.arrayBuffer();
const byteArray = new Uint8Array(arrayBuffer);
const hexString = Array.from(byteArray, (byte) =>
byte.toString(16).padStart(2, '0')
).join('');
But using the decoded data didn't work again, regardless of whether I tried the computed field method or my own conversion methods. So, am I just not converting it right? Is my line of thinking incorrect? Or what is it I'm doing wrong?
I've got it working if I just convert to base64 and store as a text field but I'd prefer to store as bytea because it takes up less space. I think something's wrong with how the data is either stored, retrieved, or converted, but I don't know how to do it. I know the blob itself is fine because when generated I can play audio with it, it only bugs out after fetching and attempted conversion its stored value. Any ideas?
Also, I'd really like to not store the file in another service like s3, even if drastically simpler.
I encrypt a text in Java and want to decrypt in Javascript and vice versa, my Java code looks like this
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec k = new SecretKeySpec(this.key.getBytes(), "AES");
c.init(Cipher.ENCRYPT_MODE, k);
byte[] encryptedData = c.doFinal(this.value.getBytes());
byte[] iv = c.getIV();
sb.append(new String(Base64.getEncoder().encode(iv)));
sb.append(';');
sb.append(new String(Base64.getEncoder().encode(encryptedData)));
// send sb.toString() to the other end
I tried node-forge and elliptic for decryption in Nodejs, in node-forge I have an error complaining about tag which I don't have, can someone provide a solution in Nodejs.
Javascript Code
function convertFromMxBase64(FormatEncryptedString) {
const speratorIndex = FormatEncryptedString.indexOf(';');
const encryptedBase64 = mxFormatEncryptedString.substr(speratorIndex + 1);
const ivBase64 = mxFormatEncryptedString.substr(0, speratorIndex);
return {
iv: forge.util.createBuffer(forge.util.decode64(ivBase64), 'utf8'),
payload: forge.util.createBuffer(forge.util.decode64(encryptedBase64), 'utf8')
};
}
function decrypt() {
let { iv, payload } = convertFromMxBase64(mxCombi);
const key = forge.util.createBuffer(theKey, 'utf8');
var cipher = forge.cipher.createDecipher("AES-GCM", key); // forge.rc2.createDecryptionCipher(key);
cipher.start({ iv });
cipher.update(payload);
cipher.finish();
var encrypted = cipher.output;
// outputs encrypted hex
console.log(encrypted.toHex());
}
The GCM mode uses by definition a tag which is needed for the authenticity/integrity check.
The tag issue on the NodeJS side is caused by not taking the authentication tag into account. On the Java side this is not necessary because the SunJCE provider does this automatically by appending the tag to the ciphertext: Therefore, the last 16 bytes of the result are the tag. On the NodeJS side, in contrast, ciphertext and tag are handled detached, so that both parts must be explicitly separated, e.g.:
var ciphertext = forge.util.createBuffer(payload.data.substring(0, payload.length() - 16));
var tag = forge.util.createBuffer(payload.data.substring(payload.length() - 16));
Tag and ciphertext must be passed to the cipher instance as follows:
cipher.start({ iv:iv, tag:tag }); // apply tag
cipher.update(ciphertext); // apply ciphertext
Another bug is that the UTF-8 encoding is used when creating iv and payload. This is wrong, the data must be passed as binary string, i.e. 'utf8' must either be replaced by 'binary' or removed completely.
iv: forge.util.createBuffer(forge.util.decode64(ivBase64)),
payload: forge.util.createBuffer(forge.util.decode64(encryptedBase64))
Also, in getBytes() of the Java code an encoding should be specified, e.g. getBytes(StandardCharsets.UTF_8). If this is not done, a platform-dependent default encoding is used, which means that different encodings can be applied depending on the environment.
With these changes, decryption works on the NodeJS side.
An API that I am trying to use is returning base64 encoded responses. The response is first compressed using GZip with 4 bits of offset and then base64 encoded. I tried parsing the response using JavaScript (pako and zlib) and in both cases, it failed. The API has an example of C# code on how should the response decompression work, but I don't really know how can I transform that into JavaScript. So can anyone give me a hand transforming this function into JavaScript or just give me some tips on how to handle the 4 bytes offset? I didn't find anything relevant in the libraries' documentation.
public string Decompress(string value)
{
byte[] gzBuffer = Convert.FromBase64String(value);
using (MemoryStream ms = new MemoryStream())
{
int msgLength = BitConverter.ToInt32(gzBuffer, 0);
ms.Write(gzBuffer, 4, gzBuffer.Length - 4);
byte[] buffer = new byte[msgLength];
ms.Position = 0;
using (System.IO.Compression.GZipStream zip = new System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Decompress))
{
zip.Read(buffer, 0, buffer.Length);
}
return System.Text.Encoding.Unicode.GetString(buffer, 0, buffer.Length);
}
}
I'm going to use fflate (disclaimer, I'm the author) to do this. If you want to translate that function line for line:
// This is ES6 code; if you want better browser compatibility
// use the ES5 variant.
import { gunzipSync, strToU8, strFromU8 } from 'fflate';
const decompress = str => {
// atob converts Base64 to Latin-1
// strToU8(str, true) converts Latin-1 to binary
const bytes = strToU8(atob(str), true);
// subarray creates a new view on the same memory buffer
// gunzipSync synchronously decompresses
// strFromU8 converts decompressed binary to UTF-8
return strFromU8(gunzipSync(bytes.subarray(4)));
}
If you don't know what ES6 is:
In your HTML file:
<script src="https://cdn.jsdelivr.net/npm/fflate/umd/index.js"></script>
In your JS:
var decompress = function(str) {
var bytes = fflate.strToU8(atob(str), true);
return fflate.strFromU8(fflate.gunzipSync(bytes.subarray(4)));
}
I'd like to mention that streams are almost totally useless if you're going to accumulate into a string at the end, so the C# code is suboptimal. At the same time, since you're using the standard library, it is the only option.
In addition, I'd highly recommend using the callback variant (i.e. gunzip instead of gunzipSync) if possible because that runs on a separate thread to avoid causing the browser to freeze.
I am trying to concatenate a string and a Readable stream (the readable stream is pointing to a file which may have data in multiple chunks, i.e. the file may be large) into one writable stream so that the writable stream can finally be written to a destination.
I am encrypting the string and content of the file and then applying zlib compression on them, then finally I want to pipe them to the writable stream.
To achieve this, I can:
a) Convert the file content into a string then concatenate both the string then encrypt, do compression and then finally pipe it into the writable stream. But this is not possible because the file may be big in size, thus I can't convert its content to string.
b) I can first encrypt and compress the string then convert the string into a stream then pipe it into the writable stream after that is done completely, pipe the file contents into the same writable stream.
To do so, I have written this:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
password = 'd6FAjkdlfAjk';
var stream = require('stream');
var fs = require('fs');
var zlib = require('zlib');
// input file
var r = fs.createReadStream('file.txt');
// zip content
var zip = zlib.createGzip();
// encrypt content
var encrypt = crypto.createCipheriv(algorithm, password, iv);
var w = fs.createWriteStream('file.out');
// the string contents are being converted into a stream so that they can be piped
var Readable = stream.Readable;
var s = new Readable();
s._read = function noop() {};
s.push(hexiv+':');
s.push(null);
s.pipe(zlib.createGzip()).pipe(w);
// start pipe when 'end' event is encountered
s.on('end', function(){
r.pipe(zip).pipe(encrypt).pipe(w);
});
What I observe is:
Only the first pipe is done successfully, i.e. the string is written to the file.out. The second pipe doesn't make any difference on the output destination. At first, I thought that the reason might be due to asynchronous behavior of pipe. So, for this reason, I am piping the file content after the first piping is closed. But still, I didn't get the desired output.
I want to know why is this happening any the appropriate way for doing this.
Found the reason why was the second piping to the writable stream w wasn't working.
When the first .pipe(w) finishes, the writable at w is also automatically closed.
So instead of using s.pipe(zlib.createGzip()).pipe(w); I should have used:
s.pipe(zlib.createGzip()).pipe(w, {end: false});
Where the {end: false} is passed to pipe() as the second argument, stating that the writable should not get closed when the piping is done.