Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How we can code a simple Alice to Bob conversation? I'm needing help to fill some empty spaces... #274

Closed
GarouDan opened this issue Jun 25, 2015 · 7 comments

Comments

@GarouDan
Copy link

   /*
    *  I think this will be very useful for other users and maybe we could put it in the docs.
     * Alice would like to talk to Bob on a secure way.
     * First Alice generate her RSA key pair. Bob do the same.
     *  Then Alice create a AES key, then send it to Bob
     *    (encrypted with the Bob's public key and signed with the Alice private key).
     *  Bob then encrypts a message with the AES key and send to Alice.
     *  Alice decrypt this message, read it and send an answer.
     */

  // 0 - Initializing:
  var pki = forge.pki;
  var rsa = forge.pki.rsa;
  var temp;

  //1 - Alice generate her keyPair:
  var alice = {};
  alice.pem = {};
  alice.passphrase = "Alice passphrase.";
    // 1.1 - Here I use a temp var because Alice would like to recreate her keyPair after:
  temp = rsa.generateKeyPair({bits: 2048, e: 0x10001});
  alice.pem.publicKey = pki.publicKeyToPem(temp.publicKey);
  alice.pem.privateKey = pki.encryptRsaPrivateKey(temp.privateKey, alice.passphrase);
  temp = null;

  console.log("alice.pem.publicKey", alice.pem.publicKey);
  console.log("alice.pem.privateKey", alice.pem.privateKey);

  //2 - Bob generate his keyPair too:
  var bob = {};
  bob.pem = {};
  bob.passphrase = "Bob passphrase.";
    //2.1 - Here I use a temp var because Bob would like to recreate his keyPair after:
  temp = rsa.generateKeyPair({bits: 2048, e: 0x10001});
  bob.pem.publicKey = pki.publicKeyToPem(temp.publicKey);
  bob.pem.privateKey = pki.encryptRsaPrivateKey(temp.privateKey, bob.passphrase);
  temp = null;

  console.log("bob.pem.publicKey", bob.pem.publicKey);
  console.log("bob.pem.privateKey", bob.pem.privateKey);

  //3 - Alice and Bob exchange their public keys (pem public keys are sent from internet).
  temp = bob.pem.publicKey; // Message from internet.
  alice.bob = {};
  alice.bob.publicKey = pki.publicKeyFromPem(temp);
  temp = alice.pem.publicKey; // Message from internet.
  bob.alice = {};
  bob.alice.publicKey = pki.publicKeyFromPem(temp);
  temp = null;

  //4 - Alice recreate her public and private keys from pem:
  alice.keyPair = {};
  alice.keyPair.publicKey = pki.publicKeyFromPem(alice.pem.publicKey);
  alice.keyPair.privateKey = pki.decryptRsaPrivateKey(alice.pem.privateKey, alice.passphrase);

  //5 - Bob recregate his public and private keys from pem too (to receive the message):
  bob.keyPair = {};
  bob.keyPair.publicKey = pki.publicKeyFromPem(bob.pem.publicKey);
  bob.keyPair.privateKey = pki.decryptRsaPrivateKey(bob.pem.privateKey, bob.passphrase);

  //6 - Alice generate a AES key:
  temp = {};
  alice.aes = {};
  alice.aes.bytes = {};
  alice.aes.bytes.key = forge.random.getBytesSync(24);
  alice.aes.bytes.iv = forge.random.getBytesSync(8);
  alice.aes.hex = {};
  alice.aes.hex.key = forge.util.bytesToHex(alice.aes.bytes.key);
  alice.aes.hex.iv = forge.util.bytesToHex(alice.aes.bytes.iv);
  alice.pem.aes = gserializer.serialize(alice.aes.hex);

  temp = null;
  console.log("alice.pem.aes", alice.pem.aes);

  //7 - Alice send the AES key (stored as pem) as a signed encrypted message to Bob over internet:
  temp = alice.pem.aes;
  temp = forge.util.createBuffer(temp, 'utf8');
  temp = temp.bytes();
  temp = alice.bob.publicKey.encrypt(temp);
  temp = {message: temp, signature: null};
  temp.signature = forge.md.sha1.create();
  temp.signature.update(temp.message, 'utf8');
  temp.signature = alice.keyPair.privateKey.sign(temp.signature);
  temp.message = forge.util.bytesToHex(temp.message);
  temp.signature = forge.util.bytesToHex(temp.signature);
  temp = gserializer.serialize(temp);
    //7.1 Now temp is sent to Bob.

  //8 - Bob receive de signed encrypted message, verify de signature and decrypt it.
  temp = gserializer.deserialize(temp);
  temp.message = forge.util.hexToBytes(temp.message);
  temp.signature = forge.util.hexToBytes(temp.signature);
  temp.md = forge.md.sha1.create();
  temp.md.update(temp.message, 'utf8');
  temp.verified = bob.alice.publicKey.verify(temp.md.digest().bytes(), temp.signature);
  if (temp.verified) {
    temp.message = bob.keyPair.privateKey.decrypt(temp.message);
  } else {
    throw Error("Unverified message.");
  }

  //9 - Bob recreate the AES key from pem:
  bob.pem.aes = temp.message;
  bob.aes = {};
  bob.aes.hex = gserializer.deserialize(bob.pem.aes);
  bob.aes.bytes = {};
  bob.aes.bytes.key = forge.util.hexToBytes(bob.aes.hex.key);
  bob.aes.bytes.iv = forge.util.hexToBytes(bob.aes.hex.iv);

  console.log("bob.pem.aes", bob.pem.aes);

  //10 - Bob send a message to Alice encrypted with the AES key:
  bob.aes.cipher = {};
  bob.aes.cipher = forge.cipher.createCipher('AES-CBC', bob.aes.bytes.key);
  bob.aes.cipher.start({
    iv: bob.aes.bytes.iv,
  });
  bob.aes.decipher = forge.cipher.createDecipher('AES-CBC', bob.aes.bytes.key);
  bob.aes.decipher.start({
    iv: bob.aes.bytes.iv
  });

  temp = "How are you Alice?";
  temp = forge.util.createBuffer(temp, 'utf8');

  bob.aes.cipher.update(temp);
  bob.aes.cipher.finish();
  temp = bob.aes.cipher.output;
  temp = {output: temp, buffer: forge.util.createBuffer()};
  temp.buffer.putBuffer(temp.output);
  temp = temp.buffer.getBytes();

  //11 - Alice decrypt the message sent by Bob encrypted with the AES key:
  alice.aes.cipher = {};
  alice.aes.cipher = forge.cipher.createCipher('AES-CBC', alice.aes.bytes.key);
  alice.aes.cipher.start({
    iv: alice.aes.bytes.iv,
  });
  alice.aes.decipher = forge.cipher.createDecipher('AES-CBC', alice.aes.bytes.key);
  alice.aes.decipher.start({
    iv: alice.aes.bytes.iv
  });

  temp = forge.util.createBuffer(temp);
  alice.aes.decipher.update(temp);
  alice.aes.decipher.finish();
  temp = alice.aes.decipher.output.getBytes();
  console.log("Alice received a message:", temp);

  //12 - Alice send a answer to Bob encrypted with the AES key:
  temp = "I'm fine Bob. And you?";
  temp = forge.util.createBuffer(temp, 'utf8');

  alice.aes.cipher.update(temp);
  alice.aes.cipher.finish();
  temp = alice.aes.cipher.output;
  temp = {output: temp, buffer: forge.util.createBuffer()};
  temp.buffer.putBuffer(temp.output);
  temp = temp.buffer.getBytes();

  //13 - Finally, Bob decrypt the message encrypted with the AES key:
  temp = forge.util.createBuffer(temp);
  bob.aes.decipher.update(temp);
  bob.aes.decipher.finish();
  temp = bob.aes.decipher.output.getBytes();
  console.log("Bob received a message:", temp);

  console.log("alice:", alice, "bob", bob);
