How to get X509Certificate thumbprint in Javascript? - javascript

I need to write a function in javascript (forge) that get a thumbnail of a pfx certificate. I created a test certificate(mypfx.pfx). By using c# X509Certificate2 library, I can see thumbprint of input certificate in X509Certificate2 object by passing file bytes array and password. Here is c# code snippet:
X509Certificate2 certificate = new X509Certificate2(byteArrayCertData, password);
var thumbprint = certificate.Thumbprint;
//thumbprint is a hex encoding SHA-1 hash
But when I am trying to do the same thing in javascript (using forge). I can't get a correct thumbprint. Here is my Javascript code:
var certi = fs.readFileSync('c:/mypfx.pfx');
let p12b64 = Buffer(certi).toString('base64');
let p12Der = forge.util.decode64(p12b64);
var outAsn1 = forge.asn1.fromDer(p12Der);
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(outAsn1, false, "1234");
var fp = null;
for (var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
var safeContents = pkcs12.safeContents[sci];
for (var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
var safeBag = safeContents.safeBags[sbi];
if (safeBag.cert != undefined && safeBag.cert.publicKey != undefined) {
fp = forge.pki.getPublicKeyFingerprint(safeBag.cert.publicKey, {type: 'RSAPublicKey'});
//Is this fingerprint value I am looking for??
break;
}
}
}
The result is different value compare to c# thumbprint which seems to be wrong. I tried different functions in pkcs12.js file. None of them works. It's acceptable using other JS library as long as correct thumbprint result is produced. Please help and correct any mistakes I made. Thanks!

You are comparing different data. The certificate thumbprint is not the same that the public key fingerprint.
The certificate thumbprint is a hash calculated on the entire certificate. Seems forge does not have a method, but you can calculate yourself
//SHA-1 on certificate binary data
var md = forge.md.sha1.create();
md.start();
md.update(certDer);
var digest = md.digest();
//print as HEX
var hex = digest.toHex();
console.log(hex);
To convert the forge certificate to DER (binary) you can use this
var certAsn1 = forge.pki.certificateToAsn1(cert);
var certDer = forge.asn1.toDer(certAsn1).getBytes();

Related

Generate encoded docket number from two integers and decode it

