Emoticons and chars like дей are broken while UInt8 conversion - javascript

i have the problem that I get JS-Objects with fields like ... title: "действительный有効なដែលមាន... or for example containing this emote 😍. Now, due to encryption, I need to convert this information to UInt8.
The code that handles the conversion looks like this (and works properly in the general case ...)
public arrayBufferToString(arrayBuffer : Uint8Array) : string {
var str : string = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));
return str;
}
public stringToArrayBuffer(string:string) : Uint8Array {
if(typeof string === 'undefined' || string === null){
this.$log.warn('Cannot convert an undefined string');
return null;
}
var arrayBuffer : any = new ArrayBuffer(string.length);
var buffer : Uint8Array = new Uint8Array(arrayBuffer);
for (var i : number = 0, stringLength : number = string.length; i < stringLength; i++) {
buffer[i] = string.charCodeAt(i);
}
return buffer;
}
In these situations, however, I have an input like this:
"title":"действительный有効なដែលមានសុពលភាព有效有效માન્યમાבתוקף" and an output, after formatting it back to a string, like this
"title":"459AB28B5;L=K9 ¹j¶»¶ H H®¾¨Í¯®¾ÑêÕçã"
Do you have any idea why this happens? Thank you!

The following scripts encrypts and decrypts dataToEncrypt. It uses modern Encoding API.
"use strict";
var dataToEncrypt = "дей ... hopefully, it means 'day' :)";
var iv = window.crypto.getRandomValues(new Uint8Array(16));
var algorithm = {
name: "AES-CBC",
iv: iv
};
window.crypto.subtle.generateKey(
{
name: "AES-CBC",
length: 128
},
/* extractable */ true,
/*keyUsages */ ['encrypt', 'decrypt']
).
then(function (aesKey) {
var uint16array = new TextEncoder('utf-16').encode(dataToEncrypt);
window.crypto.subtle.encrypt(algorithm, aesKey, uint16array)
.then(function (encryptedArrayBuffer) {
console.log("encrypted", ab2hexstr(encryptedArrayBuffer));
window.crypto.subtle.decrypt(algorithm, aesKey, encryptedArrayBuffer)
.then(function (result) {
console.log("decrypted", new TextDecoder('utf-16').decode(result));
})
.catch(function (err) {
console.log("Decryption failed.", err);
});
})
.catch(function (err) {
console.log("Encryption failed.", err);
});
}
).
catch(function (err) {
console.log("generateKey - failed.", err);
});
function ab2hexstr(buf) {
var byteArray = new Uint8Array(buf);
var hexString = "";
var nextHexByte;
for (var i = 0; i < byteArray.byteLength; i++) {
nextHexByte = byteArray[i].toString(16); // Integer to base 16
if (nextHexByte.length < 2) {
nextHexByte = "0" + nextHexByte; // Otherwise 10 becomes just a instead of 0a
}
hexString += nextHexByte;
}
return hexString;
}
jsFiddle for the previous script
jsFiddle for a version without Encoding API
The scripts are based on crypto.js.
Note:
I did not use TypeScript but you can add types as you like.
Resources:
https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder/encode
https://googlechrome.github.io/samples/encoding-api/index.html
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt

Related

Unzipping encrypted compressed report from Amazon Selling Partner

