TripleDes CBC Nodejs implementation throuble - node.js

i need to replicate in Nodejs the results of the 3DS CBC encrypt in http://tripledes.online-domain-tools.com/.
This is my code:
const crypto = require('crypto');
const cipher = crypto.createCipher('des-ede3-cbc', key);
password = Buffer.from('MYPASS', 'utf8');
let encrypted = [cipher.update(password)];
encrypted.push(cipher.final());
encrypted = Buffer.concat(encryptedArr);
console.log(encrypted.toString('hex'));
The result of tripledes.online-domain-tools.com is:
Note that the result should be 59 30 20 02 a5 8c dd 5e, but my code gives me 33 97 d8 b0 e3 00 d1 53.
What am i missing?
Edit2:
Following your suggestions, I changed my code (Also added some Tests made with the guide of the NIST Publication):
const crypto = require('crypto');
function encrypt (inputkey, keyformat, password, passwordformat) {
let shortkey = Buffer.from(inputkey, keyformat);
let key = Buffer.alloc(24);
key.fill('\0');
for (i = 0; i < shortkey.length; i++) {
key[i] = shortkey[i];
}
let IV = Buffer.alloc(8);
const cipher = crypto.createCipheriv('des-ede3-cbc', key, IV);
password = Buffer.from(password, passwordformat);
let encryptedArr = [cipher.update(password)];
encryptedArr.push(cipher.final());
encrypted = Buffer.concat(encryptedArr);
return encrypted;
}
console.log(encrypt('1046913489980131','hex','0000000000000000','hex')); // works
console.log(encrypt('1007103489988020','hex','0000000000000000','hex')); // works
console.log(encrypt('10071034C8980120','hex','0000000000000000','hex')); // works
console.log(encrypt('1046103489988020','hex','0000000000000000','hex')); // works
console.log(encrypt('MYKEY','utf8','MYPASS','utf8')); // fails
Every Permutation Operation Known Answer Test of the NIST works great, but several other examples (including the one of the image) just fails
The reason i'm testing with this shady page is because my service provider is using it as reference.

This site made me some troubles for some time , well here is the implementation it uses internally to expand the key to be a 24 bytes !
i am going to talk about tripledes but i guess this would apply to other algorithms used by this site
step 1
it first checks if the key entered has the length that it expects it to be, (you can find a table at the bottom of that site telling the length of key for each Encryption algorithm)
if it does not it will complete with 0x00 bytes like this:
var key;// is a string containing the bytes wich will be used to encrypt the msg
var nullByte = 0x00;
var padding_needed;
for (var i=key.length ;i < expected_key_length ; ++)
{padding_needed =padding_needed + nullBute.tostring(16); }
key = key + padding_needed
so for example the length that it expects for 3DES is 24 bytes ; if you happen to enter just 15 bytes like this (112233445566778899aabbccddeeff) it will be like if you entered (112233445566778899aabbccddeeff00)
step2
in the case of tripledes the algorithm to expand the 16 bytes to 24 bytes key (which is the key length required by the algorithm) this site has a simple approach to do that
it copies the first 8 bytes and append it to the end of the key like this
key =key + key.substring(0,8);
and that is the key that is going to be given to the 3DES encryption function to work with
this simple approache is not used by openssl for example , open ssl uses the first 8 bytes of the MD5 of the key ,and append them to the 16 bytes of the original key to get the 24 bytes key that is required by 3DES, like this
key = key + (MD5(key)).substring(0,8);
Summary
in that tool if you enter the key 112233445566778899AABBCCDDEEFF is the same as if you entered 112233445566778899AABBCCDDEEFF00 and same as if you entered 112233445566778899AABBCCDDEEFF001122334455667788 so to solve your problem you should give your function the complete 24 bytes of key that you gave to that site and you will surely get the same results, beacause nodejs is probably is doing the same thing as openssl does to expand the key(uses md5)
PS
if you are using the cbc mode which is your case try to specify the IV to be 8 bytes of \x00 like this "0000000000000000"
the results will be the same !!
here is a working implementation of your code you can check it in the site
const crypto = require('crypto');
function encrypt (inputkey, keyformat, password, passwordformat) {
let shortkey = Buffer.from(inputkey, keyformat);
let key = Buffer.alloc(24);
key.fill('\0');
for (i = 0; i < shortkey.length; i++) {
key[i] = shortkey[i];
}
let IV = Buffer.alloc(8);
var expansionStart = shortkey.length>16?shortkey.length:16;
for (i=expansionStart;i<24;i++){
key[i]=key[i-expansionStart];
}
console.log(key);
const cipher = crypto.createCipheriv('des-ede3-cbc', key, IV);
password = Buffer.from(password, passwordformat);
let encryptedArr = [cipher.update(password)];
encryptedArr.push(cipher.final());
encrypted = Buffer.concat(encryptedArr);
return encrypted;
}
var enc = encrypt("112233445566778899AABBCCDDEEFF","hex","password","utf8");
console.log(enc);

Related

Cipher and decipher a number into hex in nodejs

I am looking for an efficient way to cypher and decypher a number using the same key.
This is not used for cryptography or encrypting anything so it doesn't need to be secure.
I have a unique number and I always want the same result out of the cypher. The cypher shouldn't be too long (more than 6 characters).
I do care about the speed as I will be making roughly 1000/millisecond cyphers.
The max number I will be looking to cypher is 100,000,000 and considering the alphanumeric = 26 lowercase letters + 26 uppercase letters and 10 numbers for 6 characters that's about 5.680 * 10^9 combinations which should suffice.
Example of pseudocode:
let num_to_cypher = 1;
let cypher = cypher_this_number(num_to_cypher); // ==> Ax53iw
let decypher = decypher_this_number(cypher); // ==> 1
let num_to_cypher_ex_2 = 12
let cypher_ex_2 = cypher_this_number(num_to_cypher_ex_2); // ==> 2R5ty6
let decypher_ex_2 = decypher_this_number(cypher_ex_2); // ==> 1
Edit 1:
I could have done something like below, but I can't define the cypher's length in this example and I don't care about encryption so I could go with something faster.
function encrypt(text){
let cipher = crypto.createCipher('aes128','d6F3Efeq')
let crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
let decipher = crypto.createDecipher('aes128','d6F3Efeq')
let dec = decipher.update(text,'hex','utf8')
dec += decipher.final('utf8');
return dec;
}
I would use a good hash function. These 2 algorithms are pretty decent hash algorithms:
DJB2 (Daniel J Bernstein hash function:
Dr. Bernstein teaches maths and computer science at the University of Illinois at Chicago Circle. Can't find the original paper for this hash function.
NPM package at: https://www.npmjs.com/package/djb2a
FNV1a (Fowler–Noll–Vo hash function):
NPM package at https://www.npmjs.com/package/#sindresorhus/fnv1a
[Edited to note...]
Since it seems that you want a 2-way encoding, Base-64 is probably your friend here.
This will encode and decode any 32-bit signed integer value (ranging from -2,147,483,648 through +2,147,483,647 into 6 characters.
const {Buffer} = require('buffer');
const buf = Buffer.alloc(4);
const pad = [ '' , '', '==' , '=' ];
const MIN_INT32 = -2_147_483_648; // 0x80000000
const MAX_INT32 = +2_147_483_647; // 0x7FFFFFFF
function encode(n) {
if ( !Number.isInteger(n) || n < MIN_INT32 || n > MAX_INT32 ) {
throw new RangeError("n must be a valid 32-bit integer such that m is -2,147,483,648 >= m <= +2,147,483,647");
}
buf.writeInt32BE(n) ;
const b64 = buf.toString('base64').slice(0,-2);
return b64;
}
function decode(s) {
const b64 = s + pad[ s.length % 4 ];
buf.fill(b64, 'base64');
const n = buf.readInt32BE();
return n;
}
With that, it's a simple matter of:
onst n0 = 123_456_789;
const b64 = encode(n0);
const n1 = decode(b64);
console.log(`original: ${n0}` ) ;
console.log(`encoded: ${b64}` ) ;
console.log(`decoded: ${n1}` ) ;
Which produces:
original: 123456789
encoded: B1vNFQ
decoded: 123456789

How to use Diffie-Hellman with 2048 bits to create a cipher?

I'm trying to find an algorithm within the list that accommodates 2048 bit length with using crypto.createDiffieHellman(2048). In other words,
I have Alice and Bob using their corresponding secret keys to encrypt/decrypt messages to each other.
const crypto = require('crypto'),
assert = require('assert'),
algorithm = 'aes-256-cbc',
IV_LENGTH = 16,
DH_LENGTH = 2048;
const alice = crypto.createDiffieHellman(DH_LENGTH);
const aliceKey = alice.generateKeys();
const bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());
const bobKey = bob.generateKeys();
const aliceSecret = alice.computeSecret(bobKey);
const bobSecret = bob.computeSecret(aliceKey); // should be same as aliceSecret
const password = aliceSecret;
const iv = crypto.randomBytes(IV_LENGTH).toString('hex').slice(0, IV_LENGTH);
function encrypt(text){
const cipher = crypto.createCipheriv(algorithm, password, iv)
const crypted = `${cipher.update(text,'utf8','hex')}${cipher.final('hex')}`
return crypted;
}
function decrypt(text){
const decipher = crypto.createDecipheriv(algorithm, password, iv)
const dec = `${decipher.update(text,'hex','utf8')}${decipher.final('utf8')}`
return dec;
}
const msg = encrypt('Test');
const decryptedMsg = decrypt(msg)
console.log(msg, decryptedMsg);
This throws an error Invalid key length. One way to fix this is to do DH_LENGTH = 256. However, this is not a good idea with recommended minimum length being 2048 bits. Now, I can create a key with 2048 and do a slice on a length of 256 but how is this any different from doing a 256 bit DH. Basically the attacker having to guess the first/last 256 bits.
You are right, you should stick to the recommended size for DHKE. A common way is using a key derivation function on the output of the Diffie-Hellman Key Exchange.
HKDF is fine for you. HKDF follows the "extract-then-expand" paradigm and, normally, the expand would be enough if you find and implementation that gives access to those functions. The below is from futoin-hkdf;
const hkdf = require('futoin-hkdf');
// Parameter overview
//-------------------
// initial keying material
const ikm = 'string-or-buffer';
// required output length in bytes
const length = 32;
// can be empty string or false equivalent
const salt = 'strongly-encouraged';
// optional parameter
const info = 'optional-context';
// HMAC hashing algorithm to use
const hash = 'SHA-256';
// Generic derivation
//-------------------
hkdf(ikm, length, {salt, info, hash}); // Buffer(length) - derived key
IKM is your derived key, and please don't call it passwords. It is more than that. Naming like sharedKey or exchangedKey is better.
The optional context can be used for domain separation so that you can derive different keys for different applications. See the datils here; Multiple AES Key Derivation from a master key
And, for forward secrecy, don't forget to erase the key after use.
Why should we process the result of the Diffie-Hellman Key Exchange?
The security of Diffie-Hellman key exchange is based on the Decisional Diffie-Hellman assumption. This assumption says that the exchanged key is a group element that is computationally indistinguishable from a random/uniformly distributed element in the group.
One must note that the result is not uniformly distributed element, i.e. each bit has 1/2 probability for being 0 or 1. The MSB may not be uniformly distributed.
The recommended way to extract the extropy is by using a hash or better to be processed by a Key Derivation Function. HKDF would be fine here.

AES - how do I convert a string(byte string) to a byte(byte string)?

I am creating an encrypted chat program using python and AES. I have some working code that I gathered from https://riptutorial.com/python/example/18926/symmetric-encryption-using-pycrypto
import hashlib
import math
import os
import base64
from Crypto.Cipher import AES
IV_SIZE = 16 # 128 bit, fixed for the AES algorithm
KEY_SIZE = 32 # 256 bit meaning AES-256, can also be 128 or 192 bits
SALT_SIZE = 16 # This size is arbitrary
cleartext = b'Lorem ipsum'
password = b'highly secure encryption password'
salt = os.urandom(SALT_SIZE)
derived = hashlib.pbkdf2_hmac('sha256', password, salt, 100000,
dklen=IV_SIZE + KEY_SIZE)
iv = derived[0:IV_SIZE]
key = derived[IV_SIZE:]
encrypted = salt + AES.new(key, AES.MODE_CFB, iv).encrypt(cleartext)
print(encrypted)
############################################
# edit here: it is being pulled as a string instead of a byte string
encrypted = str(encrypted)
###########################################
# encrypted and enc2 should be the same, except for the salt.
encryptedString = base64.encodebytes(encrypted)
print(encryptedString) # <- this is what can be stored and fetched from mySQL
encrypted = base64.decodebytes(encryptedString) # <- get the bytes back
salt = encrypted[0:SALT_SIZE]
derived = hashlib.pbkdf2_hmac('sha256', password, salt, 100000,
dklen=IV_SIZE + KEY_SIZE)
iv = derived[0:IV_SIZE]
key = derived[IV_SIZE:]
cleartext = AES.new(key, AES.MODE_CFB, iv).decrypt(encrypted[SALT_SIZE:])
print(cleartext)
When I run the code above I get a TypeError: expected bytes-like object, not str
I have tried a few methods:
bytes(encrypted, 'utf-8')
The problem with this method is that it ended up encrypting the string again.
encrypted.encode()
This does the same thing as before.
How do I convert a string(byte string) to a byte(byte string) without having to manually copy and paste the string and place a b in front of it?
I ran the example you linked to and it worked fine, so it seems like your challenge is storing binary data into strings and recovering the bytes again. Arbitrary bytes are not going to be valid unicode strings, so you'll need to transcode them into a valid text representation, such as Base64.
Here's some code that is probably close to what you want:
import hashlib
import math
import os
import base64
from Crypto.Cipher import AES
IV_SIZE = 16 # 128 bit, fixed for the AES algorithm
KEY_SIZE = 32 # 256 bit meaning AES-256, can also be 128 or 192 bits
SALT_SIZE = 16 # This size is arbitrary
cleartext = b'Lorem ipsum'
password = b'highly secure encryption password'
salt = os.urandom(SALT_SIZE)
derived = hashlib.pbkdf2_hmac('sha256', password, salt, 100000,
dklen=IV_SIZE + KEY_SIZE)
iv = derived[0:IV_SIZE]
key = derived[IV_SIZE:]
encrypted = salt + AES.new(key, AES.MODE_CFB, iv).encrypt(cleartext)
print(encrypted)
enc2=b'J\x82K\xf6\x10j\x06\xdbK\xd6\xf2\xc2Y\x98\xb9\x16\xb7\x0f\xfa\xb9\x19?\x9ak\xa1\xd5\xc4'
# encrypted and enc2 should be the same, except for the salt.
print(enc2)
encryptedString = base64.encodebytes(enc2)
print(encryptedString) # <- this is what can be stored and fetched from mySQL
encrypted = base64.decodebytes(encryptedString) # <- get the bytes back
salt = encrypted[0:SALT_SIZE]
derived = hashlib.pbkdf2_hmac('sha256', password, salt, 100000,
dklen=IV_SIZE + KEY_SIZE)
iv = derived[0:IV_SIZE]
key = derived[IV_SIZE:]
cleartext = AES.new(key, AES.MODE_CFB, iv).decrypt(encrypted[SALT_SIZE:])
print(cleartext)
Running this, I get the expected output:
b'\xc2\xc6\x81\x9f1\xdb#I\x155\xab6\xe5K\xb5\xfb\xe9\x93\x9c\xd6\xfc\xe1EN?\xdd\xaa'
b'J\x82K\xf6\x10j\x06\xdbK\xd6\xf2\xc2Y\x98\xb9\x16\xb7\x0f\xfa\xb9\x19?\x9ak\xa1\xd5\xc4'
b'SoJL9hBqBttL1vLCWZi5FrcP+rkZP5prodXE\n'
b'Lorem ipsum'
The first two lines are the raw bytes, the 3rd line is the Base64 encoded string, which is used to decrypt Lorem imsum from.

Is the nonce in a HMAC useful to increase the security, to generate a random number based on the seed?

To generate a number based on a seed, I written this code:
var crypto = require('crypto'),
//generate the crypto random token
clientToken = crypto.createHash("sha256").update(crypto.randomBytes(128)).digest('hex')
serverToken = crypto.createHash("sha256").update(crypto.randomBytes(128)).digest('hex'),
//generate the seed
hmac = crypto.createHmac('sha512', serverToken);
hmac.update(clientToken);
var seed = hmac.digest('hex'),
//generate the random number with a PRNG algorithm
prng = new require("seedrandom")(seed),
random = Math.floor(prng() * (100000000 - 10000)) + 10000;
//log the results
console.log("clientToken: ", clientToken);
console.log("serverToken: ", serverToken);
console.log("Seed : ", seed);
console.log("Random number:", random);
As you can see, I don't HMAC a nonce value and I would to know if digesting it, will add more security.
This could be the code updated with the nonce implementation added:
//generate the nonce by using a nanosecond timestamp
var hrtime = process.hrtime(),
nonce = (hrtime[0] * 1e9 + hrtime[1]).toString();
nonce = crypto.createHash("sha1").update(nonce).digest('hex');
//generate the seed
var hmac = crypto.createHmac('sha512', serverToken);
hmac.update(clientToken);
hmac.update(nonce);
var seed = hmac.digest('hex');
Adding the nonce, will increase the security ? An user that only knows the client token, could guess the hmac seed ? (With and without the nonce implementation)
Yes, it can be if you don't trust your random number generation enough to always generate a random number. In general it never hurts to add entropy to a scheme, as long as the method is secure. Often this means a hash or HMAC based construction.
If you can base all of your security on the serverToken then adding additional entropy doesn't seem to make much sense.

Signing a string with HMAC-MD5 with C#

I got the following HMAC key (in hexadecimal format):
52320e181a481f5e19507a75b3cae4d74d5cfbc328f7f2b738e9fb06b2e05b55b632c1c3d331dcf3baacae8d3000594f839d770f2080910b52b7b8beb3458c08
I need to sign this string:
1100002842850CHF91827364
The result should be this (in hexadecimal format):
2ad2f79111afd818c1dc0916d824b0a1
I have the following code:
string key = "52320e181a481f5e19507a75b3cae4d74d5cfbc328f7f2b738e9fb06b2e05b55b632c1c3d331dcf3baacae8d3000594f839d770f2080910b52b7b8beb3458c08";
string payload = "1100002842850CHF91827364";
byte[] keyInBytes = Encoding.UTF8.GetBytes(key);
byte[] payloadInBytes = Encoding.UTF8.GetBytes(payload);
var md5 = new HMACMD5(keyInBytes);
byte[] hash = md5.ComputeHash(payloadInBytes);
var result = BitConverter.ToString(hash).Replace("-", string.Empty);
However, I am not getting the result. What am I doing wrong?
when hashing with key HMAC md5
var data = Encoding.UTF8.GetBytes(plaintext);
// key
var key = Encoding.UTF8.GetBytes(transactionKey);
// Create HMAC-MD5 Algorithm;
var hmac = new HMACMD5(key);
// Compute hash.
var hashBytes = hmac.ComputeHash(data);
// Convert to HEX string.
return System.BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
Instead of doing this:
byte[] keyInBytes = Encoding.UTF8.GetBytes(key);
you need to convert key from a hex string to array of bytes. Here you can find example:
How do you convert Byte Array to Hexadecimal String, and vice versa?

Resources