I am trying to generate encoded docket number from storeId and transactionId. Encoded docket number has to be unique, length should be <=9 and easy to read/copy for users as well.
The maximum length of storeId is 3 and maximum length of transactionId is 5.
How can I improve my code so that my docket number will be unbreakable?
Here is my code:
let myTransKey = 19651;
let myStoreKey = 186;
function generateShortCode(storeId, transactionId) {
//reverse the ids and then add the respective key
var SID = storeId.toString().split("").reverse().join("");
SID = parseInt(SID) + myStoreKey;
var TID = transactionId.toString().split("").reverse().join("");
TID = parseInt(TID) + myTransKey;
var docketNum = `${SID}-${TID}`;
return docketNum;
}
function decodeShortCode(shortCode) {
shortCode = shortCode.split("-");
var storeID = shortCode[0];
var transactionID = shortCode[1];
//subtract the same key and then reverse the ids again
storeID = parseInt(storeID.toString()) - myStoreKey;
storeID = storeID.toString().split("").reverse().join("");
transactionID = parseInt(transactionID.toString()) - myTransKey;
transactionID = transactionID.toString().split("").reverse().join("");
return {
storeId: parseInt(storeID), // store id goes here,
shopDate: new Date(), // the date the customer shopped,
transactionId: parseInt(transactionID) // transaction id goes here
};
}
Is there any better way to do this? I need to encode docket number in a way which will be really hard to decode by any third person.
Every encrypted message can be broken if an attacker tries every possible decryption key (this is called a brute-force attack). With modern computers, this is really easy to do. The way that you are encoding data is very easy to break (within seconds). However, there are encryption methods that take very long to break (like millions of years long).
One of the more popular encryption algorithms is AES. Because it is so popular, there are also many easy-to-use libraries for JavaScript. Here's an example with CryptoJS:
const KEY = "a super secret password";
let myTransKey = 19651;
let myStoreKey = 186;
function generateShortCode(storeId, transactionId) {
const docketNum = `${storeId}-${transactionId}`;
return CryptoJS.AES.encrypt(docketNum, KEY).toString().replace("=", "");
}
function decodeShortCode(shortCode) {
const docketNum = CryptoJS.AES.decrypt(shortCode, KEY).toString(CryptoJS.enc.Utf8);
const parts = docketNum.split("-");
return {
storeId: parseInt(parts[0]), // store id goes here,
shopDate: new Date(), // the date the customer shopped,
transactionId: parseInt(parts[1]) // transaction id goes here
};
}
const s1 = generateShortCode(myStoreKey, myTransKey);
console.log("Short Code: " + s1);
console.log("Decrypted Short Code:", decodeShortCode(s1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js" integrity="sha256-/H4YS+7aYb9kJ5OKhFYPUjSJdrtV6AeyJOtTkw6X72o=" crossorigin="anonymous"></script>
This shortcode is longer than 9 characters, but it very secure and nearly unbreakable. This is really just the tradeoff. If you reduce the length of the shortcode, then you won't be able to have a secure shortcode. Users can still easily copy and paste the code though. If you absolutely need a shorter cipher, then try looking at Skip32.
Be sure to change KEY to a secret key that isn't shared with anyone. Also, be sure not to run this code client-side. If the encryption key is sent to the client, then they could look at the JavaScript code and then be able to decrypt any message.
well this work for me with visual compser in Wordpress
/[[^[]vc[^]]]/ig

TripleDES Java Encryprion to Javascript Decryption

I am using Java to encrypt a text payload with Triple DES. First I create an ephemeral key that I will use for encrypting the payload:
private byte[] createEphemeralKey() throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
return keygen.generateKey().getEncoded();
}
Then I encrypt my payload with said key:
private String encryptTripleDES(byte[] ephemeralKey, String payload) throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(ephemeralKey, "DESede"));
byte[] plainTextBytes = payload.getBytes();
byte[] cipherText = cipher.doFinal(plainTextBytes);
return Base64.getEncoder().encodeToString(cipherText);
}
Also need a padding function to ensure the data length is divisable by 8:
private String adjustPadding(String input, int blockSize) {
int len = input.length() % blockSize;
int paddingLength = (len == 0) ? 0 : (blockSize - len);
while (paddingLength > 0) {
input += "F";
paddingLength--;
}
return input;
}
And here is my process end to end:
String data = "Marnus"
byte[] = ephemeralKey = createEphemeralKey();
String adjustedData = adjustPadding (data,8);
String encryptedPayload = encryptTripleDES(ephemeralKey, adjustedData);
String encodedKey = Base64.getEncoder().encodeToString(ephemeralKey)
So I take the 2 variables encryptedPayload and encodedKey, that are both Base64 encoded string, and send it off via HTTP to node express app.
In the Javascript side of things, I use node-forge - Here is the part of my express app that does the decryption:
let nodeBuffer = Buffer.from(data, 'base64')
let input = forge.util.createBuffer(nodeBuffer.toString('binary'))
// 3DES key and IV sizes
let keySize = 24;
let ivSize = 8;
let derivedBytes = forge.pbe.opensslDeriveBytes(ephemeralKey, null, keySize + ivSize);
let buffer = forge.util.createBuffer(derivedBytes);
let key = buffer.getBytes(keySize)
let iv = buffer.getBytes(ivSize)
let decipher = forge.cipher.createDecipher('3DES-ECB', key)
decipher.start({iv: iv})
decipher.update(input)
console.log('decipher result', decipher.finish())
let decryptedResult = decipher.output.data;
Here is an Triples DES example in the node-forge docs:
A few notes:
I create a node-forge buffer from a regular buffer since I don't have a input file like the examples gives. Here is how the docs states one should create one buffer from the other:
*I use base64 as that is what I used in the java side to encode the data that was sent.
Then, I dont have a salt so I left the 2'nd param null in opensslDeriveBytes as specified in the docs I should do.
Thirdly, I am also not sure if my keysize of 24 is correct?
My results
So doing an end to end test yields the following:
In my Java app, the test data was "Marnus", the encryptedPayload was ez+RweSAd+4= and the encodedKey was vCD9mBnWHPEBiQ0BGv7gc6GUCOoBgLCu.
Then in my javascript code data was obviously ez+RweSAd+4=(encryptedPayload) and the ephemeralKey was vCD9mBnWHPEBiQ0BGv7gc6GUCOoBgLCu(encodedKey).
After the decryption ran, the value of decryptedResult was ©ýÕ?µ{', which is obviously just garbage since it was not encoded yet, but I cant figure out which encoding to use?
I tried using forge.util.encode64(decipher.output.data), but that just gave me qf3VP7UYeyc=, which is not right.
For what it's worth, here is the type that decipher.output
With a lot more tweaking and testing different options, I got it working - and the good news is I managed to get it all working with the built in crypto library in nodejs (v12.18.4).
First things first, the JAVA side just needs a change to the key size (from 168 to 112), the rest remains the same - see below example as one single method (should be split up in final implementation of course for testability and usability):
//Some data:
String payload = "{\"data\":\"somedata\"}";
// Create Key
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(112);
byte[] ephemeralKey = keygen.generateKey().getEncoded();
// Adjust the data, see adjustPadding method in the question for details.
String data = adjustPadding (payload,8);
// Wil now be "{"data":"somedata"}FFFFF", can just chop off extra in JS if need be. When sending JSON one knows the end of the object will always be "}"
// Do Encrypt
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(ephemeralKey, "DESede"));
byte[] plainTextBytes = data.getBytes();
byte[] cipherText = cipher.doFinal(plainTextBytes);
String encryptedPayload = Base64.getEncoder().encodeToString(cipherText);
//Lastly, Base64 the key so you can transport it too
String encodedKey = Base64.getEncoder().encodeToString(ephemeralKey)
on the Javascript side of things we keep it simple:
// I'm using TS, so change the import if you do plain JS
import crypto = require('crypto')
//need bytes from the base64 payload
let buff = Buffer.from(ephemeralKey, 'base64')
const decipher = crypto.createDecipheriv('des-ede3', buff, null)
decipher.setAutoPadding(false)
let decrypted = decipher.update(data, 'base64', 'utf8')
decrypted += decipher.final('utf8')
console.log(decrypted)
//{"data":"somedata"}FFFFF"