I am using the getReport operation to fetch the documentId, which later I use to download the report document itself which is encrypted and compressed.
The code looks like this:
const documentData = await this.sellingPartner.callAPI({
operation: "getReportDocument",
endpoint: "reports",
path: { reportDocumentId: reportData.reportDocumentId }
})
const request = https.get(documentData.url, function(res) {
const data = [];
res.on("data", chunk => data.push(chunk));
res.on("end", () => {
const key = new Buffer.from(documentData.encryptionDetails.key, 'base64')
const initializationVector = new Buffer.from(documentData.encryptionDetails.initializationVector, 'base64')
const input = Buffer.concat(data)
let result;
try {
result = aes.decryptText(
aes.CIPHERS.AES_256,
key,
initializationVector,
input
)
} catch (e) {
console.log(e)
}
console.log(">>>>")
console.log(result)
zlib.gunzip(result, (err, unzipped) => {
debugger
});
});
}
The current error I am getting is from zlib:
Error: incorrect header check
at Zlib.zlibOnError [as onerror] (node:zlib:189:17)
I am getting the same even if I pass the unencrypted value directly to zlib.
There is a Sample Java code example in the docs, but I cannot understand very well where they do the decryption: before unzipping or after?
In any case, what is the right way to solve this: unzip and decrypt or decrypt and unzip? The former does not work at all, the latter almost works but fails at the unzipping part.
How can I solve the unzip problem?
Short Answer:
Decrypt first and then decompress if compression type is specified.
Longer answer:
After a little research I stumbled upon this example (sadly written in python not in javascript) but it covers the steps to do in more detail.
https://github.com/amzn/selling-partner-api-docs/issues/186#issuecomment-756108550
It contains both compressed or not compressed cases.
Specifically for compressed it looks like this:
import gzip
import requests
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
def ase_cbc_decryptor(key, iv, encryption):
cipher = Cipher(algorithms.AES(base64.b64decode(key)), modes.CBC(base64.b64decode(iv)))
decryptor = cipher.decryptor()
decrypted_text = decryptor.update(encryption)
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpaded_text = unpadder.update(decrypted_text)
return unpaded_text + unpadder.finalize()
def get_report_document_content(self, key, iv, url, compression_type=None):
resp = requests.get(url=url)
resp_content = resp.content
decrypted_content = ase_cbc_decryptor(key=key, iv=iv, encryption=resp_content)
if compression_type == 'GZIP':
decrypted_content = gzip.decompress(decrypted_content)
code = 'utf-8'
if 'cp1252' in resp.headers.get('Content-Type', '').lower():
code = 'Cp1252'
return decrypted_content.decode(code)
P.S. keep in mind you need to use AES in CBC mode
Short example here:
https://gist.github.com/manuks/5cef1e536ef791e97b39
var keyhex = "8479768f48481eeb9c8304ce0a58481eeb9c8304ce0a5e3cb5e3cb58479768f4"; //length 32
var blockSize = 16;
function encryptAES(input) {
try {
var iv = require('crypto').randomBytes(16);
//console.info('iv',iv);
var data = new Buffer(input).toString('binary');
//console.info('data',data);
key = new Buffer(keyhex, "hex");
//console.info(key);
var cipher = require('crypto').createCipheriv('aes-256-cbc', key, iv);
// UPDATE: crypto changed in v0.10
// https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var nodev = process.version.match(/^v(\d+)\.(\d+)/);
var encrypted;
if( nodev[1] === '0' && parseInt(nodev[2]) < 10) {
encrypted = cipher.update(data, 'binary') + cipher.final('binary');
} else {
encrypted = cipher.update(data, 'utf8', 'binary') + cipher.final('binary');
}
var encoded = new Buffer(iv, 'binary').toString('hex') + new Buffer(encrypted, 'binary').toString('hex');
return encoded;
} catch (ex) {
// handle error
// most likely, entropy sources are drained
console.error(ex);
}
}
function decryptAES(encoded) {
var combined = new Buffer(encoded, 'hex');
key = new Buffer(keyhex, "hex");
// Create iv
var iv = new Buffer(16);
combined.copy(iv, 0, 0, 16);
edata = combined.slice(16).toString('binary');
// Decipher encrypted data
var decipher = require('crypto').createDecipheriv('aes-256-cbc', key, iv);
// UPDATE: crypto changed in v0.10
// https://github.com/joyent/node/wiki/Api-changes-between-v0.8-and-v0.10
var nodev = process.version.match(/^v(\d+)\.(\d+)/);
var decrypted, plaintext;
if( nodev[1] === '0' && parseInt(nodev[2]) < 10) {
decrypted = decipher.update(edata, 'binary') + decipher.final('binary');
plaintext = new Buffer(decrypted, 'binary').toString('utf8');
} else {
plaintext = (decipher.update(edata, 'binary', 'utf8') + decipher.final('utf8'));
}
return plaintext;
}
var input="testing";
var encrypted = encryptAES(input);
console.info('encrypted:', encrypted);
var decryped = decryptAES(encrypted);
console.info('decryped:',decryped);

Why does signing algorithm in C# give different result than the one in Javascript

This is the algorithm for signing the data in C# using a private key from a certificate that is used from both me and the client in order to define an unique key to identify the user:
X509Certificate2 keyStore = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "Certifikatat\\" + certPath, certPass, X509KeyStorageFlags.Exportable);
RSA privateKey = keyStore.GetRSAPrivateKey();
byte[] iicSignature = privateKey.SignData(Encoding.ASCII.GetBytes("K31418036C|2022-5-16 13:30:41|406|st271ir481|al492py609|zz463gy579|340"), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
byte[] iic = ((HashAlgorithm)CryptoConfig,CreateFromName("MD5")).ComputeHash(iicSignature);
I then pass the private key to my Javascript using Bouncy Castle:
X509Certificate2 keyStore = new X509Certificate2(AppDomain.CurrentDomain.BaseDirectory + "Certifikatat\\" + certPath, certPass, X509KeyStorageFlags.Exportable);
RSA privateKey = keyStore.GetRSAPrivateKey();
var eky = DotNetUtilities.GetRsaKeyPair(privateKey);
Pkcs8Generator pkcs8Gen = new Pkcs8Generator(eky.Private);
Org.BouncyCastle.Utilities.IO.Pem.PemObject pkcs8 = pkcs8Gen.Generate();
PemWriter pemWriter = new PemWriter(new StringWriter());
pemWriter.WriteObject(pkcs8);
pemWriter.Writer.Flush();
return pemWriter.Writer.ToString();
This one is the algorithm used in Javascript:
window.crypto.subtle.importKey(
"pkcs8",
pemToArrayBuffer(pkcs8Pem), {
name: "RSASSA-PKCS1-v1_5",
hash: {
name: "SHA-256"
},
},
false, ["sign"]
)
.then(function(privateKey) {
console.log(privateKey);
// Sign: RSA with SHA256 and PKCS#1 v1.5 padding
window.crypto.subtle.sign({
name: "RSASSA-PKCS1-v1_5",
},
privateKey,
new TextEncoder().encode("K31418036C|2022-5-16 13:30:41|406|st271ir481|al492py609|zz463gy579|340")
)
.then(function(signature) {
var iic = md5(signature);
console.log(ab2b64(signature));
})
.catch(function(err) {
console.error(err);
});
})
.catch(function(err) {
console.error(err);
});
function ab2b64(arrayBuffer) {
return window.btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
function removeLines(str) {
str = str.replace("\r", "");
return str.replace("\n", "");
}
function base64ToArrayBuffer(b64) {
var byteString = atob(b64);
var byteArray = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
return byteArray;
}
function pemToArrayBuffer(pem) {
var b64Lines = removeLines(pem);
var b64Prefix = b64Lines.replace('-----BEGIN PRIVATE KEY-----', '');
var b64Final = b64Prefix.replace('-----END PRIVATE KEY-----', '');
return base64ToArrayBuffer(b64Final);
}
The signatures returned are different for some reason. I need them to be the same or else it's all pointless because the client won't be authenticated.
The results are as follow:
C#:
57CF663ACBEDE6305309682BA7261412
Javascript:
c099d176dcd95c59d748d6066dcd462e
I had to convert my signature to base64 and then encode it with atob() after that i needed this md5 library to hash the data and then use .toUpperCase() to reproduce the correct result.
The complete code looks like this:
md5(atob(ab2b64(signature))).toUpperCase();
Now i get the same result from both C# and JS.

RSA-OAEP encryption in Javascript and decryption in Java

I am currently implementing the RSA-OAEP encryption on Javascript and decryption at Java.
My javascript code has the following
function stringToArrayBuffer(str){
var buf = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function arrayBufferToString(str){
var byteArray = new Uint8Array(str);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
function encryptDataWithPublicKey(data, key) {
data = stringToArrayBuffer(data);
return window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
//label: Uint8Array([...]) //optional
},
key, //from generateKey or importKey above
data //ArrayBuffer of data you want to encrypt
);
}
var pem = Config.encryption.publicKey;
// fetch the part of the PEM string between header and footer
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
// base64 decode the string to get the binary data
const binaryDerString = window.atob(pemContents);
// convert from a binary string to an ArrayBuffer
const binaryDer = stringToArrayBuffer(binaryDerString);
window.crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: { name: "SHA-256" }
},
true,
["encrypt"]
).then(function (publicKey) {
encryptDataWithPublicKey(text, publicKey).then((result) => {
var rdata = arrayBufferToString(result);
resolve(rdata);
});
}).catch(function (err) {
console.log(err);
reject(err);
});
I also have a Java function to decrypt the text. Assume "rsaOaepCipherText" is a string text.
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey(), oaepParams);
return new String(cipher.doFinal(Base64.decodeBase64(rsaOaepCipherText)), "UTF-8");
However I keep getting decryption error on Java, and currently stuck at this portion, is there any error I have done on my encryption on Javascript?
Ok found it. I forgot to include btoa on the string before send to backend.
it should be
encryptDataWithPublicKey(text, publicKey).then((result) => {
var rdata = arrayBufferToString(result);
var rResult = window.btoa(rdata);
resolve(rResult);
});