@GarouDan
Copy link
Author

For now I receive the following error:
forge.js:13721 Uncaught Error: Unknown message digest algorithm.
at
temp = alice.keyPair.privateKey.sign(temp);

How we can complete the code? I didn't find a way until now using the docs.

@dlongley
Copy link
Member

I recommend AES over 3DES, but that aside...

You could just base64-encode the encrypted symmetric keys and IVs and transmit the data using your own custom format (eg: a JSON container).

If you're looking for something standard, PKCS#7 will get close to what you want but perhaps not all the way. "Secure Messaging" is being worked on as a JSON-LD format to do pretty much exactly what you want here:

https://web-payments.org/specs/source/secure-messaging/

@dlongley
Copy link
Member

forge.js:13721 Uncaught Error: Unknown message digest algorithm.

The private key's sign() method is expecting a message digest, as a digest of what you want signed is typically what is actually signed. So create a message digest object, consume temp, and then pass the object to sign. You should be able to see how this is done in the examples under RSA in the README.

Then you can base64 encode the signature for transport.

@msporny
Copy link
Member

msporny commented Jun 30, 2015

Note that "Secure Messaging" has changed slightly to just be about Linked Data signatures and key management: https://web-payments.org/specs/source/ld-signatures/

@GarouDan
Copy link
Author

I suggest update the readme to be more clear. I don't know about the others but I had difficulties to understand what should be done next.
I have created a full working code, but I don't know if this code is the best one. I have updated it on the first comment. I just did it because I used gserializer. Maybe there is a way to do it using just forge. If possible, could you rewrite the code to have fewer steps (and using just forge or standard javascript methods)? (Using AES now.)

@dlongley
Copy link
Member

dlongley commented Jul 1, 2015

Forge provides JS-based components for working with crypto systems, it doesn't specify what systems to use or how to put them together. In fact, the security of complete systems is more complicated to analyze than component parts on their own. So we don't necessarily want to say "This is how you build a full, secure, message system with forge." That being said, showing an example for how to put an extremely naive system together may be ok, but we must provide appropriate disclaimers.