C# SHA256 ComputeHash result different with CryptoJS SHA256 function

I have a C# function as below:
string stringvalue = "530500480530490480530480480520570480520510500490";
var encodedvalue= Encoding.Unicode.GetBytes(stringvalue);
using (HashAlgorithm ssp = System.Security.Cryptography.HashAlgorithm.Create("SHA256"))
{
var digest = ssp.ComputeHash(encodedvalue);
return BitConverter.ToString(digest);
}
I need to create a javascript function that match the code above so that the end result for both C# and JS is the same.
Currently in my JS code, I'm using this:
var hash = CryptoJS.SHA256("530500480530490480530480480520570480520510500490");
var hexhash = hash.toString(CryptoJS.enc.hex);
This is the result of my hexhash:
d956678c8f12c65299daf35019a9a1eb3e6eb9855fd850aeb5aafe46057d179e
But in my C# code, this line of var digest = ssp.ComputeHash(bPass); return the following array:
I don't know much about encoding. Please tell me what type of result is being populated in the c# code above? If I'm not mistaken, the ComputeHash is returning bytes but I need lots of reading to confirm that which is another long hour of studying
I tried many different ways of converting the JS Sha256 code but no luck. I'm stuck at this particular line for almost a day.
Please help. Thanks
EDIT:
Sorry for the code error. I had updated the C# code. ComputeHash accept an array
In my example I am using System.Security.Cryptography.SHA256Managed to get SHA256 in C#.
The method SHA256Managed.ComputeHash takes a byte array as a parameter and return another byte array. Now we need to convert back your byte array to a string.
The following code return the same result a Javascript SHA-256.
byte[] bytes = Encoding.UTF8.GetBytes("530500480530490480530480480520570480520510500490");
SHA256Managed hashstring = new SHA256Managed();
byte[] hash = hashstring.ComputeHash(bytes);
string hashString = string.Empty;
foreach (byte x in hash)
{
hashString += String.Format("{0:x2}", x);
}
return(hashString);
Just to explain : String.Format("{0:x2}", x)
X means Hexadecimal format.
2 means 2 characters.
I finally found the answer after uncountable hours of trial and error.
The C# code var digest = ssp.ComputeHash(encodedvalue) is returning byte array from the result of var encodedvalue= Encoding.Unicode.GetBytes(stringvalue); as Jean replied. In order to create the function in Javascript, I need to ensure that the encodedvalue is producing the correct encoding format and size just like the one in C#.
Using only CryptoJS, I manage to get the matching result from below
function GetHexFromString() {
var stringVal = '8563A578-7402-4567-A6CE-4DE4E0825B021234';
// Convert the string to UTF 16 little-endian
// Result: 560530540510650530550560450550520480500450520530540550450650540670690450520680690520690480560500530660480500490500510520
var utf16le = CryptoJS.enc.Utf16LE.parse(stringVal);
// Convert to Sha256 format and get the word array
var utf16Sha256 = CryptoJS.SHA256(utf16le);
// Convert the Sha256 word array to Uint8Array to get the 32 byte array just to see the result to ensure it match with the C# function
// Result: 94,203,69,29,35,202,209,149,121,144,44,6,98,250,141,161,102,7,238,35,228,117,111,236,118,115,51,113,134,72,52,69
var utf16sha256Array = convertWordArrayToUint8Array(utf16Sha256);
// Convert the Sha256 to hex (if i'm not mistaken, it's base 16) format
var hexSha256 = utf16Sha256.toString(CryptoJS.enc.hex);
// Insert a dash in between 2 characters in the string
hexSha256 = hexSha256.replace(/(\S{2})/g, "$1-");
// Remove the last dash in the string
hexSha256 = hexSha256.replace(/-$/, "");
// Final Result: 5E-CB-45-1D-23-CA-D1-95-79-90-2C-06-62-FA-8D-A1-66-07-EE-23-E4-75-6F-EC-76-73-33-71-86-48-34-45
return hexSha256.toUpperCase();
}
function convertWordArrayToUint8Array(wordArray) {
var len = wordArray.words.length,
u8_array = new Uint8Array(len << 2),
offset = 0, word, i
;
for (i = 0; i < len; i++) {
var word = wordArray.words[i];
u8_array[offset++] = word >> 24;
u8_array[offset++] = (word >> 16) & 0xff;
u8_array[offset++] = (word >> 8) & 0xff;
u8_array[offset++] = word & 0xff;
}
return u8_array;
}
Hope it help whoever that need such method
An alternative to Koo SengSeng's answer (if you don't want to use CryptoJS library).
SHA256 function is from here, the arrToUintArr function is from Koo SengSeng's answer.
var SHA256=function a(b){function c(a,b){return a>>>b|a<<32-b}for(var d,e,f=Math.pow,g=f(2,32),h="length",i="",j=[],k=8*b[h],l=a.h=a.h||[],m=a.k=a.k||[],n=m[h],o={},p=2;64>n;p++)if(!o[p]){for(d=0;313>d;d+=p)o[d]=p;l[n]=f(p,.5)*g|0,m[n++]=f(p,1/3)*g|0}for(b+="\x80";b[h]%64-56;)b+="\x00";for(d=0;d<b[h];d++){if(e=b.charCodeAt(d),e>>8)return;j[d>>2]|=e<<(3-d)%4*8}for(j[j[h]]=k/g|0,j[j[h]]=k,e=0;e<j[h];){var q=j.slice(e,e+=16),r=l;for(l=l.slice(0,8),d=0;64>d;d++){var s=q[d-15],t=q[d-2],u=l[0],v=l[4],w=l[7]+(c(v,6)^c(v,11)^c(v,25))+(v&l[5]^~v&l[6])+m[d]+(q[d]=16>d?q[d]:q[d-16]+(c(s,7)^c(s,18)^s>>>3)+q[d-7]+(c(t,17)^c(t,19)^t>>>10)|0),x=(c(u,2)^c(u,13)^c(u,22))+(u&l[1]^u&l[2]^l[1]&l[2]);l=[w+x|0].concat(l),l[4]=l[4]+w|0}for(d=0;8>d;d++)l[d]=l[d]+r[d]|0}for(d=0;8>d;d++)for(e=3;e+1;e--){var y=l[d]>>8*e&255;i+=(16>y?0:"")+y.toString(16)}return i};
var arrToUintArr=function(a){for(var l=a.length,b=new Uint8Array(l<<2),o=0,w,i=0;i<l;i++) w=a[i],b[o++]=w>>24,b[o++]=(w>>16)&0xff,b[o++]=(w>>8)&0xff,b[o++]=w&0xff;return b;}
var computeHash=function(k){for(var a=[],s=SHA256(k),i=0;i<8;i++) a.push(parseInt(s.substr(i*8,8),16));return arrToUintArr(a);}
computeHash(k) will return an array of numbers representing bytes.
This is equal to below code in C#:
new System.Security.Cryptography.SHA256CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(k));
Try
var digest = ssp.ComputeHash(Encoding.UTF8.GetBytes(stringvalue))
return BitConverter.ToString(digest)
.Replace("-", string.Empty)
.ToLowerInvariant();
That js library is converting the string to UTF8 before calculating its hash.
typescript code:
private computeHash(text: string): string {
return CryptoJS.SHA256(text).toString();
}
c# equivalent:
private string ComputeHash(string text)
{
using (var sha256 = SHA256.Create())
{
var bytes = Encoding.UTF8.GetBytes(text);
var hash = sha256.ComputeHash(bytes);
return hash.Aggregate(string.Empty, (current, x) => current + $"{x:x2}");
}
}
after two days of research it works perfectly! Two different codes give the same result.
js
const sha1 = require('sha1');
const getHash = str =>{
const hashingBytes = Buffer.from(sha1(str), "hex");
const base64Value = Buffer.from(hashingBytes).toString('base64');
return base64Value;
}
c#
System.Security.Cryptography.SHA1 sha = new System.Security.Cryptography.SHA1CryptoServiceProvider();
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(str);
byte[] hashingbytes = sha.ComputeHash(bytes);
var hash = Convert.ToBase64String(hashingbytes);