subtleCrypto.encrypt() fails in MS Edge but work in Chome

I am trying to setup a client-side script that will encrypt files <2MB in size to be uploaded to a SharePoint document library. The encryption is necessary for files that contain personnally idenfiable information (PII) such as social security numbers and the like. The code below works perfectly in Google Chrome but fails the .encrypt(...) portion in MS Edge.
Edge will create all the needed results/buffers for the .encrypt(...) function; however, it returns a "Could not complete the operation due to error 8070000b." result. I've seen some other boards talk about Edge and needing to include a "hash" parameter in both the .generateKey(...) and .encrypt(...) scripts; however, that hasn't had any effect on my problem.
What am I missing?
file = document.getElementsByName('aFile')[0].files[0]
function encrypt() {
window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length:256,
},
true,
["encrypt", "decrypt"]
)
.then(function(key) {
pks = key
crypto.subtle.exportKey( "raw", key)
.then(function(buf) {
newKey = new Int8Array(buf)
exportedAsBase64 = _arrayBufferToBase64(buf);
})
.then(function() {
reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function() {
fileString = reader.result,
n=fileString.indexOf(";base64,") + 8;
X = _base64ToArrayBuffer(fileString.substring(n))
crypto.subtle.encrypt({name: 'AES-GCM', iv: newKey}, pks, X)
.then( function(buf) {
newFile = new Int8Array(buf)
encrypted = _arrayBufferToBase64(buf);
})
.catch(function (err) {
console.error(err);
});
}
})
})
}
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array( len );
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}