Anyway, here's some refactoring of your code for you to explore further:

var forge = require('node-forge');

// 1. Alice generates her key pair.
var alice = {
  passphrase: 'Alice passphrase'
};
generateKeyPair(alice);

// 2. Bob generates his key pair.
var bob = {
  passphrase: 'Bob passphrase'
};
generateKeyPair(bob);

// 3. Alice sends her PEM public key to Bob via the Internet.
bob.friend = {
  publicKey: alice.publicKey
};

// 4. Bob sends his PEM public key to Alice via the Internet.
alice.friend = {
  publicKey: bob.publicKey
};

// 5. Alice sends a secret message to Bob via the Internet as JSON.
var encryptedMessage = encryptMessage(alice, 'How are you?');
var json = JSON.stringify(encryptedMessage, null, 2);
console.log('Alice sends: ' + json);

// 6. Bob parses and decrypts the secret message.
encryptedMessage = JSON.parse(json);
var message = decryptMessage(bob, encryptedMessage);

console.log('Bob sees that Alice says: ', message);

// 7. Bob sends a secret response to Alice via the Internet.
encryptedMessage = encryptMessage(bob, 'Doing well.');
json = JSON.stringify(encryptedMessage, null, 2);
console.log('Bob sends: ' + json);

// 8. Alice parses and decrypts the secret message.
encryptedMessage = JSON.parse(json);
var message = decryptMessage(alice, encryptedMessage);

console.log('Alice sees that Bob says: ', message);


function generateKeyPair(person) {
  var pair = forge.rsa.generateKeyPair({bits: 2048, e: 0x10001});
  person.publicKey = forge.pki.publicKeyToPem(pair.publicKey);
  person.privateKey = forge.pki.encryptRsaPrivateKey(
    pair.privateKey, person.passphrase);
}

function encryptMessage(person, message) {
  // 1. Generate symmetric key and IV.
  var key = forge.random.getBytesSync(16);
  var iv = forge.random.getBytesSync(16);

  // 2. Symmetric-encrypt the message.
  var cipher = forge.cipher.createCipher('AES-CBC', key);
  cipher.start({iv: iv});
  cipher.update(forge.util.createBuffer(message, 'utf8'));
  cipher.finish();
  var encryptedMessage = cipher.output.getBytes();

  // 3. Sign the encrypted message.
  var privateKey = forge.pki.decryptRsaPrivateKey(
    person.privateKey, person.passphrase);
  var md = forge.md.sha256.create();
  md.update(encryptedMessage);
  var signature = privateKey.sign(md);

  // 4. Encrypt symmetric key w/friend's public key.
  var publicKey = forge.pki.publicKeyFromPem(
    person.friend.publicKey);
  var encryptedKey = publicKey.encrypt(key);

  // 5. Bundle the encrypted message, iv, and signature.
  return {
    encryptedMessage: forge.util.encode64(encryptedMessage),
    encryptedKey: forge.util.encode64(encryptedKey),
    iv: forge.util.encode64(iv),
    signature: forge.util.encode64(signature)
  };
}

function decryptMessage(person, bundle) {
  // 1. Verify the signature on the encrypted message.
  var encryptedMessage = forge.util.decode64(bundle.encryptedMessage);
  var publicKey = forge.pki.publicKeyFromPem(person.friend.publicKey);
  var signature = forge.util.decode64(bundle.signature);
  var md = forge.md.sha256.create();
  md.update(encryptedMessage);
  var verified = publicKey.verify(md.digest().getBytes(), signature);
  if(!verified) {
    throw new Error('Unverified message.');
  }

  // 2. Decrypt the symmetric key using the private key.
  var encryptedKey = forge.util.decode64(bundle.encryptedKey);
  var privateKey = forge.pki.decryptRsaPrivateKey(
    person.privateKey, person.passphrase);
  var key = privateKey.decrypt(encryptedKey);

  // 3. Symmetric-decrypt the message.
  var iv = forge.util.decode64(bundle.iv);
  var cipher = forge.cipher.createDecipher('AES-CBC', key);
  cipher.start({iv: iv});
  cipher.update(forge.util.createBuffer(encryptedMessage));
  if(!cipher.finish()) {
    throw new Error('Decryption failed.');
  }

  // 4. Return the decrypted message.
  return cipher.output.getBytes();
}

@dlongley
Copy link
Member

dlongley commented Jul 1, 2015

An example of a flaw with this approach is that an adversary can potentially capture and replay the messages sent at a later time (a MiTM attack). So, for example, if Bob and Alice are communicating in the clear and their conversation looks like this:

Alice: These bananas are old. What should I do with them?
Bob: Please throw them out.
Alice: Ok.

Then an adversary can capture Bob's response and wait to replay it...

Alice: I found your diamonds. What should I do with them?
**Adversary Replays Bob's message**: Please throw them out.
Alice: Ok.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants