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

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.

Related

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.

How to create a big list of different UUIDs, efficiently?

I want to generate tickets for an event. I need to generate a lot of them and I decided to have the ticket number as UUIDs. The question is how to generate a big list of UUIDs and it to be different.
I know the easy way to just check every new UUID generated in the already existing list, but this is not very performance friendly. :)
I am using NodeJS with UUID v4.
Thank you!
You could use home-grown UUID function, which is guaranteed to be unique pseudo-random integer in the range [0...2128). Below is one based on Linear Contguential Generator. Constants are taken from here or here. You only need to keep previous number/UUID at hands to generate next one, no need to check because it will be repeated only after full period of 2128.
Code relies on BigInt, tested with node v12
const a = 199967246047888932297834045878657099405n; // should satisfy a % 8n = 5n
const c = 1n; // should be odd
const m = (1n << 128n);
const mask = m - 1n;
function LCG128(state) {
return (BigInt(state) * a + c) & mask; // same as % m
}
q = 7654321n; // seed
q = LCG128(q);
q.toString(16); // first UUID
q = LCG128(q);
q.toString(16); // second UUID
q = LCG128(q);
q.toString(16); // third UUID
UPDATE
Just to be a more philosophical on the issue at hands:
You could consider UUID4 to be black box and trust it - this is what #ChrisWhite proposed
You could consider UUID4 to be black box and distrust it - that is whatyou proposed to check in the list or answer by #KevinPastor
Make your own transparent box which produces numbers in the proper range and be unique - that is my proposal
Beauty of LCG approach is that, given good multiplier and carry, it uniquely and reversable maps range [0...2128) into itself (it could do that for 64bit numbers, with different a, c, or 32bit numbers and so on and so forth). You could even use counter as input starting with 0 up to 2128-1 and it will produce non-repeatable numbers in the same range filling whole [0...2128). So you know that if you either chain it with previous uuid, or use counter, there is 0 chances of collision.
You can create an empty object and every time you generate a UUID, you add an attribute to that object where the key is the generated UUID. When you will generate another UUID, you just have to check if the object attribute is undefined or not to see if it's already used.
const uuids = [];
let uuidUsed = {};
const size = 10;
for (let i = 0; i < size; i++) {
let uuid = uuidv4();
while (uuidUsed[uuid] !== undefined) {
uuid = uuidv4();
}
uuidUsed[uuid] = true;
}
here’s a list of 446,538 IPs formatted in the following way: | id | date | name | uuid | ip |
https://minerlock.com/lock/F50f4b8d8e27e

Create a Diffie Hellman Object from hex encoded p and g parameters

Om my application I receive the diffie hellman p and g parameters via an XMPP message for example:
<message to="admin#0.0.0.0/4fjd0564xh" from="keygen1546701622316#conference.0.0.0.0/admin" type="groupchat"><body>
<x xmlns="http://pcmagas.tk/gkePlusp#intiator_key">
<p>d521b3f62a54c9a7274a2e6da70fb977e21129f0b40b0ec328b4c584572dea3c24a9d41feb13bbbcc2a894cbb8d26ccb036589ec55d2ebb32f4ec7d5c9eaa686820f06f8ff4863720f746b7b3bcd51be8b7bdfcead397600621c9b3932b3aebbe01c555e97bb6068de823f234851a883eba3d916255cfa3320d0d12c4c5fc80a263782b52636f0b978a765439c434ad71511f6be91b0f8ccb2a3cf3b54b02a68730354f3f4ff555a2f7c0bfaebc21fbbac003d7bd9e9b2b2f9bd7d73cf385a1c04c094d721a2adb0ef5f4b725ba529bd74bd73d23e0cc0f14e87d6df7bf6251ab0b8b54b63b01188675dbf56113a0e956e39e3230a3b86494902250211e762eb</p>
<g>02</g>
</x>
</body>
</message>
As Node.js crypto module says you can Initiate a Diffie Hellman either via an interger providing the g parameter length:
const alice = crypto.createDiffieHellman(2048);
Or via provided p and g parameters:
const bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());
But on my stream the keys are encoded as a hex string thus how I can convert them to the appropriate object in order to make the appropriate diffieHellman Object ?
As the following code shows the functions getPrime and getGenerator generate a Buffer object:
const crypto = require('crypto');
const k1 = crypto.createDiffieHellman(2048);
const p = k1.getPrime();
const g = k1.getGenerator();
// Outputs true,true
console.log(Buffer.isBuffer(p), Buffer.isBuffer(g));
So you can generate a Diffie Hellman Object from a hex encoded strings via:
//pInHex,gInHex hex encoded p and g
const p = Buffer.from(pInHex,'hex');
const g = Bugger.from(gInHex,'hex');
const dh = crypto.createDiffieHellman(p, g);
As you can see according to documentation you can create buffers from hex-encoded strings.

TripleDes CBC Nodejs implementation throuble

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);

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.

Resources