web.crypto encryption c# decryption

I'm trying to do:
encrypt text using web.crypto,
decrypt text using AesCryptoServiceProvider
I didn't get any exceptions in my code, but the decryption doesn't match the plain text that i've encrypted
the plaintext bytes after decyption are the same, but the encoding to string doesn't work
I'm using random iv, the key and the plaintext are constant.
function cryptoSys(plaintext,KeyString){
var iVec=window.crypto.getRandomValues(new Uint8Array(16));
var encryptSuccessFunc=(encrypt)=> { AfterEncrypt(BytearrayToString(iVec),arraybufferTostring(encrypt));}
var ImportKeySuccessFunc= (keyObj)=>{
window.crypto.subtle.encrypt(
{name:"AES-CBC", iv:iVec},
keyObj,
StringToByteArray(plaintext)
).then(encryptSuccessFunc);
window.crypto.subtle.importKey(
"raw",
StringToByteArray(KeyString),
{name:"AES-CBC", length:128},
true,
["encrypt","decrypt"]
).then(ImportKeySuccessFunc);
}
After that I'm sending the iVec, ciphertext using json
function AfterEncrypt(iVec,ciphertext)
{
var plaintext="String to Encrypt";
if(iVec==null)
return;
var send2server= {"ciphertext":ciphertext,
"iVec":iVec,
"plaintext":plaintext};
var objectDataString = JSON.stringify(send2server);
sendJSONtoserver(objectDataString,"getDelayedBid");
}
I'm using the following utility functions :
function StringToByteArray(strString) {
var byteArray = new Uint8Array(strString.length);
for (var i=0; i<strString.length; i++) {
byteArray[i] = strString.charCodeAt(i);
}
return byteArray;
}
function BytearrayToString(arrayBuffer) {
var strString = "";
for (var i=0; i<arrayBuffer.byteLength; i++) {
strString += String.fromCharCode(arrayBuffer[i]);
}
return strString;
}
function arraybufferTostring(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
server side accepts the keys, and suppose to decrypt:
public string DecryptCipher(Encoding u16, string cipherText, string key,string iVec)
{
byte[] ciphertextBytes = clearZeros(u16.GetBytes(cipherText));
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.BlockSize = 128;
aes.KeySize = 128; //minimun key length
aes.Key = clearZeros(u16.GetBytes(key));
aes.IV = clearZeros(u16.GetBytes(iVec));
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
ICryptoTransform cryptoTrans = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] plaintextBytes = cryptoTrans.TransformFinalBlock(ciphertextBytes, 0, ciphertextBytes.Length);
cryptoTrans.Dispose();
return Convert.ToBase64String(plaintextBytes);
}
I've got this utility function as well:
private byte [] clearZeros(byte [] bytearray)
{
byte[] ans = new byte[bytearray.Length / 2];
for (int i = 0; i < bytearray.Length / 2; i++)
ans[i] = bytearray[2 * i];
return ans;
}
I'm getting the paramters from anthor function:
public ActionResult getDelayedBid([FromBody] DelayedSubmission delaySub)
{
if (delaySub.ciphertext == null)
return Json("Failure", JsonRequestBehavior.AllowGet);
Encoding u16LE = Encoding.Unicode;
String decrypetedMessageLE = new Encryption().DecryptCipher(u16LE, delaySub.ciphertext, getKeyFromDB(), delaySub.iVec);
if (decrypetedMessageLE.Equals(delaySub.plaintext) )
{
return Json("Success", JsonRequestBehavior.AllowGet);
}
return Json("Failure", JsonRequestBehavior.AllowGet);
}
I've used wrong Ecoding,
instand of
Convert.ToBase64String(plaintextBytes);
I should've used
Encoding.ASCII.GetString(plaintextBytes);

Categories