Is function computeHmacSha256Signature(value, key) broken? - javascript

I am trying to sign the value eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6InRzIiwibm9uY2UiOiI4MTZkMWVmNi0zYjNlLTQ1MmEtOWM5Yi0xNDYyZWIzNWZlNzUiLCJpc3MiOiIwYzE3MjYwNDEwMjhkMTI5ZGI3YjU4NzUzYzU2OTYwYyIsImV4cCI6MTU4ODUyOTE3OCwibmJmIjoxNTg4NTI4ODE4LCJpYXQiOjE1ODg1Mjg4MTgsImp0aSI6IjE2ZGExZGZiLTkyYjQtNDI0ZS04ZTU5LWIyNzZmYmQ3MWVkYSIsInJlZ2lvbiI6Im55IiwibWV0aG9kIjoiR0VUIiwicGF0aCI6Ii9hdXRoZW50aWNhdGUvY29ubmVjdC9kZXZpY2UiLCJob3N0IjoiaHR0cHM6Ly9hcGkuYmxvb21iZXJnLmNvbSIsImNsaWVudF9pZCI6IjBjMTcyNjA0MTAyOGQxMjlkYjdiNTg3NTNjNTY5NjBjIn0 with key a1b2c3a4b5c6. The key is hexadecimal.
This should generate signature 8Wspda1l2Z3-hLwvMI_5Q8AQic59oclZAav7kWVtGHw. This signature is provided from my service provider as a sample.
I tried the following:
var signature = Utilities.computeHmacSha256Signature("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6InRzIiwibm9uY2UiOiI4MTZkMWVmNi0zYjNlLTQ1MmEtOWM5Yi0xNDYyZWIzNWZlNzUiLCJpc3MiOiIwYzE3MjYwNDEwMjhkMTI5ZGI3YjU4NzUzYzU2OTYwYyIsImV4cCI6MTU4ODUyOTE3OCwibmJmIjoxNTg4NTI4ODE4LCJpYXQiOjE1ODg1Mjg4MTgsImp0aSI6IjE2ZGExZGZiLTkyYjQtNDI0ZS04ZTU5LWIyNzZmYmQ3MWVkYSIsInJlZ2lvbiI6Im55IiwibWV0aG9kIjoiR0VUIiwicGF0aCI6Ii9hdXRoZW50aWNhdGUvY29ubmVjdC9kZXZpY2UiLCJob3N0IjoiaHR0cHM6Ly9hcGkuYmxvb21iZXJnLmNvbSIsImNsaWVudF9pZCI6IjBjMTcyNjA0MTAyOGQxMjlkYjdiNTg3NTNjNTY5NjBjIn0", "a1b2c3a4b5c6");
var encodedSignature = Utilities.base64EncodeWebSafe(signature).replace(/=+$/, '');
but I got different result: vnshuAi4GtWTZ3VxmPgLzJ--V18mm-r4cBUNA0FIvTs.
Since computeHmacSha256Signature(value, key) also allows input to be pair of byte[] (in addition to string pair as an input), I tried the following:
function toUTF8Array(str) {
var utf8 = [];
for (var i = 0; i < str.length; i++) {
var charcode = str.charCodeAt(i);
if (charcode < 0x80) utf8.push(charcode);
else if (charcode < 0x800) {
utf8.push(0xc0 | (charcode >> 6),
0x80 | (charcode & 0x3f));
}
else if (charcode < 0xd800 || charcode >= 0xe000) {
utf8.push(0xe0 | (charcode >> 12),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
}
// surrogate pair
else {
i++;
// UTF-16 encodes 0x10000-0x10FFFF by
// subtracting 0x10000 and splitting the
// 20 bits of 0x0-0xFFFFF into two halves
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
| (str.charCodeAt(i) & 0x3ff));
utf8.push(0xf0 | (charcode >>18),
0x80 | ((charcode>>12) & 0x3f),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
}
}
return utf8;
}
function resecret(s) {
var bin_secret = "";
var duo = ""
var new_secret_array = [];
var j = 0;
var len=s.length;
var element = 0;
for (var i = 0; i < len; i++) {
duo = s.charAt(i) + s.charAt(i+1);
element = parseInt(duo, 16);
// element = "0x"+duo;
bin_secret = bin_secret + element;
new_secret_array[j] = element;
j++;
i++;
}
return new_secret_array;
}
function test() {
var value="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6InRzIiwibm9uY2UiOiI4MTZkMWVmNi0zYjNlLTQ1MmEtOWM5Yi0xNDYyZWIzNWZlNzUiLCJpc3MiOiIwYzE3MjYwNDEwMjhkMTI5ZGI3YjU4NzUzYzU2OTYwYyIsImV4cCI6MTU4ODUyOTE3OCwibmJmIjoxNTg4NTI4ODE4LCJpYXQiOjE1ODg1Mjg4MTgsImp0aSI6IjE2ZGExZGZiLTkyYjQtNDI0ZS04ZTU5LWIyNzZmYmQ3MWVkYSIsInJlZ2lvbiI6Im55IiwibWV0aG9kIjoiR0VUIiwicGF0aCI6Ii9hdXRoZW50aWNhdGUvY29ubmVjdC9kZXZpY2UiLCJob3N0IjoiaHR0cHM6Ly9hcGkuYmxvb21iZXJnLmNvbSIsImNsaWVudF9pZCI6IjBjMTcyNjA0MTAyOGQxMjlkYjdiNTg3NTNjNTY5NjBjIn0";
var key = "a1b2c3a4b5c6";
var signature = Utilities.computeHmacSha256Signature(toUTF8Array(value),resecret(key));
}
This time, I get an error:
Cannot convert Array to (class)[].
What am I doing wrong here? Has anyone able to run computeHmacSha256Signature(value, key) using byte array?
How do I get the signature 8Wspda1l2Z3-hLwvMI_5Q8AQic59oclZAav7kWVtGHw?

Related

TripleDESCryptoServiceProvider for C# and Cryptojs gives different results

Why do I get different result in my encryption on TriplesDes using c# and JavaScript cryptojs? Please see my code below.
c#
public static string EncryptTxt()
{
SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider();
using (var tdes = new TripleDESCryptoServiceProvider())
{
var msg = 'jvofs:JCV XXXXX:201911141547:12345678';
var key = 'jjvofs';
var keyOffset = 10;
System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();
byte[] Results;
byte[] newKeyx = new byte[24];
byte[] keybyte = sha.ComputeHash(Encoding.UTF8.GetBytes(key));
Array.Copy(keybyte, keyOffset, newKeyx, 0, newKeyx.Length);
TDESAlgorithm.Key = newKeyx;
TDESAlgorithm.Mode = CipherMode.ECB;
TDESAlgorithm.Padding = PaddingMode.PKCS7;
byte[] DataToEncrypt = UTF8.GetBytes(msg);
try
{
ICryptoTransform Encryptor = TDESAlgorithm.CreateEncryptor();
Results = Encryptor.TransformFinalBlock(DataToEncrypt, 0, DataToEncrypt.Length);
}
finally
{
TDESAlgorithm.Clear();
}
var a = Convert.ToBase64String(DataToEncrypt);
var b = Convert.ToBase64String(newKeyx);
var c = Convert.ToBase64String(Results);
return Convert.ToBase64String(Results);
}
}
JavaScript using cryptojs
txtEncrypter = () => {
const msg = 'jvofs:JCV XXXXX:201911141547:12345678';
const key = 'jjvofs';
const keyOffset = 10;
const keybyte: any = this.wordArrayToByteArray(crypto.SHA512(key), 100);
// For message
const dataToEncrypt = crypto.enc.Utf8.parse(msg);
const dte = this.wordArrayToByteArray(dataToEncrypt, 100);
const dataToEncryptx = this._arrayBufferToBase64(dte);
const dataToEncryptxx = crypto.enc.Utf8.parse(dataToEncryptx);
// For key
let newKeyx = keybyte.slice(keyOffset, 34);
const newKeyxB4Splice = newKeyx;
const newKeyxB4Splicex = this._arrayBufferToBase64(newKeyx);
newKeyx = crypto.enc.Utf8.parse(newKeyx);
const options = {
mode: crypto.mode.ECB,
padding: crypto.pad.Pkcs7
};
const encrypted = crypto.TripleDES.encrypt(dataToEncrypt, newKeyx, options);
const base64String = encrypted.toString();
console.log(base64String);
}
wordArrayToByteArray(wordArray, length) {
if (wordArray.hasOwnProperty('sigBytes') && wordArray.hasOwnProperty('words')) {
length = wordArray.sigBytes;
wordArray = wordArray.words;
}
const result = [];
let bytes: any;
let i = 0;
while (length > 0) {
bytes = this.wordToByteArray(wordArray[i], Math.min(4, length));
length -= bytes.length;
result.push(bytes);
i++;
}
return [].concat.apply([], result);
}
wordToByteArray(word: any, length: any) {
const ba = [], xFF = 0xFF;
if (length > 0) {
// tslint:disable-next-line:no-bitwise
ba.push(word >>> 24);
}
if (length > 1) {
// tslint:disable-next-line:no-bitwise
ba.push((word >>> 16) & xFF);
}
if (length > 2) {
// tslint:disable-next-line:no-bitwise
ba.push((word >>> 8) & xFF);
}
if (length > 3) {
// tslint:disable-next-line:no-bitwise
ba.push(word & xFF);
}
return ba;
}
byteArrayToWordArray(ba) {
const wa = [];
let i = 0;
for (i = 0; i < ba.length; i++) {
// tslint:disable-next-line:no-bitwise
wa[(i / 4) | 0] |= ba[i] << (24 - 8 * i);
}
return crypto.lib.WordArray.create(wa);
}
toUTF8Array(str) {
const utf8 = [];
for (let i = 0; i < str.length; i++) {
let charcode = str.charCodeAt(i);
if (charcode < 0x80) { utf8.push(charcode); } else if (charcode < 0x800) {
// tslint:disable-next-line:no-bitwise
utf8.push(0xc0 | (charcode >> 6),
// tslint:disable-next-line: no-bitwise
0x80 | (charcode & 0x3f));
} else if (charcode < 0xd800 || charcode >= 0xe000) {
// tslint:disable-next-line: no-bitwise
utf8.push(0xe0 | (charcode >> 12),
// tslint:disable-next-line: no-bitwise
0x80 | ((charcode>>6) & 0x3f),
// tslint:disable-next-line: no-bitwise
0x80 | (charcode & 0x3f));
} else {
i++;
// UTF-16 encodes 0x10000-0x10FFFF by
// subtracting 0x10000 and splitting the
// 20 bits of 0x0-0xFFFFF into two halves
// tslint:disable-next-line:no-bitwise
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
// tslint:disable-next-line:no-bitwise
| (str.charCodeAt(i) & 0x3ff));
// tslint:disable-next-line:no-bitwise
utf8.push(0xf0 | (charcode >>18),
// tslint:disable-next-line:no-bitwise
0x80 | ((charcode>>12) & 0x3f),
// tslint:disable-next-line: no-bitwise
0x80 | ((charcode>>6) & 0x3f),
// tslint:disable-next-line: no-bitwise
0x80 | (charcode & 0x3f));
}
}
return utf8;
}
_arrayBufferToBase64( buffer ) {
let binary = '';
const bytes = new Uint8Array( buffer );
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode( bytes[ i ] );
}
return window.btoa( binary );
}
When parsing the data for message and key, both c# and JavaScript are the same:
Message:
[C#] anZvZnM6SkNWIFhYWFhYOjIwMTkxMTE0MTU0NzoxMjM0NTY3OA==
[cryptojs] anZvZnM6SkNWIFhYWFhYOjIwMTkxMTE0MTU0NzoxMjM0NTY3OA==
Key:
[C#] smbkkmDrCBdRev7S4hLaWE16Nvym+9gW
[cryptojs] smbkkmDrCBdRev7S4hLaWE16Nvym+9gW
But as soon as the "crypto.TripleDES.encrypt(....)" runs, I get different result for c# and javascript:
[C#] 1pjvBOB81iAOqsskZ+cM080yDU37XBoCwMhbYULwva/Nql5vbEMiPQ==
[cryptojs] ncCcCYNy3jVsB/95SaC2N1rH5Q+hX04WvScMvwmtkPkrnL7Ki1bmPg==
After the key has been determined from the byte-array of the hash as a sub-array (incl. index 10 to excl. index 34), this sub-array must be converted back into a WordArray with equivalent content, i.e. the line:
newKeyx = crypto.enc.Utf8.parse(newKeyx);
must be replaced by:
newKeyx = byteArrayToWordArray(newKeyx);
With this change, the NodeJS-code returns the same result as the C#-code.
The conversion WordArray <-> byte-array (and thus all functions required for these conversions) aren't really necessary, because as an alternative the key can also be derived using only the CryptoJS-encoders:
...
const key = 'jjvofs';
const keyOffset = 10;
const keyLength = 24;
const keyHash = crypto.enc.Hex.stringify(crypto.SHA512(key));
const newKey = crypto.enc.Hex.parse(keyHash.slice(keyOffset * 2, (keyOffset + keyLength) * 2));
...
By the way: The ECB-mode is insecure and instead of TripleDES the more performant AES should be used.

How can I check whether a provided parameter is a type of byte array in Javascript?

I have to validate whether a provided parameter is a type of byte array or not using Javascript. How can I achieve this? Please advice.
Because its not clear which type / instance of a Typed-Array you are trying to check, here an universal check. check if byteLength is present, then it should be a byteArray
function isByteArray(array) {
if (array && array.byteLength !== undefined) return true;
return false;
}
Modified version of Jonis Answer: (this will return an ArrayBuffer, which is designed to contain bytes)
function toUTF8Array(str) {
var utf8 = new ArrayBuffer(str.length);
for (var i=0; i < str.length; i++) {
var charcode = str.charCodeAt(i);
if (charcode < 0x80) {
utf8[i] = charcode;
continue;
}
if (charcode < 0x800) {
utf8[i] = (0xc0 | (charcode >> 6),
0x80 | (charcode & 0x3f));
continue;
}
if (charcode < 0xd800 || charcode >= 0xe000) {
utf8[i] = (0xe0 | (charcode >> 12),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
continue;
}
i++;
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
| (str.charCodeAt(i) & 0x3ff));
utf8[i - 1] = (0xf0 | (charcode >>18),
0x80 | ((charcode>>12) & 0x3f),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
}
return utf8;
}

How to convert UTF8 string to byte array?

The .charCodeAt function returns with the unicode code of the caracter. But I would like to get the byte array instead. I know, if the charcode is over 127, then the character is stored in two or more bytes.
var arr=[];
for(var i=0; i<str.length; i++) {
arr.push(str.charCodeAt(i))
}
The logic of encoding Unicode in UTF-8 is basically:
Up to 4 bytes per character can be used. The fewest number of bytes possible is used.
Characters up to U+007F are encoded with a single byte.
For multibyte sequences, the number of leading 1 bits in the first byte gives the number of bytes for the character. The rest of the bits of the first byte can be used to encode bits of the character.
The continuation bytes begin with 10, and the other 6 bits encode bits of the character.
Here's a function I wrote a while back for encoding a JavaScript UTF-16 string in UTF-8:
function toUTF8Array(str) {
var utf8 = [];
for (var i=0; i < str.length; i++) {
var charcode = str.charCodeAt(i);
if (charcode < 0x80) utf8.push(charcode);
else if (charcode < 0x800) {
utf8.push(0xc0 | (charcode >> 6),
0x80 | (charcode & 0x3f));
}
else if (charcode < 0xd800 || charcode >= 0xe000) {
utf8.push(0xe0 | (charcode >> 12),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
}
// surrogate pair
else {
i++;
// UTF-16 encodes 0x10000-0x10FFFF by
// subtracting 0x10000 and splitting the
// 20 bits of 0x0-0xFFFFF into two halves
charcode = 0x10000 + (((charcode & 0x3ff)<<10)
| (str.charCodeAt(i) & 0x3ff));
utf8.push(0xf0 | (charcode >>18),
0x80 | ((charcode>>12) & 0x3f),
0x80 | ((charcode>>6) & 0x3f),
0x80 | (charcode & 0x3f));
}
}
return utf8;
}
JavaScript Strings are stored in UTF-16. To get UTF-8, you'll have to convert the String yourself.
One way is to mix encodeURIComponent(), which will output UTF-8 bytes URL-encoded, with unescape, as mentioned on ecmanaut.
var utf8 = unescape(encodeURIComponent(str));
var arr = [];
for (var i = 0; i < utf8.length; i++) {
arr.push(utf8.charCodeAt(i));
}
The Encoding API lets you both encode and decode UTF-8 easily (using typed arrays):
var encoded = new TextEncoder().encode("Γεια σου κόσμε");
var decoded = new TextDecoder("utf-8").decode(encoded);
console.log(encoded, decoded);
Browser support isn't too bad, and there's a polyfill that should work in IE11 and older versions of Edge.
While TextEncoder can only encode to UTF-8, TextDecoder supports other encodings. I used it to decode Japanese text (Shift-JIS) in this way:
// Shift-JIS encoded text; must be a byte array due to values 129 and 130.
var arr = [130, 108, 130, 102, 130, 80, 129, 64, 130, 102, 130, 96, 130, 108, 130, 100,
129, 64, 130, 99, 130, 96, 130, 115, 130, 96, 129, 124, 130, 79, 130, 80];
// Convert to byte array
var data = new Uint8Array(arr);
// Decode with TextDecoder
var decoded = new TextDecoder("shift-jis").decode(data.buffer);
console.log(decoded);
The Google Closure library has functions to convert to/from UTF-8 and byte arrays. If you don't want to use the whole library, you can copy the functions from here. For completeness, the code to convert to a string to a UTF-8 byte array is:
goog.crypt.stringToUtf8ByteArray = function(str) {
// TODO(user): Use native implementations if/when available
var out = [], p = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 128) {
out[p++] = c;
} else if (c < 2048) {
out[p++] = (c >> 6) | 192;
out[p++] = (c & 63) | 128;
} else if (
((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
// Surrogate Pair
c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
out[p++] = (c >> 18) | 240;
out[p++] = ((c >> 12) & 63) | 128;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
} else {
out[p++] = (c >> 12) | 224;
out[p++] = ((c >> 6) & 63) | 128;
out[p++] = (c & 63) | 128;
}
}
return out;
};
Assuming the question is about a DOMString as input and the goal is to get an Array, that when interpreted as string (e.g. written to a file on disk), would be UTF-8 encoded:
Now that nearly all modern browsers support Typed Arrays, it'd be ashamed if this approach is not listed:
According to the W3C, software supporting the File API should accept DOMStrings in their Blob constructor (see also: String encoding when constructing a Blob)
Blobs can be converted to an ArrayBuffer using the .readAsArrayBuffer() function of a File Reader
Using a DataView or constructing a Typed Array with the buffer read by the File Reader, one can access every single byte of the ArrayBuffer
Example:
// Create a Blob with an Euro-char (U+20AC)
var b = new Blob(['€']);
var fr = new FileReader();
fr.onload = function() {
ua = new Uint8Array(fr.result);
// This will log "3|226|130|172"
// E2 82 AC
// In UTF-16, it would be only 2 bytes long
console.log(
fr.result.byteLength + '|' +
ua[0] + '|' +
ua[1] + '|' +
ua[2] + ''
);
};
fr.readAsArrayBuffer(b);
Play with that on JSFiddle. I haven't benchmarked this yet but I can imagine this being efficient for large DOMStrings as input.
You can save a string raw as is by using FileReader.
Save the string in a blob and call readAsArrayBuffer(). Then the onload-event results an arraybuffer, which can converted in a Uint8Array.
Unfortunately this call is asynchronous.
This little function will help you:
function stringToBytes(str)
{
let reader = new FileReader();
let done = () => {};
reader.onload = event =>
{
done(new Uint8Array(event.target.result), str);
};
reader.readAsArrayBuffer(new Blob([str], { type: "application/octet-stream" }));
return { done: callback => { done = callback; } };
}
Call it like this:
stringToBytes("\u{1f4a9}").done(bytes =>
{
console.log(bytes);
});
output: [240, 159, 146, 169]
explanation:
JavaScript use UTF-16 and surrogate-pairs to store unicode characters in memory. To save unicode character in raw binary byte streams an encoding is necessary.
Usually and in the most case, UTF-8 is used for this. If you not use an enconding you can't save unicode character, just ASCII up to 0x7f.
FileReader.readAsArrayBuffer() uses UTF-8.
As there is no pure byte type in JavaScript we can represent a byte array as an array of numbers, where each number represents a byte and thus will have an integer value between 0 and 255 inclusive.
Here is a simple function that does convert a JavaScript string into an Array of numbers that contain the UTF-8 encoding of the string:
function toUtf8(str) {
var value = [];
var destIndex = 0;
for (var index = 0; index < str.length; index++) {
var code = str.charCodeAt(index);
if (code <= 0x7F) {
value[destIndex++] = code;
} else if (code <= 0x7FF) {
value[destIndex++] = ((code >> 6 ) & 0x1F) | 0xC0;
value[destIndex++] = ((code >> 0 ) & 0x3F) | 0x80;
} else if (code <= 0xFFFF) {
value[destIndex++] = ((code >> 12) & 0x0F) | 0xE0;
value[destIndex++] = ((code >> 6 ) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 0 ) & 0x3F) | 0x80;
} else if (code <= 0x1FFFFF) {
value[destIndex++] = ((code >> 18) & 0x07) | 0xF0;
value[destIndex++] = ((code >> 12) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 6 ) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 0 ) & 0x3F) | 0x80;
} else if (code <= 0x03FFFFFF) {
value[destIndex++] = ((code >> 24) & 0x03) | 0xF0;
value[destIndex++] = ((code >> 18) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 12) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 6 ) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 0 ) & 0x3F) | 0x80;
} else if (code <= 0x7FFFFFFF) {
value[destIndex++] = ((code >> 30) & 0x01) | 0xFC;
value[destIndex++] = ((code >> 24) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 18) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 12) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 6 ) & 0x3F) | 0x80;
value[destIndex++] = ((code >> 0 ) & 0x3F) | 0x80;
} else {
throw new Error("Unsupported Unicode character \""
+ str.charAt(index) + "\" with code " + code + " (binary: "
+ toBinary(code) + ") at index " + index
+ ". Cannot represent it as UTF-8 byte sequence.");
}
}
return value;
}
function toBinary(byteValue) {
if (byteValue < 0) {
byteValue = byteValue & 0x00FF;
}
var str = byteValue.toString(2);
var len = str.length;
var prefix = "";
for (var i = len; i < 8; i++) {
prefix += "0";
}
return prefix + str;
}
I was using Joni's solution and it worked fine, but this one is much shorter.
This was inspired by the atobUTF16() function of Solution #3 of Mozilla's Base64 Unicode discussion
function convertStringToUTF8ByteArray(str) {
let binaryArray = new Uint8Array(str.length)
Array.prototype.forEach.call(binaryArray, function (el, idx, arr) { arr[idx] = str.charCodeAt(idx) })
return binaryArray
}
function convertByte()
{
var c=document.getElementById("str").value;
var arr = [];
var i=0;
for(var ind=0;ind<c.length;ind++)
{
arr[ind]=c.charCodeAt(i);
i++;
}
document.getElementById("result").innerHTML="The converted value is "+arr.join("");
}