Node.js/javascript encrypt AES-128 like mcrypt_ecb in PHP

I have a PHP code that encrypt in AES-128 an ip address:
$ip = "MY_IP";
$secret = "MY_KEY";
$ip = #mcrypt_ecb(MCRYPT_RIJNDAEL_128, $secret, $ip, MCRYPT_ENCRYPT);
$encrypted = bin2hex($ip); // encrypted: 2854edb405cb7230ba1f4b87acddba8a
What I need to do is to have the same piece of code but using javascript/node.js. I've searched in the crypto node.js native module but I wasn't able to reproduce the same result:
var crypto = require('crypto');
var ip = "MY_IP";
var secret = "MY_KEY";
var cipher = crypto.createCipher("AES-128-ECB", secret);
var encrypted = cipher.update(ip, 'utf8', 'hex');
encrypted += cipher.final('hex'); // encrypted: e84c06888696edda0139e98fc2c0a8cc
Does someone have an idea ?
I've posted too quickly, found the solution:
$> npm install mcrypt
And then the code:
var MCrypt = require('mcrypt').MCrypt;
var ip = "MY_IP";
var secret = "MY_KEY"
var desEcb = new MCrypt('rijndael-128', 'ecb');
desEcb.open(secret);
var cipherText = desEcb.encrypt(ip); // cipherText: 2854edb405cb7230ba1f4b87acddba8a
MCrypt github for more encryption tool: https://github.com/tugrul/node-mcrypt
The problem here is that there are some things that PHP's mcrypt extension (and node's createCipher()) does behind the scenes that you're probably not aware of.
First off, createCipher() accepts a 'password' that is hashed with MD5 to derive the actual key. So instead what you should be using is createCipheriv(), which allows you to pass the key (and IV) directly, like PHP's mcrypt accepts. In ECB mode, IVs are ignored, so you can just pass in an empty string for the IV parameter.
Secondly, PHP's mcrypt will magically pad both your input and your key with null bytes if they are less than the cipher's block size and key size, respectively.
So for AES-128-ECB, we need to make sure the input and key lengths are a multiple of 16 bytes. With all of this knowledge we then find that appropriate code for the built-in crypto module might look something like:
var crypto = require('crypto');
function makePadded(str) {
var buf;
var len = str.length;
if (str.length % 16)
len = str.length + (16 - str.length % 16);
buf = new Buffer(len);
buf.fill(0);
buf.write(str, 0, str.length);
return buf;
}
var ip = makePadded('MY_IP');
var secret = makePadded('MY_KEY');
var cipher = crypto.createCipheriv("AES-128-ECB", secret, '');
var encrypted = cipher.update(ip, 'binary', 'hex');
encrypted += cipher.final('hex');
// Slice off at 16 bytes to match the input length
encrypted = encrypted.slice(0, 32);
console.log(encrypted);
One last thing that might be worth mentioning is that MCRYPT_RIJNDAEL_128 in PHP can be used to do 128, 192, or 256 bit encryption. So in PHP if 0 < keylen <= 16 then 128-bit encryption will be used, 192-bit encryption will be used if 16 < keylen <= 24 and 256-bit encryption will be used if 24 < keylen <= 32. However in node, you will need to adjust the cipher name appropriately, as node does not do the kind of "automatic adjustment" that PHP does.
in nodejs - password must be a 'binary' encoded string or a buffer. in PHP, the deprecated #mcrypt_ecb expects a key to be a string

Sign Key HMAC SHA1 with Javascript

For some reason I am not able to create a signature from a private key in JS. Using this online help from google:
https://m4b-url-signer.appspot.com/
URL:https://google.maps.com/maps/api/geocode/json?address=New+York&client=test
Example Key (fake for the purposes of the exercise)
Key: QNade5DtdJKKZbidTsrIgONupc4=
(Result) Signature: XDsiH5JAY7kJLgA1K2PWlhTdO1k=
However, my javascript code:
var keyString = 'QNade5DtdJKKZbidTsrIgONupc4=';
console.log(keyString)
var urlString = encodeURIComponent('/maps/api/geocode/json?address=New+York&client=test');
console.log(urlString)
// We need to decode private key to binary
var decoded_key_words = CryptoJS.enc.Utf8.parse(keyString);
var decoded_key = CryptoJS.enc.Base64.stringify(decoded_key_words);
console.log(decoded_key);
var signature = CryptoJS.HmacSHA1(decoded_key,urlString);
console.log(signature);
// Encode binary signature to base 64
var encoded_signature = CryptoJS.enc.Base64.stringify(signature);
console.log(encoded_signature)
Gives me a signature:
bOenVNeXI6xI1xlSa77oqGKssyY=
I can't seem to figure out what I'm doing wrong. Am I decoding base64 incorrectly?
For the record, this worked:
<script src="sha.js"></script>
var url = '/maps/api/geocode/json?address=New+York&client=test';
var key = 'QNade5DtdJKKZbidTsrIgONupc4='
var hmacObj = new jsSHA(url, 'TEXT');
var hmacOutput = hmacObj.getHMAC(key,'B64','SHA-1','B64');
console.log(hmacOutput)
Giving me:
XDsiH5JAY7kJLgA1K2PWlhTdO1k=

Categories

Resources