Javascript processing Cyrillic input

When i get a json feed from a Cyrillic site, the data is in a \ufffd format instead of Cyrillic chars.
(example feed: http://jsonduit.com/v1/f/l/7sg?cb=getJsonP_1284131679846_0)
So when i set the source html to the input, i get weird boxes instead of characters.
I tried to unescape the input but that wont work too.
How do i revert the feed back to Cyrillic?
(btw, the source page encoding is set to UTF-8)
decodeURIComponent("stringToDecodeToCyrillic")
Example:
decodeURIComponent("%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B5%D0%B9") === "Алексей"
Fastest way to encode cyrillic letters for url
It seems you receive UTF8 string. Use the following class to decode:
UTF8 = {
encode: function(s){
for(var c, i = -1, l = (s = s.split("")).length, o = String.fromCharCode; ++i < l;
s[i] = (c = s[i].charCodeAt(0)) >= 127 ? o(0xc0 | (c >>> 6)) + o(0x80 | (c & 0x3f)) : s[i]
);
return s.join("");
},
decode: function(s){
for(var a, b, i = -1, l = (s = s.split("")).length, o = String.fromCharCode, c = "charCodeAt"; ++i < l;
((a = s[i][c](0)) & 0x80) &&
(s[i] = (a & 0xfc) == 0xc0 && ((b = s[i + 1][c](0)) & 0xc0) == 0x80 ?
o(((a & 0x03) << 6) + (b & 0x3f)) : o(128), s[++i] = "")
);
return s.join("");
}
};
Usage:
var newString = UTF8.decode( yourString );

Javascript Base64 encoding UTF8 string fails in webkit/safari

I'm trying to base64 encode a utf8 string containing Thai characters. I'm using the browser's built in btoa function. It works for ascii text, however Thai is causing it to throw a INVALID_CHARACTER_ERR: DOM Exception 5 exception.
Here's a sample that fails (the character that looks like an "n" is Thai)
btoa('aก')
What do I need to do to base64 encode non-ascii strings?
var Base64 = {
encode: function(s) {
return btoa(unescape(encodeURIComponent(s)));
},
decode: function(s) {
return decodeURIComponent(escape(atob(s)));
}
};
Unfortunately btoa/atob aren't specified in any standard, but the implementations in firefox and webkit both fail on multibyte characters so even if they were now specified those builtin functions would not be able to support multibyte characters (as the input and output strings would necessarily change).
It would seem your only option would be to roll your own base64 encode+decode routines
check this workaround
http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
I know this is old, but I was recently looking for a UTF8-to-Base64 encoder as well. I found a handy little script at http://www.webtoolkit.info/javascript-base64.html, and a performance improved version at http://jsbase64.codeplex.com/.
Here is the script:
var B64 = {
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
lookup: null,
ie: /MSIE /.test(navigator.userAgent),
ieo: /MSIE [67]/.test(navigator.userAgent),
encode: function (s) {
var buffer = B64.toUtf8(s),
position = -1,
len = buffer.length,
nan0, nan1, nan2, enc = [, , , ];
if (B64.ie) {
var result = [];
while (++position < len) {
nan0 = buffer[position];
nan1 = buffer[++position];
enc[0] = nan0 >> 2;
enc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);
if (isNaN(nan1))
enc[2] = enc[3] = 64;
else {
nan2 = buffer[++position];
enc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);
enc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;
}
result.push(B64.alphabet.charAt(enc[0]), B64.alphabet.charAt(enc[1]), B64.alphabet.charAt(enc[2]), B64.alphabet.charAt(enc[3]));
}
return result.join('');
} else {
var result = '';
while (++position < len) {
nan0 = buffer[position];
nan1 = buffer[++position];
enc[0] = nan0 >> 2;
enc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);
if (isNaN(nan1))
enc[2] = enc[3] = 64;
else {
nan2 = buffer[++position];
enc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);
enc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;
}
result += B64.alphabet[enc[0]] + B64.alphabet[enc[1]] + B64.alphabet[enc[2]] + B64.alphabet[enc[3]];
}
return result;
}
},
decode: function (s) {
if (s.length % 4)
throw new Error("InvalidCharacterError: 'B64.decode' failed: The string to be decoded is not correctly encoded.");
var buffer = B64.fromUtf8(s),
position = 0,
len = buffer.length;
if (B64.ieo) {
var result = [];
while (position < len) {
if (buffer[position] < 128)
result.push(String.fromCharCode(buffer[position++]));
else if (buffer[position] > 191 && buffer[position] < 224)
result.push(String.fromCharCode(((buffer[position++] & 31) << 6) | (buffer[position++] & 63)));
else
result.push(String.fromCharCode(((buffer[position++] & 15) << 12) | ((buffer[position++] & 63) << 6) | (buffer[position++] & 63)));
}
return result.join('');
} else {
var result = '';
while (position < len) {
if (buffer[position] < 128)
result += String.fromCharCode(buffer[position++]);
else if (buffer[position] > 191 && buffer[position] < 224)
result += String.fromCharCode(((buffer[position++] & 31) << 6) | (buffer[position++] & 63));
else
result += String.fromCharCode(((buffer[position++] & 15) << 12) | ((buffer[position++] & 63) << 6) | (buffer[position++] & 63));
}
return result;
}
},
toUtf8: function (s) {
var position = -1,
len = s.length,
chr, buffer = [];
if (/^[\x00-\x7f]*$/.test(s)) while (++position < len)
buffer.push(s.charCodeAt(position));
else while (++position < len) {
chr = s.charCodeAt(position);
if (chr < 128)
buffer.push(chr);
else if (chr < 2048)
buffer.push((chr >> 6) | 192, (chr & 63) | 128);
else
buffer.push((chr >> 12) | 224, ((chr >> 6) & 63) | 128, (chr & 63) | 128);
}
return buffer;
},
fromUtf8: function (s) {
var position = -1,
len, buffer = [],
enc = [, , , ];
if (!B64.lookup) {
len = B64.alphabet.length;
B64.lookup = {};
while (++position < len)
B64.lookup[B64.alphabet.charAt(position)] = position;
position = -1;
}
len = s.length;
while (++position < len) {
enc[0] = B64.lookup[s.charAt(position)];
enc[1] = B64.lookup[s.charAt(++position)];
buffer.push((enc[0] << 2) | (enc[1] >> 4));
enc[2] = B64.lookup[s.charAt(++position)];
if (enc[2] == 64)
break;
buffer.push(((enc[1] & 15) << 4) | (enc[2] >> 2));
enc[3] = B64.lookup[s.charAt(++position)];
if (enc[3] == 64)
break;
buffer.push(((enc[2] & 3) << 6) | enc[3]);
}
return buffer;
}
};
Disclaimer: I haven't tested this with Thai characters specifically, but assume it will work.
Sav

Categories